diff --git a/.dockerignore b/.dockerignore index 9f5e8187..b864b7fb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,16 +1,166 @@ -node_modules/ -.github/ -.vscode/ -data/ -dist/ -.husky/ - -.travis.yml -**.md -!README.md -docker-compose.yml -renovate.json -*.lock +# User-specific stuff +.idea/ + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xm + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### JetBrains+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### JetBrains+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Kotlin ### +# Compiled class file +*.class + +# Log file *.log -application.yml + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Gradle ### +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +### Gradle Patch ### +**/build/ + +# other stuff config.yml +.github +LICENSE +README.md +renovate.json +docker-compose.yml diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b38db2f2..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -build/ diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index f7be1b09..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": ["prettier", "@augu/eslint-config/ts.js"], - "plugins": ["prettier"], - "rules": { - "@typescript-eslint/indent": "off", - "quote-props": ["error", "consistent-as-needed"], - "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }] - } -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..64023115 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @auguwu @IceeMC diff --git a/.github/workflows/ESLint.yml b/.github/workflows/ESLint.yml deleted file mode 100644 index b7114582..00000000 --- a/.github/workflows/ESLint.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: ESLint Workflow -on: - push: - branches: - - 'feature/**' - - 'issue/gh-**' - - master - - edge - - paths-ignore: - - '.github/**' - - '.husky/**' - - '.vscode/**' - - 'assets/**' - - 'locales/**' - - 'docker/**' - - '.dockerignore' - - '.eslintignore' - - '.gitignore' - - '**.md' - - 'LICENSE' - - 'renovate.json' - - pull_request: - branches: - - 'feature/**' - - 'issue/gh-**' - - master - - edge - - paths-ignore: - - '.github/**' - - '.husky/**' - - '.vscode/**' - - 'assets/**' - - 'locales/**' - - 'docker/**' - - '.dockerignore' - - '.eslintignore' - - '.gitignore' - - '**.md' - - 'LICENSE' - - 'renovate.json' -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: 'Checkout repository' - uses: actions/checkout@v2 - - - name: Use Node.js v16 - uses: actions/setup-node@v2 - with: - node-version: 16.x - - - name: Installs all global packages - run: npm install -g eslint typescript - - - name: Installs local packages - run: yarn - - - name: Lints the repository and checks for linting errors - run: eslint src --ext .ts - - - name: Compiles the project to check for any build errors - run: tsc --noEmit diff --git a/.github/workflows/Production.yml b/.github/workflows/Production.yml new file mode 100644 index 00000000..b6fb6cf1 --- /dev/null +++ b/.github/workflows/Production.yml @@ -0,0 +1,69 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: Update Production instance +on: + release: + types: + - published + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checks out the repository + uses: actions/checkout@v3 + + - name: Get the current git tag to use + id: tag + uses: dawidd6/action-get-tag@v1 + with: + strip_v: true + + - name: Login to the registry + run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin + + - name: Build the container + run: docker build --no-cache . -t registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + - name: Push to the registry + run: docker push registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + deploy: + needs: build-container + runs-on: ubuntu-latest + steps: + - name: Login to Kubernetes + run: | + mkdir ~/.kube + echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config + + - name: Get the current git tag to use + id: tag + uses: dawidd6/action-get-tag@v1 + with: + strip_v: true + + - name: Set tag + run: kubectl set image deployment/nino-prod nino-prod=registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + - name: Deploy to the bot + run: kubectl rollout status deployment/nino-prod diff --git a/.github/workflows/Sentry.yml b/.github/workflows/Sentry.yml new file mode 100644 index 00000000..aa16f92b --- /dev/null +++ b/.github/workflows/Sentry.yml @@ -0,0 +1,42 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: Update Sentry release on sentry.floof.gay +on: + release: + types: + - created +jobs: + sentry-release: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: noelware + SENTRY_PROJECT: nino + SENTRY_URL: https://sentry.floof.gay + with: + environment: production diff --git a/.github/workflows/Shortlinks.yml b/.github/workflows/Shortlinks.yml new file mode 100644 index 00000000..d2f3c543 --- /dev/null +++ b/.github/workflows/Shortlinks.yml @@ -0,0 +1,60 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: Update Shortlinks +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' +jobs: + shortlinks: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Update shortlinks + uses: NinoDiscord/actions@master + with: + command: shortlinks + + - name: Setup Git configuration + run: | + git config --global user.name 'Noel[bot]' + git config --global user.email 'ohlookitsaugust@gmail.com' + git config --global committer.email 'cutie@floofy.dev' + git config --global committer.name 'Noel' + + - name: Check if git status is dirty + id: git_status + run: | + if [ -n "$(git status --porcelain)" ]; then + echo '::set-output name=STATUS_DIRTY::true' + else + echo '::set-output name=STATUS_DIRTY::false' + fi + + - name: Commit changes (if dirty) + if: contains(steps.git_status.outputs.STATUS_DIRTY, 'true') + run: | + git add . + git commit -m "chore(automate): update shortlinks.json file" + git push -u origin $(git rev-parse --abbrev-ref HEAD) diff --git a/.github/workflows/Staging.yml b/.github/workflows/Staging.yml new file mode 100644 index 00000000..484d4b99 --- /dev/null +++ b/.github/workflows/Staging.yml @@ -0,0 +1,69 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: Updating Beta instance +on: + release: + types: + - prereleased + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checks out the repository + uses: actions/checkout@v3 + + - name: Get the current git tag to use + id: tag + uses: dawidd6/action-get-tag@v1 + with: + strip_v: true + + - name: Login to the registry + run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin + + - name: Build the container + run: docker build --no-cache . -t registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + - name: Push to the registry + run: docker push registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + deploy: + needs: build-container + runs-on: ubuntu-latest + steps: + - name: Login to Kubernetes + run: | + mkdir ~/.kube + echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config + + - name: Get the current git tag to use + id: tag + uses: dawidd6/action-get-tag@v1 + with: + strip_v: true + + - name: Set tag + run: kubectl set image deployment/nino-edge nino-edge=registry.floofy.dev/nino/bot:${{ steps.tag.outputs.tag }} + + - name: Deploy to the bot + run: kubectl rollout status deployment/nino-edge diff --git a/.github/workflows/edge.yml b/.github/workflows/edge.yml deleted file mode 100644 index 02faa681..00000000 --- a/.github/workflows/edge.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Worklow to update Nino Edge, development bot for testing the edge branch - -name: Updating Beta instance -on: - workflow_dispatch: - push: - branches: - - edge - - paths-ignore: - - '.github/**' - - '.husky/**' - - '.vscode/**' - - 'assets/**' - - 'locales/**' - - 'docker/**' - - '.dockerignore' - - '.eslintignore' - - '.gitignore' - - '**.md' - - 'LICENSE' - - 'renovate.json' - -jobs: - build-container: - runs-on: ubuntu-latest - steps: - - name: Checks out the repository - uses: actions/checkout@v2 - - - name: Login to the registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin - - - name: Build the container - run: docker build --no-cache . -t registry.floofy.dev/noelware/nino:edge-${{github.sha}} - - - name: Push to the registry - run: docker push registry.floofy.dev/noelware/nino:edge-${{github.sha}} - - deploy: - needs: build-container - runs-on: ubuntu-latest - steps: - - name: Login to Kubernetes - run: | - mkdir ~/.kube - echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config - - - name: Set tag - run: kubectl set image deployment/nino-edge nino-edge=registry.floofy.dev/noelware/nino:edge-${{github.sha}} - - - name: Deploy to the bot - run: kubectl rollout status deployment/nino-edge diff --git a/.github/workflows/ktlint.yml b/.github/workflows/ktlint.yml new file mode 100644 index 00000000..cb4045cd --- /dev/null +++ b/.github/workflows/ktlint.yml @@ -0,0 +1,105 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +name: ktlint +on: + push: + branches: + - 'feature/**' + - 'issue/gh-**' + - master + - edge + + paths-ignore: + - '.github/**' + - '.husky/**' + - '.vscode/**' + - 'assets/**' + - 'locales/**' + - 'docker/**' + - '.dockerignore' + - '.eslintignore' + - '.gitignore' + - '**.md' + - 'LICENSE' + - 'renovate.json' + + pull_request: + branches: + - 'feature/**' + - 'issue/gh-**' + - master + - edge + + paths-ignore: + - '.github/**' + - '.husky/**' + - '.vscode/**' + - 'assets/**' + - 'locales/**' + - 'docker/**' + - '.dockerignore' + - '.eslintignore' + - '.gitignore' + - '**.md' + - 'LICENSE' + - 'renovate.json' +jobs: + ktlint: + name: Linting and Unit Tests + runs-on: ubuntu-latest + services: + redis: + image: bitnami/redis:latest + ports: + - 6379:6379 + timeouts: + image: ghcr.io/ninodiscord/timeouts/timeouts:latest + env: + AUTH: owodauwu + REDIS_HOST: redis + REDIS_PORT: 6379 + ports: + - 4025:4025 + steps: + - name: Checks out the repository + uses: actions/checkout@v3 + + - name: Sets up Java 17 + uses: actions/setup-java@v3 + with: + distribution: temurin # Eclipse Temurin is <3 + java-version: 17 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Lets ./gradlew be executable + run: chmod +x ./gradlew + + - name: Lints the repository for any code errors + run: ./gradlew spotlessCheck --no-daemon + + - name: Builds the project for any errors + run: ./gradlew compileKotlin --no-daemon + + - name: Unit test project + run: ./gradlew kotest diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml deleted file mode 100644 index 18946ec9..00000000 --- a/.github/workflows/prod.yml +++ /dev/null @@ -1,53 +0,0 @@ -# Worklow to update Nino, production bot you see today! - -name: Update Production instance -on: - workflow_dispatch: - push: - branches: - - master - - paths-ignore: - - '.github/**' - - '.husky/**' - - '.vscode/**' - - 'assets/**' - - 'locales/**' - - 'docker/**' - - '.dockerignore' - - '.eslintignore' - - '.gitignore' - - '**.md' - - 'LICENSE' - - 'renovate.json' - -jobs: - build-container: - runs-on: ubuntu-latest - steps: - - name: Checks out the repository - uses: actions/checkout@v2 - - - name: Login to the registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin - - - name: Build the container - run: docker build --no-cache . -t registry.floofy.dev/noelware/nino:${{github.sha}} - - - name: Push to the registry - run: docker push registry.floofy.dev/noelware/nino:${{github.sha}} - - deploy: - needs: build-container - runs-on: ubuntu-latest - steps: - - name: Login to Kubernetes - run: | - mkdir ~/.kube - echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config - - - name: Set tag - run: kubectl set image deployment/nino-prod nino-prod=registry.floofy.dev/noelware/nino:${{github.sha}} - - - name: Deploy to the bot - run: kubectl rollout status deployment/nino-prod diff --git a/.gitignore b/.gitignore index c1ff570a..718c2cf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,571 @@ -# Folders -node_modules/ -old_locales/ -.husky/_/ -build/ - -# Jest -coverage/ - -# Files -application.yml -config.yml -*.log -.env - -# Redis dumps, I run redis-server in the working directory :3 -*.rdb -launch.json - -# Ignore user-config -.vscode/ -.idea/ +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/* + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Kotlin ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope + +### Gradle ### +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +### Gradle Patch ### +**/build/ + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +*.code-workspace + +# Local History for Visual Studio Code + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +# other stuff +config.yml +docker/cluster-operator/config.json +logs/*.log +bot/src/main/resources/config/logging.properties +!bot/src/main/resources/config/logging.example.properties +api/src/main/resources/config/logging.properties +!api/src/main/resources/config/logging.example.properties diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 18913c0d..00000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -echo 'nino - ❓ lint ~ ❓ project - checking eslint for errors' -eslint src --ext .ts # `--fix` would normally be here but it should only print and not fix - -echo 'nino - ✔ lint ~ ❓ project - compiling project for errors' -yarn tsc --noEmit - -echo 'nino - ✔ lint ~ ✔ project - we are done here' diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index c20b01f2..00000000 --- a/.prettierignore +++ /dev/null @@ -1,16 +0,0 @@ -.github -.husky -.idea -assets -build - -.dockerignore -.env -.eslintignore -.gitignore -LICENSE -package-*.json -package.json -README.md -renovate.json -tsconfig.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 44aeb406..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "node_modules\\typescript\\lib" -} diff --git a/Dockerfile b/Dockerfile index 7bc43f34..8d7f506e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,17 @@ -FROM node:16-alpine +FROM eclipse-temurin:18-alpine AS builder -LABEL MAINTAINER="Nino " RUN apk update && apk add git ca-certificates - -WORKDIR /opt/Nino +WORKDIR / COPY . . -RUN apk add --no-cache git -RUN npm i -g typescript eslint typeorm -RUN yarn -RUN yarn build:no-lint -RUN yarn cache clean +RUN chmod +x gradlew && ./gradlew :bot:installDist --stacktrace + +FROM eclipse-temurin:18-alpine AS builder -# Give it executable permissions -RUN chmod +x ./scripts/run-docker.sh +WORKDIR /app/noelware/nino +COPY --from=builder /docker/run.sh /app/noelware/nino/run.sh +COPY --from=builder /bot/build/install/bot /app/noelware/nino/bot +COPY --from=builder /docker/scripts/liblog.sh /app/noelware/nino/scripts/liblog.sh +COPY --from=builder /docker/docker-entrypoint.sh /app/noelware/nino/docker-entrypoint.sh -ENTRYPOINT [ "sh", "./scripts/run-docker.sh" ] +ENTRYPOINT [ "/app/noelware/nino/docker-entrypoint.sh" ] +CMD [ "/app/noelware/nino/run.sh" ] diff --git a/LICENSE b/LICENSE index b4c59589..baa7da17 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2019-2021 Nino +Copyright (c) 2019-2022 Nino Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e60d980b..1120e0cc 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,307 @@ -
-

🔨 Nino

-
Cute, advanced discord moderation bot made in Eris. Make your server cute and automated with utilities for you and your server moderators *:・゚✧*:・゚✧
-
+# [Nino](https://nino.sh) • GitHub Workflow Status GitHub License Noelware Server -
- GitHub Workflow Status - GitHub License - Noelware Server -
- -
+> :hammer: **Cute, advanced discord moderation bot made in Kord. Make your server cute and automated with utilities for you and your server moderators! ☆ ~('▽^人)** ## Features -- Auto Moderation: **Prevents raids, spam, ads, and much more!** -- Advanced warning system and automated punishments: **Automically punish who commit offenses!** -- Simplicity: **Simplicity is key to any discord bot, and Nino makes sure of it! All commands are tailored to be simple yet powerful.** +- 🔨 **Auto Moderation** - Prevent raids, spam, ads, and much more! + - Target users who post invites to your server, spam in a certain interval, and much more! + - Thresholds, messages, prevent roles/members from automod is all customizable! + +- ⚙️ **Advanced Configuration** - Configure Nino to feel like Nino is a part of your server~ + - Anything to Nino (mod-log messages, automod threshold/messages/prevention, logging) is all customizable! + - Implement guild policies to implement custom events Nino will react to! + +- ✨ **Simplicity** - Simplicity is key to any discord bot, and Nino is sure of it! + - Commands are tailored to be powerful but simple to the end user. + - Retrieve help on a command using `x!help `, `x! --help/-h`, or `x! help`. + - Stuck on what command usage is like? You can run `x!help usage` on how arguments are executed. ...and much more! ## Support -Need support related to Nino or any microservices under the organization? Join in the **Noelware** Discord server in #support under the **Nino** category: +Need any help with Nino or any microservices under the **@NinoDiscord** organization? You can join the **Noelware** Discord server +under [**#support**](https://discord.com/channels/824066105102303232/824071651486335036): -[![discord embed owo](https://discord.com/api/v9/guilds/824066105102303232/widget.png?style=banner3)](https://discord.gg/ATmjFH9kMH) +![Noelware Discord Server](https://invidget.switchblade.xyz/ATmjFH9kMH) ## Contributing -View our [contributing guidelines](https://github.com/NinoDiscord/Nino/blob/master/.github/CONTRIBUTING.md) and [code of conduct](https://github.com/NinoDiscord/Nino/blob/master/.github/CODE_OF_CONDUCT.md) before contributing. +Before you get started to contribute to Nino, view our [contributing guidelines](https://github.com/NinoDiscord/Nino/blob/master/.github/CONTRIBUTING.md) and our [code of conduct](https://github.com/NinoDiscord/Nino/blob/master/.github/CODE_OF_CONDUCT.md) before contributing. ## Self-hosting -Before attempting to self-host Nino, we didn't plan for users to be able to self-host their own instance of Nino. Most builds are usually buggy and untested as of late, we do have a "stable" branch but it can be buggy sometimes! If you want to use cutting edge features that are most likely not finished, view the [edge](https://github.com/NinoDiscord/Nino/tree/edge) branch for more details. The "stable" branch is master, so anything that should be stable will be added to the upstream. - -We will not provide support on how to self-host Nino, use at your own risk! If you do not want to bother hosting it, you can always invite the [public instance](https://discord.com/oauth2/authorize?client_id=531613242473054229&scope=bot) which will be the same experience if you hosted it or not. +Before to self-host Nino, ***we will not give you support on how to run your own Nino!*** The source code is available for +education purposes, and is not meant to run on small instances. Also, we have not planned for people to self-host their own +instance of Nino, since the source code and the main bots ARE identical from the source code. And, most builds pushed +to this repository have NOT been tested in production environments, so bewarn before running! If you do encouter bugs +with the bot or running it, please report it in our [issue tracker](https://github.com/NinoDiscord/Nino/issues) with the **bug** +label! ### Prerequisites -Before running your own instance of Nino, you will need the following tools: +Before running your own instance of Nino, you will need the following required tools: + +- [Timeouts Microservice](https://github.com/NinoDiscord/timeouts) **~** Used for mutes, bans, and more. This will not make Nino operate successfully. +- [PostgreSQL](https://postgresql.org) **~** Main database for holding user or guild data. Recommended version is 10 or higher. +- [Redis](https://redis.io) **~** Open source in-memory database storage to hold entities for quick retrieval. Recommended version is 5 or higher. +- [Java](https://java.com) **~** Language compiler for Gradle and Kotlin. Required version is JDK 16 or higher. + +If you're moving from **v0.x** -> **v2.x**, you will need to have your MongoDB instance and our utility for converting documents +into JSON, [Rei](https://github.com/NinoDiscord/Rei) before contiuning. + +#### Extra Tooling +There is tools that are *optional* but are mostly not-recommended in most cases: -- [Timeouts Service](https://github.com/NinoDiscord/timeouts) (Used for mutes and such or it'll not work!) -- [Node.js](https://nodejs.org) (Latest is always used in development, but LTS is recommended) -- [PostgreSQL](https://postgresql.org) (12 is used in development but anything above 10 should fairly work!) -- [Redis](https://redis.io) (6.2 is used in development but above v5 should work) +- [cluster-operator](https://github.com/MikaBot/cluster-operator) **~** Easily manages discord clustering between multiple nodes +- [Docker](https://docker.com) **~** Containerisation tool for isolation between the host and the underlying container. +- [Sentry](https://sentry.io) **~** Open-source application monitoring, with a focus on error reporting. -If you're moving from v0 to v1, you will need your MongoDB instance before to port the database and [Rei](https://github.com/NinoDiscord/Rei) installed on your system. +### Setup +You're almost there on how to run your instance! Before you frantically clone the repository and such, there is two options +on how to use Nino: -There is tools that are optional but are mostly recommended in some cases: +- Normally: You can spin up a **PM2** process to run Nino, which will run fine! +- Docker: Uses the [Dockerfile](./Dockerfile) to run Nino in a isolated container seperated from your machine and the host. -- [Sentry](https://sentry.io) - Useful to find out where errors are in a pretty UI -- [Docker](https://docker.com) - If you're a masochist and want to run a private instance with Docker -- [Git](https://git-scm.com) - Useful for fetching new updates easily. +#### Docker Setup +> ️️️️️️️⚠️ **BEFORE YOU CLONE, MAKE SURE DOCKER IS INSTALLED!** -### Setting up -There are 2 ways to setup Nino: using Docker or just doing shit yourself. Doing it yourself can very tedious -of how much Nino uses from v0 to v1 since Nino utilizes microservices! **☆♬○♩●♪✧♩((ヽ( ᐛ )ノ))♩✧♪●♩○♬☆** +1. **Clone the repository** using the `git clone` command: -### Docker ```sh -# 1. Clone the repository -$ git clone https://github.com/NinoDiscord/Nino.git && cd Nino +$ git clone https://github.com/NinoDiscord/Nino [-b edge] # If you want to use cutting edge features, +# add the `-b edge` flag! +``` + +2. **Change the directory** to `Nino` or however you named it, and create a image: -# 2. Create a image +```sh +$ cd Nino $ docker build -t nino:latest --no-cache . +``` + +3. **Run the image** to start the bot! + +```sh +# A volume is required for `.env` and `config.yml` so it can properly +# load your configuration! +$ docker run -d -v './config.yml:/app/Nino/config.yml:ro' -v './.env:/app/Nino/.env' nino:latest +``` -# 3. Run the image -$ docker run -d \ - --volume './config.yml:/opt/Nino/config.yml:ro' \ # read-only - nino:latest +4. **[OPTIONAL]** Use `docker-compose` to start all services. -# OPTIONAL: Use docker-compose.yml to run the services +```sh +# We provide a `docker-compose.yml` file so you can spin up the required +# services Nino requires without setting it up yourself. $ docker-compose up -d ``` -### Normal +#### Normal Setup +> ✏️ **Make sure you have a service to run Nino like `systemd` or `pm2`.** + +1. **Clone the repository** using the `git clone` command: + ```sh -# 1. Clone the repository -$ git clone https://github.com/NinoDiscord/Nino.git && cd Nino +$ git clone https://github.com/NinoDiscord/Nino [-b edge] # If you want to use cutting edge features, +# add the `-b edge` flag! +``` + +2. **Install import dependencies** -# 2. Install the dependencies -$ npm install +```sh +$ ./gradlew tasks +``` -# 3. Build the project -$ npm run build +3. **Build and compile** the Kotlin code -# 4. Run the project -$ npm start +```sh +$ ./gradlew :bot:build ``` -### Migrating from v0.x -> v1.x -If you used v0.x in the past, this is the process on how to migrate: +4. **Runs the project** -- Run `rei convert ...` to convert the documents into JSON, this process should take a while if there is a lot of cases or warnings. -- Run `node scripts/migrate.js `, where `` is the directory Rei converted your database to. +```sh +$ java -jar ./bot/build/libs/Nino.jar +``` -## Example `config.yml` file -- Replace `` with your Discord bot's token -- Replace `` with your PostgreSQL database username -- Replace `` with your PostgreSQL database password -- Replace `` (under `database`) with your PostgreSQL database host, if running locally, just use `localhost` or `database` if on Docker -- Replace `` with your PostgreSQL database port it's running, if running locally, set it to `5432` -- Replace `` (under `redis`) with your Redis connection host, if running locally, just use `localhost` or `redis` if on Docker -- Replace `` with the authenication token you set in the [timeouts](https://github.com/NinoDiscord/timeouts) relay service. +## Migrations +There has many changes towards the database when it comes to Nino, since all major releases have some-what a database revision once a while. -```yml -environment: development -token: +### v0.x -> v2.x +If you used **v0.x** in the past and you want to use the **v2.x** version, you can run the following commands: + +```sh +# Convert your MongoDB database into JSON file that the migrator script can read. +$ rei convert ... +# Runs the migrator script, if you're using Windows, +# use PowerShell: `./scripts/migrate.ps1` +$ ./scripts/migrate 0.x ./data +``` + +### v1.x -> v2.x +If you wish to migrate from **v1.x** towards **v2.x**, you can run the following commands: + +```sh +# Runs the migrator script +# *NIX: +$ ./scripts/migrate 1.x --password=... --user=... --database=... --host=... --port=... + +# PowerShell: +$ ./scripts/migrate.ps1 1.x -Password ... -User ... -Database ... -Host ... -Port ... +``` + +## Configuration +Nino's configurations are made up in a simple **config.yml** file located in the `root directory` of the project. The following keys +must be replaced: + +- **Replace `` with your [Discord bot's](https://discord.com/developers/applications) token.** +- **Replace `` with your Redis host** + - **If you are using Docker Compose, replace `` with "redis" since Compose will link the host with the container.** + - **If you're running it locally or the config key is not present, it'll infer as `localhost`** +- **Replace `` with your Redis network port** + - **If you are using Docker Compose, you can omit this config key since Compose will infer it to the redis container.** + - **If you're running it locally or the config key is not present, it'll infer as `6379`** +- **Replace `` with your PostgreSQL host.** + - **If you are using Docker Compose, replace `` with "postgres" since Compose will link the host with the container.** + - **If you're running it locally or the config key is not present, it'll infer as `localhost`** +- **Replace `` with your Redis network port** + - **If you are using Docker Compose, you can omit this config key since Compose will infer it to the redis container.** + - **If you're running it locally or the config key is not present, it'll infer as `5432`** + +```yml +# Returns the default locale Nino will use to send messages with. Our locales are managed +# under our GitHub repository for now, but this will change. +# +# Default: "en_US" +defaultLocale: "en_US" or "fr_FR" or "pt_BR" + +# Sets the environment for logging and such, `development` will give you debug logs +# in which you can report bugs and `production` will omit debug logs without +# any clutter. +# +# Default: "development" +environment: "development" or "production" + +# Sets the DSN url for configuring Sentry, this is not recommended on smaller instances! +# +# Default: Not present. +sentryDsn: ... + +# Returns the owners of the bot that can execute system admin commands like +# `eval`, `sh`, etc. +# +# Default: [empty array] +owners: + - owner1 + - owner2 + - ... + +# Yields your token to authenticate with Discord. This is REQUIRED +# and must be a valid token or it will not connect. +token: ... + +# Returns the token for `ravy.org` API, you cannot retrieve a key +# this is only for the public instances. +ravy: ... + +# Returns a list of prefixes to use when executing text-based commands prefixes: - - ! + - owo! + - uwu? + - pwp. -database: - url: postgres://:@:/ +# Returns the configuration for the botlists task. +# This is not recommended for smaller instances since using Nino and adding it +# to a public botlist will be deleted from it. +botlists: + # Returns the token for posting to Discord Services - https://discordservices.net + dservices: ... + + # Returns the token for posting to Discord Boats - https://discord.boats + dboats: ... + + # Returns the token for posting to Discord Bots - https://discord.bots.gg + dbots: ... + + # Returns the token for posting to top.gg - https://top.gg + topgg: ... + + # Returns the token for posting to Delly (Discord Extreme List) - https://del.rip + delly: ... + # Returns the token for posting to https://discords.com + discords: ... + +# Configuration for Redis for caching entities for quick retrival. +# Read our Privacy Policy on how we collect minimal data: https://nino.sh/privacy redis: - host: - port: 6379 + # Returns the password for authenticating to your Redis database. + password: ... + + # Returns an array of sentinels mapped to "host:port", + # this isn't required on smaller instances. + # Read more: https://redis.io/topics/sentinel + sentinels: + - host:port + - host2:port2 + + # Returns the master password for authenticating using the Sentinel + # approach. This is not required on smaller instances. + # Read more: https://redis.io/topics/sentinel + master: ... + + # Returns the index for Nino so it doesn't collide with any other + # Redis databases. + db: 1-16 + # Returns the redis host for connecting + host: ... + + # Returns the port for connecting + port: ... + +# Timeouts configuration timeouts: + # Returns the port for connecting to the WebSocket server. port: 4025 - auth: + + # Returns the authentication string for authorizing. + auth: ... + +# Instatus configuration for displaying the Gateway Ping +instatus: + # Metric component ID + # Use the instatus cli to retrieve: https://github.com/auguwu/instatus-cli + metricId: ... + + # The statuspage component ID + # Use the instatus cli to retrieve: https://github.com/auguwu/instatus-cli + component: ... + + # Instatus API key, fetch it here: + key: ... + +# Database configuration. Required! +database: + username: postgres + password: postgres + schema: public + host: + port: + name: nino ``` ## Maintainers -* Ice#4710 (DevOps, Developer) ([GitHub](https://github.com/IceeMC)) -* Rodentman87#8787 (Frontend Developer) ([GitHub](https://github.com/Rodentman87)) -* August#5820 (Project Lead, Developer) ([GitHub](https://github.com/auguwu)) +- [**Maisy ~ Rodentman87#8787**](https://likesdinosaurs.com) - Web Developer ([GitHub](https://githubc.om/Rodentman87)) +- [**Noel ~ August#5820**](https://floofy.dev) - Project Lead ([GitHub](https://github.com/auguwu)) +- [**Ice ~ Ice#4710**](https://winterfox.tech) - DevOps ([GitHub](https://github.com/IceeMC)) ## Hackweek Participants -* Chris ([GitHub](https://github.com/auguwu)) -* dondish ([GitHub](https://github.com/dondish)) -* Kyle ([GitHub](https://github.com/scrap)) -* Wessel ([GitHub](https://github.com/Wessel)) -* David ([GitHub](https://github.com/davidjcralph)) +> Since Nino was a submission towards [Discord's Hackweek](https://blog.discord.com/discord-community-hack-week-build-and-create-alongside-us-6b2a7b7bba33), this is a list of the participants that contributed to the project during June 23rd, 2019 - June 28th, 2019. + +- [**davidjcralph#9721**](https://davidjcralph.com) - ([GitHub](https://github.com/davidjcralph)) +- [**August#5820**](https://floofy.dev) - ([GitHub](https://github.com/auguwu)) +- [**dondish#8072**](https://odedshapira.me/) - ([GitHub](https://github.com/dondish)) +- [**Wessel#0498**](https://wessel.meek.moe) - ([GitHub](https://github.com/Wessel)) +- **Kyle** - ([GitHub](https://github.com/scrap)) + +## License +**Nino** is released under the **MIT License**, read [here](/LICENSE) for more information! 💖 diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 00000000..fb001b4a --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,54 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import gay.floof.gradle.utils.* + +plugins { + `nino-module` +} + +dependencies { + // Ktor (server) + implementation("io.ktor:ktor-serialization-kotlinx-json") + implementation("io.ktor:ktor-server-content-negotiation") + implementation("io.ktor:ktor-server-auto-head-response") + implementation("io.ktor:ktor-server-default-headers") + implementation("io.ktor:ktor-server-double-receive") + implementation("io.ktor:ktor-server-call-logging") + implementation("io.ktor:ktor-server-status-pages") + implementation("io.ktor:ktor-serialization") + implementation("io.ktor:ktor-server-netty") + implementation("io.ktor:ktor-server-cors") + + // JWT (for authentication) + implementation("com.auth0:java-jwt:3.19.1") + + // Nino projects + implementation(project(":core")) + implementation(project(":modules")) + implementation(project(":database")) + implementation(project(":modules:metrics")) + + // Prometheus stuff + implementation("io.prometheus:simpleclient_common:0.15.0") +} diff --git a/api/src/main/kotlin/sh/nino/api/ApiServer.kt b/api/src/main/kotlin/sh/nino/api/ApiServer.kt new file mode 100644 index 00000000..dca5a554 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/ApiServer.kt @@ -0,0 +1,213 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api + +import gay.floof.utils.slf4j.logging +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.autohead.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.cors.* +import io.ktor.server.plugins.defaultheaders.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.sentry.Sentry +import kotlinx.serialization.json.* +import org.koin.core.context.GlobalContext +import org.slf4j.LoggerFactory +import sh.nino.api.endpoints.AbstractEndpoint +import sh.nino.api.exceptions.add401Exception +import sh.nino.api.plugins.KtorLoggingPlugin +import sh.nino.api.plugins.UserAgentPlugin +import sh.nino.commons.NinoInfo +import sh.nino.commons.data.Config +import sh.nino.commons.data.Environment +import sh.nino.commons.extensions.inject +import sh.nino.commons.extensions.retrieveAll + +/** + * Represents the API server that is in the background if `config.api` is not null. + */ +class ApiServer(private val config: Config) { + private lateinit var server: NettyApplicationEngine + private val registeredEndpoints = mutableListOf>() + private val log by logging() + + suspend fun launch() { + log.info("Starting API service...") + + val apiConfig = config.api!! + val self = this // so i don't have to `this@ApiServer.config`... + + val environment = applicationEngineEnvironment { + this.developmentMode = self.config.environment == Environment.Development + this.log = LoggerFactory.getLogger("sh.nino.api.ktor.KtorService") + + connector { + host = apiConfig.host + port = apiConfig.port + } + + module { + val json: Json by inject() + + install(UserAgentPlugin) + install(KtorLoggingPlugin) + install(AutoHeadResponse) + install(ContentNegotiation) { + this.json(json) + } + + install(CORS) { + anyHost() + headers += "X-Forwarded-Proto" + } + + // Install some default headers uwu + install(DefaultHeaders) { + header("X-Powered-By", "Nino/API (+https://github.com/NinoDiscord/tree/master/api; v${NinoInfo.VERSION})") + header("Cache-Control", "public, max-age=7776000") + + if (apiConfig.securityHeaders) { + header("X-Frame-Options", "deny") + header("X-Content-Type-Options", "nosniff") + header("X-XSS-Protection", "1; mode=block") + } + + for ((key, value) in apiConfig.extraHeaders) { + header(key, value) + } + } + + install(StatusPages) { + // If the route was not found + status(HttpStatusCode.NotFound) { call, _ -> + call.respond( + HttpStatusCode.NotFound, + buildJsonObject { + put("success", false) + put( + "errors", + buildJsonArray { + add( + buildJsonObject { + put("message", "Route ${call.request.httpMethod.value} ${call.request.uri} was not found.") + put("code", "UNKNOWN_ROUTE") + } + ) + } + ) + } + ) + } + + add401Exception() + + exception { call, cause -> + if (Sentry.isEnabled()) { + Sentry.captureException(cause) + } + + self.log.error("Unable to handle request ${call.request.httpMethod.value} ${call.request.uri}:", cause) + call.respond( + HttpStatusCode.InternalServerError, + buildJsonObject { + put("success", false) + put( + "errors", + buildJsonArray { + add( + buildJsonObject { + put("message", "Unknown exception has occured") + put("code", "INTERNAL_SERVER_ERROR") + + if (self.config.environment == Environment.Development) { + put("detail", cause.message) + } + } + ) + } + ) + } + ) + } + } + + routing { + val endpoints = GlobalContext.retrieveAll() + self.log.info("Found ${endpoints.size} to register!") + + for (endpoint in endpoints) { + for (method in endpoint.methods) { + if (self.registeredEndpoints.contains(Pair(method, endpoint.path))) + continue + + self.registeredEndpoints.add(Pair(method, endpoint.path)) + self.log.info("Registering endpoint ${method.value} ${endpoint.path}!") + + route(endpoint.path, method) { + for ((plugin, configure) in endpoint.pluginsToRegister) { + install(plugin) { + configure.invoke(this) + } + } + + handle { + endpoint.call(call) + } + } + } + } + } + } + } + + server = embeddedServer(Netty, environment, configure = { + requestQueueLimit = apiConfig.requestQueueLimit + runningLimit = apiConfig.runningLimit + shareWorkGroup = apiConfig.shareWorkGroup + responseWriteTimeoutSeconds = apiConfig.responseWriteTimeoutSeconds + requestReadTimeoutSeconds = apiConfig.requestReadTimeout + tcpKeepAlive = apiConfig.tcpKeepAlive + }) + + if (!apiConfig.securityHeaders) + log.warn("It is not recommended disabling security headers :3") + + server.start(wait = true) + } + + fun destroy() { + if (!::server.isInitialized) return + + log.warn("Destroying API server...") + server.stop(1000, 5000) + } +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/AbstractEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/AbstractEndpoint.kt new file mode 100644 index 00000000..bbc568e8 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/AbstractEndpoint.kt @@ -0,0 +1,65 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.routing.* + +/** + * Represents an abstraction for creating API endpoints. This is collected in the Koin module. + * @param path The path to use for this endpoint + * @param methods A list of methods to call this path on. + */ +abstract class AbstractEndpoint(val path: String, val methods: List = listOf(HttpMethod.Get)) { + // TODO: type `Plugin` so the receiving `configure` can be configured successfully. + val pluginsToRegister = mutableMapOf, Any.() -> Unit>() + + /** + * Represents an abstraction for creating API endpoints. This is collected in the Koin module. + * @param path The path to use for this endpoint + * @param method A single [HttpMethod] this endpoint supports. + */ + constructor(path: String, method: HttpMethod): this(path, listOf(method)) + + /** + * Registers a [Plugin] to this route only, this can be useful for sessions and such. :3 + * @param plugin The plugin to register + * @param configure The configuration of the plugin which will be called when `install` is placed + * on the route. + * + * @return This endpoint to chain methods :) + */ + fun addPlugin(plugin: Plugin, configure: B.() -> Unit): AbstractEndpoint { + @Suppress("UNCHECKED_CAST") + pluginsToRegister[plugin] = configure as Any.() -> Unit + return this + } + + /** + * Runs the endpoint once the router has reached it. + * @param call The [ApplicationCall] from the handler. + */ + abstract suspend fun call(call: ApplicationCall) +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/HeartbeatEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/HeartbeatEndpoint.kt new file mode 100644 index 00000000..ac80ea41 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/HeartbeatEndpoint.kt @@ -0,0 +1,34 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* + +class HeartbeatEndpoint: AbstractEndpoint("/heartbeat") { + override suspend fun call(call: ApplicationCall) { + call.respond(HttpStatusCode.OK, "pong!") + } +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/InfoEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/InfoEndpoint.kt new file mode 100644 index 00000000..e4de8b4b --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/InfoEndpoint.kt @@ -0,0 +1,125 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints + +import dev.kord.cache.api.query +import dev.kord.core.Kord +import dev.kord.core.cache.data.UserData +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import kotlinx.coroutines.flow.count +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import sh.nino.commons.Constants +import sh.nino.commons.NinoInfo +import sh.nino.commons.extensions.asSnowflake +import sh.nino.commons.extensions.formatSize +import sh.nino.commons.extensions.reduceWith +import java.lang.management.ManagementFactory +import kotlin.time.Duration +import kotlin.time.DurationUnit + +@kotlinx.serialization.Serializable +data class ShardInfo( + var guilds: Int, + var users: Int, + val ping: Long +) + +class InfoEndpoint(private val kord: Kord): AbstractEndpoint("/info") { + override suspend fun call(call: ApplicationCall) { + // Collect shard information + val shardMap = mutableMapOf() + val guildShardMap = kord.guilds.map { + ((it.id.value.toLong() shr 22) % kord.gateway.gateways.size) to it.id.value.toLong() + }.toList() + + for ((id, guildID) in guildShardMap) { + if (!shardMap.containsKey(id)) { + val gatewayPing = kord.gateway.gateways[id.toInt()]?.ping?.value ?: Duration.ZERO + + shardMap[id] = ShardInfo( + 0, + 0, + gatewayPing.toLong(DurationUnit.MILLISECONDS) + ) + } + + val shardInfo = shardMap[id]!! + shardInfo.guilds += 1 + shardInfo.users += kord.getGuild(guildID.asSnowflake())!!.memberCount ?: 0 + + shardMap[id] = shardInfo + } + + // Get all users cached + val users = kord.cache.query {}.count() + val channels = kord.guilds.reduceWith(0) { acc, guild -> + val chan = guild.channels.count() + acc + chan + } + + val ping = kord.gateway.averagePing ?: Duration.ZERO + val memory = ManagementFactory.getMemoryMXBean() + + call.respond( + HttpStatusCode.OK, + buildJsonObject { + put("success", true) + put( + "data", + buildJsonObject { + put("guilds", kord.guilds.count()) + put("users", users) + put("channels", channels) + put("average_ping", ping.toLong(DurationUnit.MILLISECONDS)) + put("version", NinoInfo.VERSION) + put("commit_sha", NinoInfo.COMMIT_HASH) + put("build_date", NinoInfo.BUILD_DATE) + put("dedi_node", Constants.dediNode) + put("memory_usage", memory.heapMemoryUsage.used.formatSize()) + put( + "shards", + buildJsonArray { + for (shard in shardMap.values) { + add( + buildJsonObject { + put("guilds", shard.guilds) + put("users", shard.users) + put("ping", shard.ping) + } + ) + } + } + ) + } + ) + } + ) + } +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/MainEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/MainEndpoint.kt new file mode 100644 index 00000000..fb4bde2b --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/MainEndpoint.kt @@ -0,0 +1,42 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +class MainEndpoint: AbstractEndpoint("/", HttpMethod.Get) { + override suspend fun call(call: ApplicationCall) { + call.respond( + HttpStatusCode.OK, + buildJsonObject { + put("success", true) + put("message", "Hello, world!") + } + ) + } +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/MetricsEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/MetricsEndpoint.kt new file mode 100644 index 00000000..c2622104 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/MetricsEndpoint.kt @@ -0,0 +1,65 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.prometheus.client.exporter.common.TextFormat +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import sh.nino.modules.Registry +import sh.nino.modules.metrics.MetricsModule +import java.io.StringWriter + +class MetricsEndpoint: AbstractEndpoint("/metrics") { + private val metrics by Registry.inject() + + override suspend fun call(call: ApplicationCall) { + if (metrics == null) { + call.respond( + HttpStatusCode.NotFound, + buildJsonObject { + put("success", false) + put("message", "Prometheus Metrics is not enabled.") + } + ) + + return + } + + val accept = call.request.header("Accept") ?: TextFormat.CONTENT_TYPE_004 + val contentType = TextFormat.chooseContentType(accept) + + val stream = StringWriter() + stream.use { + TextFormat.writeFormat(contentType, it, metrics!!.registry.metricFamilySamples()) + } + + call.respondOutputStream(ContentType.parse(contentType), HttpStatusCode.OK) { + TextFormat.writeFormat(contentType, writer(), metrics!!.registry.metricFamilySamples()) + } + } +} diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/ApiV1Endpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/ApiV1Endpoint.kt new file mode 100644 index 00000000..3d1c0b45 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/ApiV1Endpoint.kt @@ -0,0 +1,49 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import sh.nino.api.endpoints.AbstractEndpoint + +class ApiV1Endpoint: AbstractEndpoint("/v1", HttpMethod.Get) { + override suspend fun call(call: ApplicationCall) { + call.respond( + HttpStatusCode.OK, + buildJsonObject { + put("success", true) + put( + "data", + buildJsonObject { + put("docs_uri", "https://nino.sh/docs/api/v1") + put("tagline", "You know, for moderation at scale™️ totally rad bro") + } + ) + } + ) + } +} diff --git a/prettierrc.js b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/AutomodApiEndpoint.kt similarity index 83% rename from prettierrc.js rename to api/src/main/kotlin/sh/nino/api/endpoints/api/v1/AutomodApiEndpoint.kt index 23c50b86..e49bb2af 100644 --- a/prettierrc.js +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/AutomodApiEndpoint.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2021 Noel +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,13 +21,4 @@ * SOFTWARE. */ -module.exports = { - semi: true, - tabWidth: 2, - singleQuote: true, - endOfLine: 'lf', - printWidth: 120, - trailingComma: 'es5', - bracketSpacing: true, - jsxBracketSameLine: false, -}; +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GlobalBansApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GlobalBansApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GlobalBansApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildCasesApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildCasesApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildCasesApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildLoggingApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildLoggingApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildLoggingApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildPunishmentsApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildPunishmentsApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildPunishmentsApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildTagsApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildTagsApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildTagsApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildsApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildsApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/GuildsApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/SessionApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/SessionApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/SessionApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/UsersApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/UsersApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/UsersApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/WarningsApiEndpoint.kt b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/WarningsApiEndpoint.kt new file mode 100644 index 00000000..e49bb2af --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/endpoints/api/v1/WarningsApiEndpoint.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.endpoints.api.v1 diff --git a/src/commands/core/UptimeCommand.ts b/api/src/main/kotlin/sh/nino/api/endpoints/koinModule.kt similarity index 69% rename from src/commands/core/UptimeCommand.ts rename to api/src/main/kotlin/sh/nino/api/endpoints/koinModule.kt index f860c190..e06b094a 100644 --- a/src/commands/core/UptimeCommand.ts +++ b/api/src/main/kotlin/sh/nino/api/endpoints/koinModule.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +21,14 @@ * SOFTWARE. */ -import { Command, CommandMessage } from '../../structures'; -import { humanize } from '@augu/utils'; +package sh.nino.api.endpoints -export default class UptimeCommand extends Command { - constructor() { - super({ - description: 'descriptions.uptime', - cooldown: 3, - aliases: ['up', 'upfor', 'online'], - name: 'uptime', - }); - } +import org.koin.dsl.bind +import org.koin.dsl.module - run(msg: CommandMessage) { - return msg.reply(humanize(Math.floor(process.uptime() * 1000), true)); - } +val apiEndpointsModule = module { + single { HeartbeatEndpoint() } bind AbstractEndpoint::class + single { InfoEndpoint(get()) } bind AbstractEndpoint::class + single { MetricsEndpoint() } bind AbstractEndpoint::class + single { MainEndpoint() } bind AbstractEndpoint::class } diff --git a/api/src/main/kotlin/sh/nino/api/exceptions/UnauthorisedException.kt b/api/src/main/kotlin/sh/nino/api/exceptions/UnauthorisedException.kt new file mode 100644 index 00000000..224eb182 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/exceptions/UnauthorisedException.kt @@ -0,0 +1,66 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.exceptions + +import io.ktor.http.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.response.* +import io.sentry.Sentry +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put + +fun StatusPagesConfig.add401Exception() { + exception { call, cause -> + if (Sentry.isEnabled()) { + Sentry.captureException(cause) + } + + call.respond(cause.asStatusCode(), cause.toJsonObject()) + } +} + +/** + * Exception when the user is unauthorized to do a specific action. + */ +class UnauthorisedException(override val message: String): Exception() { + fun toJsonObject(): JsonObject = buildJsonObject { + put("success", false) + put( + "errors", + buildJsonArray { + add( + buildJsonObject { + put("code", "UNAUTHORIZED") + put("message", message) + put("detail", "You are not authorized to do this specific action.") + } + ) + } + ) + } + + fun asStatusCode(): HttpStatusCode = HttpStatusCode.Unauthorized +} diff --git a/api/src/main/kotlin/sh/nino/api/plugins/KtorLoggingPlugin.kt b/api/src/main/kotlin/sh/nino/api/plugins/KtorLoggingPlugin.kt new file mode 100644 index 00000000..d5f8fe3f --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/plugins/KtorLoggingPlugin.kt @@ -0,0 +1,62 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.application.hooks.* +import io.ktor.server.request.* +import io.ktor.util.* +import org.apache.commons.lang3.time.StopWatch +import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit + +val KtorLoggingPlugin = createApplicationPlugin("KtorLoggingPlugin") { + val stopwatchKey = AttributeKey("stopwatchKey") + val log = LoggerFactory.getLogger("sh.nino.api.plugins.KtorLoggingPluginKt") + + environment?.monitor?.subscribe(ApplicationStarted) { + log.info("HTTP service has started successfully! :3") + } + + environment?.monitor?.subscribe(ApplicationStopped) { + log.warn("HTTP service has completely stopped. :(") + } + + onCall { call -> + call.attributes.put(stopwatchKey, StopWatch.createStarted()) + } + + on(ResponseSent) { call -> + val method = call.request.httpMethod + val version = call.request.httpVersion + val endpoint = call.request.uri + val status = call.response.status() ?: HttpStatusCode(-1, "Unknown HTTP Method") + val stopwatch = call.attributes[stopwatchKey] + val userAgent = call.request.userAgent() + + stopwatch.stop() + log.info("${method.value} $version $endpoint :: ${status.value} ${status.description} [UA=$userAgent] [${stopwatch.getTime(TimeUnit.MILLISECONDS)}ms]") + } +} diff --git a/api/src/main/kotlin/sh/nino/api/plugins/UserAgentPlugin.kt b/api/src/main/kotlin/sh/nino/api/plugins/UserAgentPlugin.kt new file mode 100644 index 00000000..8e7e6e54 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/plugins/UserAgentPlugin.kt @@ -0,0 +1,42 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.plugins + +import io.ktor.server.application.* +import io.ktor.server.request.* +import org.slf4j.MDC +import sh.nino.commons.extensions.ifNotNull + +val UserAgentPlugin = createApplicationPlugin("NinoUserAgentPlugin") { + onCall { c -> + val userAgent = c.request.userAgent() + userAgent.ifNotNull { + MDC.put("user_agent", it) + } + } + + onCallReceive { _, _ -> + MDC.remove("user_agent") + } +} diff --git a/api/src/main/kotlin/sh/nino/api/ratelimiting/RatelimitManager.kt b/api/src/main/kotlin/sh/nino/api/ratelimiting/RatelimitManager.kt new file mode 100644 index 00000000..7452e303 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/ratelimiting/RatelimitManager.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.ratelimiting diff --git a/api/src/main/kotlin/sh/nino/api/sessions/SessionsManager.kt b/api/src/main/kotlin/sh/nino/api/sessions/SessionsManager.kt new file mode 100644 index 00000000..4f1f00d6 --- /dev/null +++ b/api/src/main/kotlin/sh/nino/api/sessions/SessionsManager.kt @@ -0,0 +1,149 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.api.sessions + +import gay.floof.utils.slf4j.logging +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.util.* +import kotlinx.coroutines.future.await +import kotlinx.serialization.SerialName +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.slf4j.MDC +import sh.nino.core.redis.Manager +import java.util.UUID + +/** + * Represents a session object, which is stored in Redis under the hash table: `nino:sessions`. + * + * This only appends the user's ID, the session ID, and the access/refresh token from Discord. + */ +@kotlinx.serialization.Serializable +data class Session( + @SerialName("refresh_token") + val refreshToken: String, + + @SerialName("access_token") + val accessToken: String, + + @SerialName("user_id") + val userId: String, + val id: String +) + +class SessionsManager(private val redis: Manager, private val json: Json) { + companion object { + val SessionKey = AttributeKey("NinoSession") + } + + private val log by logging() + + val Plugin = createApplicationPlugin("NinoSessionsPlugin") { + onCall { call -> + // Add the user agent to the MDC, so Logstash can append it to + // the payload for monitoring reasons. :) + MDC.put("user_agent", call.request.userAgent()) + + // Check if we have the `session_id` cookie + val cookie = call.request.cookies["session_id"] + ?: run { + call.respond( + HttpStatusCode.BadRequest, + buildJsonObject { + put("success", false) + put( + "errors", + buildJsonArray { + add( + buildJsonObject { + put("code", "MISSING_COOKIE") + put("message", "Missing `session_id` cookie. :(") + } + ) + } + ) + } + ) + + return@onCall + } + + val data = redis.commands.get("sessions:$cookie").await() ?: run { + call.respond( + HttpStatusCode.Unauthorized, + buildJsonObject { + put("success", false) + put( + "errors", + buildJsonArray { + add( + buildJsonObject { + put("code", "UNKNOWN_SESSION") + put("message", "Session doesn't exist or was expired, please re-login.") + } + ) + } + ) + } + ) + + return@onCall + } + + val session = json.decodeFromString(data) + + // add it to the call and continue + call.attributes.put(SessionKey, session) + MDC.put("user_id", session.userId) + } + } + + suspend fun create( + accessToken: String, + refreshToken: String, + userId: String + ): Session { + val id = UUID.randomUUID().toString() + val session = Session( + refreshToken, + accessToken, + userId, + id + ) + + // Decode it to a string, so it can be put in Redis + val sessionStr = json.encodeToString(Session.serializer(), session) + + redis.commands.set("nino:sessions:$id", sessionStr).await() + redis.commands.setex("nino:sessions:$id", 604800, sessionStr).await() + + return session + } +} diff --git a/assets/HEADING b/assets/HEADING new file mode 100644 index 00000000..c42c2526 --- /dev/null +++ b/assets/HEADING @@ -0,0 +1,23 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + diff --git a/assets/banner.txt b/assets/banner.txt new file mode 100644 index 00000000..14525490 --- /dev/null +++ b/assets/banner.txt @@ -0,0 +1,19 @@ + ##### # ## + ###### /# #### / # + /# / / ## ###/ ### +/ / / ## # # # + / / ## # + ## ## ## # ### ### /### /### + ## ## ## # ### ###/ #### / / ### / + ## ## ## # ## ## ###/ / ###/ + ## ## ## # ## ## ## ## ## + ## ## ## # ## ## ## ## ## + # ## ### ## ## ## ## ## + / ### ## ## ## ## ## + /##/ ## ## ## ## ## ## + / ##### ### / ### ### ###### +/ ## ##/ ### ### #### +# + ## + +~ Version: v{{.Version}} ~ Commit SHA: {{.CommitSha}} @ {{.BuildDate}} ~ diff --git a/assets/shortlinks.json b/assets/shortlinks.json index effe0abe..055027e2 100644 --- a/assets/shortlinks.json +++ b/assets/shortlinks.json @@ -672,5 +672,27 @@ "ur3.us", "kek.gg", "steamcommunity.ru", - "steanconmunity.ru" + "steanconmunity.ru", + "discord-nitro.link", + "discorcl.click", + "discod.fun", + "steancomunnity.ru", + "dicsordnitro.info", + "dirscod.com", + "dicsord.net", + "discorcl.link", + "discorb.co", + "discord-nitro.net", + "discord-partner.com", + "diskord.ru.com", + "discordapps.gift", + "steamcomnumily.com", + "steamcommmunilty.com", + "discord-nitro.su", + "steamcomnumnity.com", + "discod.info", + "discordgift.ru.com", + "steamnitro.com", + "nitroos-frieie.ru", + "discorb.blog" ] diff --git a/automod/build.gradle.kts b/automod/build.gradle.kts new file mode 100644 index 00000000..3be4abee --- /dev/null +++ b/automod/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules:punishments")) + implementation(project(":modules")) + implementation(project(":database")) +} diff --git a/automod/src/main/kotlin/sh/nino/automod/AutomodBuilder.kt b/automod/src/main/kotlin/sh/nino/automod/AutomodBuilder.kt new file mode 100644 index 00000000..a82f4196 --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/AutomodBuilder.kt @@ -0,0 +1,72 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod + +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.core.event.guild.MemberUpdateEvent +import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.event.user.UserUpdateEvent + +/** + * Represents a builder class for constructing automod objects. + */ +class AutomodBuilder { + private var onMemberNickUpdateCall: AutomodCallable? = null + private var onMemberJoinCall: AutomodCallable? = null + private var onUserUpdateCall: AutomodCallable? = null + private var onMessageCall: AutomodCallable? = null + + /** + * Returns the name of the automod. + */ + var name: String = "" + + /** + * Hooks this [Automod] object to react on message create events + * @param callable The callable function to execute + */ + fun onMessage(callable: AutomodCallable) { + onMessageCall = callable + } + + fun onUserUpdate(callable: AutomodCallable) { + onUserUpdateCall = callable + } + + fun onMemberJoin(callable: AutomodCallable) { + onMemberJoinCall = callable + } + + fun onMemberNickUpdate(callable: AutomodCallable) { + onMemberNickUpdateCall = callable + } + + fun build(): AutomodObject = AutomodObject( + this.name, + onMessageCall, + onUserUpdateCall, + onMemberJoinCall, + onMemberNickUpdateCall + ) +} diff --git a/automod/src/main/kotlin/sh/nino/automod/AutomodObject.kt b/automod/src/main/kotlin/sh/nino/automod/AutomodObject.kt new file mode 100644 index 00000000..c36a82f5 --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/AutomodObject.kt @@ -0,0 +1,100 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod + +import dev.kord.common.entity.Permission +import dev.kord.core.Kord +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.Event +import dev.kord.core.event.guild.MemberJoinEvent +import dev.kord.core.event.guild.MemberUpdateEvent +import dev.kord.core.event.message.MessageCreateEvent +import dev.kord.core.event.user.UserUpdateEvent +import org.koin.core.context.GlobalContext +import sh.nino.commons.extensions.retrieve +import sh.nino.commons.isMemberAbove +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Represents a callable function that can be reflected upon an [AutomodObject]. + */ +typealias AutomodCallable = suspend (C) -> Boolean + +@OptIn(ExperimentalContracts::class) +fun automod(builder: AutomodBuilder.() -> Unit): AutomodObject { + contract { callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + + return AutomodBuilder().apply(builder).build() +} + +class AutomodObject( + val name: String, + private val onMessageCallback: AutomodCallable? = null, + private val onUserUpdateCallback: AutomodCallable? = null, + private val onGuildMemberJoinCallback: AutomodCallable? = null, + private val onGuildMemberNickCallback: AutomodCallable? = null +) { + init { + check(name != "") { "Automod name cannot be empty. :(" } + } + + suspend fun execute(event: Event): Boolean = when { + onMessageCallback != null -> { + val ev = (event as? MessageCreateEvent) ?: error("Unable to cast ${event::class} -> MessageCreateEvent") + val guild = event.getGuild()!! + val kord = GlobalContext.retrieve() + val channel = event.message.getChannel() as? TextChannel + + if ( + (event.member != null && !isMemberAbove(guild.getMember(kord.selfId), event.member!!)) || + (channel != null && channel.getEffectivePermissions(kord.selfId).contains(Permission.ManageMessages)) || + (event.message.author == null || event.message.author!!.isBot) || + (channel != null && channel.getEffectivePermissions(event.message.author!!.id).contains(Permission.ModerateMembers)) + ) { + false + } else { + onMessageCallback.invoke(ev) + } + } + + onUserUpdateCallback != null -> { + val ev = (event as? UserUpdateEvent) ?: error("Unable to cast ${event::class} -> UserUpdateEvent") + onUserUpdateCallback.invoke(ev) + } + + onGuildMemberJoinCallback != null -> { + val ev = (event as? MemberJoinEvent) ?: error("Unable to cast ${event::class} -> MemberJoinEvent") + onGuildMemberJoinCallback.invoke(ev) + } + + onGuildMemberNickCallback != null -> { + val ev = (event as? MemberUpdateEvent) ?: error("Unable to cast ${event::class} -> MemberUpdateEvent") + onGuildMemberNickCallback.invoke(ev) + } + + else -> false + } +} diff --git a/src/listeners/UserListener.ts b/automod/src/main/kotlin/sh/nino/automod/Container.kt similarity index 54% rename from src/listeners/UserListener.ts rename to automod/src/main/kotlin/sh/nino/automod/Container.kt index 7db169a8..8bbd3299 100644 --- a/src/listeners/UserListener.ts +++ b/automod/src/main/kotlin/sh/nino/automod/Container.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +21,36 @@ * SOFTWARE. */ -import { Inject, Subscribe } from '@augu/lilith'; -import AutomodService from '../services/AutomodService'; -import type { User } from 'eris'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class UserListener { - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; +package sh.nino.automod - @Inject - private readonly automod!: AutomodService; +import dev.kord.core.event.Event +import sh.nino.automod.automods.AccountAgeAutomod +import sh.nino.automod.automods.BlacklistAutomod +import sh.nino.automod.automods.MentionsAutomod +import sh.nino.automod.automods.MessageLinksAutomod - @Subscribe('userUpdate', { emitter: 'discord' }) - async onUserUpdate(user: User) { - const mutualGuilds = this.discord.client.guilds.filter((guild) => guild.members.has(user.id)); +/** + * Represents the global container for the auto moderation objects. + */ +class Container { + private val automods = listOf( + AccountAgeAutomod, + BlacklistAutomod, + MentionsAutomod, + MessageLinksAutomod + ) - for (const guild of mutualGuilds) { - const automod = await this.database.automod.get(guild.id); - if (!automod) continue; + suspend fun run(event: Event): Boolean { + var ret = true + for (automod in automods) { + try { + ret = automod.execute(event) + if (ret) break + } catch (e: Exception) { + continue + } + } - if (automod.dehoist === true) await this.automod.run('memberNick', guild.members.get(user.id)!); + return ret } - } } diff --git a/automod/src/main/kotlin/sh/nino/automod/automods/AccountAgeAutomod.kt b/automod/src/main/kotlin/sh/nino/automod/automods/AccountAgeAutomod.kt new file mode 100644 index 00000000..aefd091e --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/automods/AccountAgeAutomod.kt @@ -0,0 +1,68 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod.automods + +import dev.kord.core.Kord +import kotlinx.datetime.toJavaInstant +import org.koin.core.context.GlobalContext +import sh.nino.automod.automod +import sh.nino.commons.extensions.retrieve +import sh.nino.database.PunishmentType +import sh.nino.database.asyncTransaction +import sh.nino.database.entities.AutomodEntity +import sh.nino.modules.Registry +import sh.nino.modules.punishments.PunishmentModule +import sh.nino.modules.punishments.toMemberLikeObject +import java.time.OffsetDateTime +import java.time.temporal.ChronoUnit + +val AccountAgeAutomod = automod { + name = "accountAge" + onMemberJoin { event -> + // TODO: Implement Registry#currentOrNull? + val punishments = Registry.CURRENT!!.getOrNull()!!.current + val kord = GlobalContext.retrieve() + + val settings = asyncTransaction { + AutomodEntity.findById(event.guildId.value.toLong())!! + } + + // If it is disabled, do not continue + if (!settings.accountAge) return@onMemberJoin false + + val total = ChronoUnit.DAYS.between(event.member.joinedAt.toJavaInstant(), OffsetDateTime.now().toLocalDate()) + if (total <= settings.accountAgeThreshold) { + val guild = event.getGuild() + val selfMember = guild.getMember(kord.selfId) + + punishments.apply(event.member.toMemberLikeObject(event.member.id, guild), selfMember, PunishmentType.KICK) { + reason = "[Automod :: Account Age] Join date threshold was under ${settings.accountAgeThreshold} days." + } + + return@onMemberJoin true + } + + false + } +} diff --git a/automod/src/main/kotlin/sh/nino/automod/automods/BlacklistAutomod.kt b/automod/src/main/kotlin/sh/nino/automod/automods/BlacklistAutomod.kt new file mode 100644 index 00000000..36b9bd74 --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/automods/BlacklistAutomod.kt @@ -0,0 +1,63 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod.automods + +import dev.kord.core.Kord +import org.koin.core.context.GlobalContext +import sh.nino.automod.automod +import sh.nino.commons.extensions.retrieve +import sh.nino.database.asyncTransaction +import sh.nino.database.entities.AutomodEntity +import sh.nino.modules.Registry +import sh.nino.modules.punishments.PunishmentModule + +val BlacklistAutomod = automod { + name = "blacklist" + onMessage { event -> + // TODO: Implement Registry#currentOrNull? + val punishments = Registry.CURRENT!!.getOrNull()!!.current + val kord = GlobalContext.retrieve() + val guild = event.message.getGuild() + val settings = asyncTransaction { + AutomodEntity.findById(guild.id.value.toLong())!! + } + + if (!settings.blacklist) + return@onMessage false + + // TODO: is regex better for this? + val content = event.message.content.split(" ") + for (word in settings.blacklistedWords) { + if (content.any { it.lowercase() == word.lowercase() }) { + event.message.delete() + event.message.channel.createMessage("Hey, **${event.message.author!!.tag}**! You're not allowed to say that... :<") + + punishments.addWarning(event.member!!, guild.getMember(kord.selfId), 1, "[Automod :: Blacklisted Words] User said blacklisted word in <#${event.message.channel.id}>.") + return@onMessage true + } + } + + false + } +} diff --git a/automod/src/main/kotlin/sh/nino/automod/automods/MentionsAutomod.kt b/automod/src/main/kotlin/sh/nino/automod/automods/MentionsAutomod.kt new file mode 100644 index 00000000..d8df3edf --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/automods/MentionsAutomod.kt @@ -0,0 +1,57 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod.automods + +import dev.kord.core.Kord +import kotlinx.coroutines.flow.count +import org.koin.core.context.GlobalContext +import sh.nino.automod.automod +import sh.nino.commons.extensions.retrieve +import sh.nino.database.asyncTransaction +import sh.nino.database.entities.AutomodEntity +import sh.nino.modules.Registry +import sh.nino.modules.punishments.PunishmentModule + +val MentionsAutomod = automod { + name = "mentions" + onMessage { event -> + // TODO: Implement Registry#currentOrNull? + val punishments = Registry.CURRENT!!.getOrNull()!!.current + val kord = GlobalContext.retrieve() + val guild = event.message.getGuild() + val settings = asyncTransaction { + AutomodEntity.findById(guild.id.value.toLong())!! + } + + val mentionedUsers = event.message.mentionedUsers.count() + if (mentionedUsers >= settings.mentionThreshold) { + event.message.channel.createMessage("Hey, **${event.message.author!!.tag}**! You're not allowed to mention that many people!") + + punishments.addWarning(event.member!!, guild.getMember(kord.selfId), 1, "[Automod :: Mention Threshold] Too many mentions in <#${event.message.channel.id}>.") + return@onMessage true + } + + false + } +} diff --git a/automod/src/main/kotlin/sh/nino/automod/automods/MessageLinksAutomod.kt b/automod/src/main/kotlin/sh/nino/automod/automods/MessageLinksAutomod.kt new file mode 100644 index 00000000..9a9d1da7 --- /dev/null +++ b/automod/src/main/kotlin/sh/nino/automod/automods/MessageLinksAutomod.kt @@ -0,0 +1,175 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.automod.automods + +import dev.kord.common.Color +import dev.kord.common.entity.ChannelType +import dev.kord.common.entity.optional.value +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Message +import dev.kord.core.entity.channel.NewsChannel +import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.create.allowedMentions +import kotlinx.datetime.Instant +import sh.nino.automod.automod +import sh.nino.commons.Constants +import sh.nino.commons.extensions.asSnowflake + +private val DISCORD_MESSAGE_LINK_REGEX = "(?:https?:\\/\\/)?(?:canary\\.|ptb\\.)?discord\\.com\\/channels\\/(\\d{15,21}|@me)\\/(\\d{15,21})\\/(\\d{15,21})\n".toRegex() + +val MessageLinksAutomod = automod { + name = "message_links" + onMessage { event -> + // If the message doesn't include the thing, let's just not do anything + if (!event.message.content.matches(DISCORD_MESSAGE_LINK_REGEX)) return@onMessage false + + val matcher = DISCORD_MESSAGE_LINK_REGEX.toPattern().matcher(event.message.content) + if (!matcher.matches()) return@onMessage false + + val channelId = matcher.group(4).asSnowflake() + val messageId = matcher.group(5).asSnowflake() + val channel = event.kord.getChannel(channelId) ?: return@onMessage false + + val message: Message = when (channel.type) { + is ChannelType.GuildText -> { + try { + (channel as TextChannel).getMessage(messageId) + } catch (e: Exception) { + null + } + } + + is ChannelType.GuildNews -> { + try { + (channel as NewsChannel).getMessage(messageId) + } catch (e: Exception) { + null + } + } + + else -> null + } ?: return@onMessage false + + if (message.embeds.isNotEmpty()) { + val first = message.embeds.first() + val member = message.getAuthorAsMember() + + event.message.channel.createMessage { + if (message.content.isNotEmpty()) { + content = message.content + } + + allowedMentions { + repliedUser = false + } + + embeds += EmbedBuilder().apply { + if (first.data.title.value != null) { + title = first.data.title.value + } + + if (first.data.description.value != null) { + description = first.data.description.value + } + + if (first.data.url.value != null) { + url = first.data.url.value + } + + color = if (first.data.color.asNullable != null) { + Color(first.data.color.asOptional.value!!) + } else { + Constants.COLOR + } + + if (first.data.timestamp.value != null) { + timestamp = Instant.parse(first.data.timestamp.value!!) + } + + if (first.data.footer.value != null) { + footer { + text = first.data.footer.value!!.text + icon = first.data.footer.value!!.iconUrl.value ?: first.data.footer.value!!.proxyIconUrl.value ?: "" + } + } + + if (first.data.thumbnail.value != null) { + thumbnail { + url = first.data.thumbnail.value!!.url.value ?: first.data.thumbnail.value!!.proxyUrl.value ?: "" + } + } + + if (first.data.author.value != null) { + author { + name = first.data.author.value!!.name.value ?: "" + icon = first.data.author.value!!.iconUrl.value ?: first.data.author.value!!.proxyIconUrl.value ?: "" + url = first.data.author.value!!.url.value ?: "" + } + } else { + author { + name = if (message.author == null) { + "Webhook" + } else { + "${message.author!!.tag} (${message.author!!.id})" + } + + icon = member?.avatar?.url ?: message.author!!.avatar?.url ?: message.author!!.defaultAvatar.url + } + } + + if (first.data.fields.value != null) { + for (f in first.data.fields.value!!) { + field { + name = f.name + value = f.value + inline = f.inline.value ?: true + } + } + } + } + } + + true + } else { + val member = message.getAuthorAsMember() + event.message.channel.createMessage { + embeds += EmbedBuilder().apply { + description = message.content + color = Constants.COLOR + + author { + name = if (message.author == null) "Webhook" else "${message.author!!.tag} (${message.author!!.id})" + + if (message.author != null) { + icon = member?.avatar?.url ?: message.author!!.avatar?.url ?: message.author!!.defaultAvatar.url + } + } + } + } + + true + } + } +} diff --git a/bot/Dockerfile b/bot/Dockerfile new file mode 100644 index 00000000..a8fa1f67 --- /dev/null +++ b/bot/Dockerfile @@ -0,0 +1,67 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Create the builder stage, where the bot gets built. +FROM eclipse-temurin:18-alpine AS builder + +# Install git, which is required for retrieving the commit SHA +RUN apk update && apk add git ca-certificates + +# Set the working directory to /build/api +WORKDIR /build/nino + +# Copy everything. :) +COPY . . + +# Assuming the current Docker context is the root directory. +RUN chmod +x gradlew + +# Build the API. +RUN ./gradlew :bot:installDist --stacktrace --no-daemon + +# The runtime image, where the API can be executed. +FROM eclipse-temurin:18-alpine + +# Install bash, which is required to execute Docker scripts. +# This also installs tini, the valid "init" for containers. +RUN apk update && apk add --no-cache tini bash + +# Set the working directory to /app/nino/bot +WORKDIR /app/nino/bot + +# Copy the Docker scripts here +COPY docker/bot /app/nino/bot/scripts + +# Copy common libraries in the scripts directory. +COPY docker/bot /app/nino/bot/scripts + +# Copy the API here +COPY --from=builder /build/nino/build/install/Nino . + +# Make the Docker scripts executable +RUN chmod +x /app/nino/bot/scripts/docker-entrypoint.sh /app/nino/bot/scripts/run.sh + +# Do not use the root user +USER 1001 + +# Set the entrypoint and runner +ENTRYPOINT ["/app/nino/bot/scripts/docker-entrypoint.sh"] +CMD ["/app/nino/bot/scripts/run.sh"] diff --git a/bot/build.gradle.kts b/bot/build.gradle.kts new file mode 100644 index 00000000..3680267b --- /dev/null +++ b/bot/build.gradle.kts @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2019-2022 Nino + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import java.text.SimpleDateFormat +import java.util.Date + +plugins { + `nino-module` + application +} + +val commitHash by lazy { + val cmd = "git rev-parse --short HEAD".split("\\s".toRegex()) + val proc = ProcessBuilder(cmd) + .directory(File(".")) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + proc.waitFor(1, TimeUnit.MINUTES) + proc.inputStream.bufferedReader().readText().trim() +} + +distributions { + main { + distributionBaseName.set("Nino") + } +} + +dependencies { + runtimeOnly(kotlin("scripting-jsr223")) + + // Nino libraries + projects + implementation(project(":api")) + implementation(project(":core")) + implementation(project(":database")) + implementation(project(":modules")) + implementation(project(":modules:localisation")) + implementation(project(":modules:metrics")) + implementation(project(":modules:timeouts")) + implementation(project(":modules:punishments")) + implementation(project(":modules:ravy")) + + // Logging + implementation("ch.qos.logback:logback-classic:1.2.11") + implementation("ch.qos.logback:logback-core:1.2.11") + + // YAML (configuration) + implementation("com.charleskorn.kaml:kaml:0.43.0") + + // Logstash encoder for Logback + implementation("net.logstash.logback:logstash-logback-encoder:7.1.1") + implementation("io.sentry:sentry-logback:5.7.3") +} + +application { + mainClass.set("sh.nino.bot.Bootstrap") +} + +tasks { + processResources { + filesMatching("build-info.json") { + val date = Date() + val formatter = SimpleDateFormat("EEE, MMM d, YYYY - HH:mm:ss a") + + expand( + mapOf( + "version" to rootProject.version, + "commitSha" to commitHash, + "buildDate" to formatter.format(date) + ) + ) + } + } + + build { + dependsOn(processResources) + dependsOn(spotlessApply) + dependsOn(installDist) + dependsOn(kotest) + } +} diff --git a/bot/src/main/kotlin/sh/nino/bot/Bootstrap.kt b/bot/src/main/kotlin/sh/nino/bot/Bootstrap.kt new file mode 100644 index 00000000..26a4c90b --- /dev/null +++ b/bot/src/main/kotlin/sh/nino/bot/Bootstrap.kt @@ -0,0 +1,361 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.bot + +import ch.qos.logback.classic.LoggerContext +import com.charleskorn.kaml.Yaml +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import com.zaxxer.hikari.util.IsolationLevel +import dev.kord.cache.map.MapLikeCollection +import dev.kord.cache.map.internal.MapEntryCache +import dev.kord.core.Kord +import gay.floof.utils.slf4j.logging +import io.ktor.client.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.plugins.websocket.* +import io.ktor.serialization.kotlinx.json.* +import io.sentry.Sentry +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.core.context.GlobalContext +import org.koin.core.context.startKoin +import org.koin.dsl.module +import org.slf4j.LoggerFactory +import sh.nino.api.ApiServer +import sh.nino.api.endpoints.apiEndpointsModule +import sh.nino.commons.NinoInfo +import sh.nino.commons.data.Config +import sh.nino.commons.data.Environment +import sh.nino.commons.extensions.ifNotNull +import sh.nino.commons.extensions.retrieve +import sh.nino.core.NinoBot +import sh.nino.core.NinoScope +import sh.nino.core.globalModule +import sh.nino.core.interceptors.LogInterceptor +import sh.nino.core.interceptors.SentryInterceptor +import sh.nino.core.jobs.jobsModule +import sh.nino.core.launchIn +import sh.nino.core.redis.Manager +import sh.nino.core.sentry.SentryLogger +import sh.nino.database.registerOrUpdateEnums +import sh.nino.database.tables.* +import sh.nino.modules.Registry +import sh.nino.modules.localisation.LocalisationModule +import sh.nino.modules.metrics.MetricsModule +import sh.nino.modules.punishments.PunishmentModule +import sh.nino.modules.ravy.RavyModule +import sh.nino.modules.timeouts.TimeoutsModule +import java.io.File +import java.io.IOError +import java.lang.management.ManagementFactory +import kotlin.concurrent.thread +import kotlin.system.exitProcess + +object Bootstrap { + private val log by logging() + + @JvmStatic + fun main(args: Array) { + Thread.currentThread().name = "Nino-BootstrapThread" + + val bannerFile = File("./assets/banner.txt").readText(Charsets.UTF_8) + for (line in bannerFile.split("\n")) { + val l = line + .replace("{{.Version}}", NinoInfo.VERSION) + .replace("{{.CommitSha}}", NinoInfo.COMMIT_HASH) + .replace("{{.BuildDate}}", NinoInfo.BUILD_DATE) + + println(l) + } + + val configFile = File("./config.yml") + val config = Yaml.default.decodeFromString(Config.serializer(), configFile.readText()) + + log.info("Connecting to PostgreSQL...") + val dataSource = HikariDataSource( + HikariConfig().apply { + jdbcUrl = "jdbc:postgresql://${config.database.host}:${config.database.port}/${config.database.name}" + username = config.database.username + password = config.database.password + schema = config.database.schema + driverClassName = "org.postgresql.Driver" + isAutoCommit = false + transactionIsolation = IsolationLevel.TRANSACTION_REPEATABLE_READ.name + leakDetectionThreshold = 30L * 1000 + poolName = "Nino-HikariPool" + } + ) + + Database.connect( + dataSource, + databaseConfig = DatabaseConfig { + defaultRepetitionAttempts = 5 + // defaultIsolationLevel = IsolationLevel.TRANSACTION_REPEATABLE_READ.levelId + sqlLogger = if (config.environment == Environment.Development) { + Slf4jSqlDebugLogger + } else { + null + } + } + ) + + // Create the database enums + runBlocking { + registerOrUpdateEnums() + } + + // Create the missing tables and columns + transaction { + SchemaUtils.createMissingTablesAndColumns( + AutomodTable, + CasesTable, + GlobalBansTable, + GuildsTable, + LoggingTable, + PunishmentsTable, + TagsTable, + UsersTable, + WarningsTable + ) + } + + log.info("Connected to PostgreSQL successfully! Now connecting to Redis...") + val redis = Manager(config) + redis.connect() + + log.info("Connected to Redis! Creating Kord instance...") + val kord = runBlocking { + Kord(config.token) { + enableShutdownHook = false + cache { + members { cache, desc -> MapEntryCache(cache, desc, MapLikeCollection.concurrentHashMap()) } + users { cache, desc -> MapEntryCache(cache, desc, MapLikeCollection.concurrentHashMap()) } + } + } + } + + // Setup Sentry + val os = ManagementFactory.getOperatingSystemMXBean() + if (config.sentryDsn != null) { + log.info("Installing Sentry...") + + Sentry.init { + it.dsn = config.sentryDsn + it.release = "v${NinoInfo.VERSION} (${NinoInfo.COMMIT_HASH})" + it.setLogger(SentryLogger(config.environment == Environment.Development)) + } + + Sentry.configureScope { + it.tags += mutableMapOf( + "nino.environment" to config.environment.toString(), + "nino.build.date" to NinoInfo.BUILD_DATE, + "nino.commitSha" to NinoInfo.COMMIT_HASH, + "nino.version" to NinoInfo.VERSION, + "system.user" to System.getProperty("user.name"), + "system.os" to "${os.name} (${os.arch}; ${os.version})" + ) + } + } + + val json = Json { + ignoreUnknownKeys = true + isLenient = true + } + + val httpClient = HttpClient(OkHttp) { + engine { + config { + followRedirects(true) + addInterceptor(LogInterceptor()) + + if (Sentry.isEnabled()) { + addInterceptor(SentryInterceptor()) + } + } + } + + install(WebSockets) + + install(ContentNegotiation) { + this.json(json) + } + + install(UserAgent) { + agent = "Nino/DiscordBot (+https://github.com/NinoDiscord/Nino; v${NinoInfo.VERSION})" + } + } + + log.info("Created Kord instance! Initializing modules...") + + val registry = Registry() + registry.register(MetricsModule(config.metrics, config.api != null)) + registry.register(LocalisationModule(config.defaultLocale, json)) + registry.register(TimeoutsModule(config.timeouts.uri, config.timeouts.auth, httpClient, json)) + registry.register(PunishmentModule()) + + if (config.ravy != null) { + log.info("Found configuration token for Ravy API!") + registry.register(RavyModule(config.ravy!!, httpClient)) + } + + var api: ApiServer? = null + if (config.api != null) { + log.info("API server is enabled! Registering to Koin...") + api = ApiServer(config) + } + + log.info("Initialized modules! Initializing Koin...") + val koin = startKoin { + modules( + jobsModule, + globalModule, + apiEndpointsModule, + module { + single { config } + single { kord } + single { dataSource } + single { redis } + single { registry } + single { json } + single { httpClient } + single { NinoBot() } + + api.ifNotNull { server -> + single { server } + } + } + ) + } + + log.info("Initialized modules! Launching Nino...") + addShutdownHook() + installGlobalUnhandledExceptionHandler() + + api.ifNotNull { s -> + NinoScope.launchIn { + s.launch() + } + } + + runBlocking { + val bot = koin.koin.get() + try { + bot.start() + } catch (e: Exception) { + log.error("Unable to bootstrap Nino:", e) + exitProcess(1) + } + } + } + + private fun addShutdownHook() { + val runtime = Runtime.getRuntime() + runtime.addShutdownHook( + thread(start = false, name = "Nino-ShutdownThread") { + log.warn("Shutting down...") + + if (GlobalContext.getKoinApplicationOrNull() != null) { + val kord = GlobalContext.retrieve() + val ds = GlobalContext.retrieve() + val redis = GlobalContext.retrieve() + val registry = GlobalContext.retrieve() + val apiServer = GlobalContext.get().getOrNull() + + runBlocking { + kord.gateway.stopAll() + NinoScope.cancel() + } + + apiServer?.destroy() + ds.close() + redis.close() + registry.unregisterAll() + } + + log.info("We are going offline, bye!") + + // Dispose TCP connection with Logstash (if any) + val logCtx = LoggerFactory.getILoggerFactory() as? LoggerContext + logCtx?.stop() + } + ) + } + + // credit: https://github.com/elastic/logstash/blob/main/logstash-core/src/main/java/org/logstash/Logstash.java#L98-L133 + private fun installGlobalUnhandledExceptionHandler() { + Thread.setDefaultUncaughtExceptionHandler { t, e -> + if (e is Error) { + log.error("Uncaught error in thread ${t.name} (#${t.id})", e) + var success = false + + if (e is InternalError) { + success = true + Runtime.getRuntime().halt(128) + } + + if (e is OutOfMemoryError) { + success = true + Runtime.getRuntime().halt(127) + } + + if (e is StackOverflowError) { + success = true + Runtime.getRuntime().halt(126) + } + + if (e is UnknownError) { + success = true + Runtime.getRuntime().halt(125) + } + + if (e is IOError) { + success = true + Runtime.getRuntime().halt(124) + } + + if (e is LinkageError) { + success = true + Runtime.getRuntime().halt(123) + } + + if (!success) { + Runtime.getRuntime().halt(120) + } + + exitProcess(1) + } else { + log.error("Uncaught exception in thread ${t.name} (#${t.id})", e) + } + } + } +} diff --git a/bot/src/main/resources/build-info.json b/bot/src/main/resources/build-info.json new file mode 100644 index 00000000..cc1adb96 --- /dev/null +++ b/bot/src/main/resources/build-info.json @@ -0,0 +1,5 @@ +{ + "version": "${version}", + "commit_sha": "${commitSha}", + "build_date": "${buildDate}" +} diff --git a/bot/src/main/resources/config/logging.example.properties b/bot/src/main/resources/config/logging.example.properties new file mode 100644 index 00000000..f9093654 --- /dev/null +++ b/bot/src/main/resources/config/logging.example.properties @@ -0,0 +1,50 @@ +# ? Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This is the configuration properties that you can define for Logback. +# You can override this using the system property: `-Dsh.nino.api.logback.config` to +# the path that this is at. +# +# This will also look in `bot/src/main/kotlin/resources/config/logging.properties`. +# This example file is just for documentation purposes. + +# This enables a list of encoders that Nino will enable if defined under +# this property. The encoders are: +# +# - Sentry (recommend in prod): Enables the Sentry hook to report errors from the `ERROR` log level +# and report it to Sentry. +# +# - Logstash: Enables the Logstash TCP hook to monitor Nino with the Elastic Stack. Nino +# uses this in production to monitor and check for logs. +# +# Examples: +# - nino.encoders=sentry,logstash +# - nino.encoders=sentry +nino.encoders= + +# This is the DSN to use when `nino.encoders` contains the Sentry encoder. +nino.dsn= + +# This is the TCP endpoint to reach when using the Logstash encoder. +nino.logstash.endpoint= + +# Enables verbose logging when printing. +nino.debug=false diff --git a/bot/src/main/resources/logback.xml b/bot/src/main/resources/logback.xml new file mode 100644 index 00000000..2de75471 --- /dev/null +++ b/bot/src/main/resources/logback.xml @@ -0,0 +1,130 @@ + + + + + + + + + [%d{yyyy-MM-dd | HH:mm:ss, +10}] %boldCyan([%thread]) %highlight([%logger{36}]) %boldMagenta(%-5level) :: %msg%n + + + + + + + + ${nino.dsn} + + + + + + + + + ${nino.logstash.endpoint} + + + + + + + + + + + + + + + + + + user_agent + user_id + + + {"project":"Nino","application":"bot"} + + + 5 minutes + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..e1c67cf1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import gay.floof.gradle.utils.* + +plugins { + application +} + +group = "sh.nino" +version = "$current" + +repositories { + mavenCentral() + mavenLocal() + noel() +} diff --git a/src/structures/decorators/Subscribe.ts b/buildSrc/build.gradle.kts similarity index 59% rename from src/structures/decorators/Subscribe.ts rename to buildSrc/build.gradle.kts index 58ba3c32..2110c283 100644 --- a/src/structures/decorators/Subscribe.ts +++ b/buildSrc/build.gradle.kts @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,23 +21,25 @@ * SOFTWARE. */ -import { MetadataKeys } from '../../util/Constants'; +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -interface Subscription { - run(...args: any[]): Promise; - event: string; +plugins { + groovy + `kotlin-dsl` } -export const getSubscriptionsIn = (target: any) => - Reflect.getMetadata(MetadataKeys.Subscribe, target) ?? []; -export default function Subscribe(event: string): MethodDecorator { - return (target, _, descriptor: TypedPropertyDescriptor) => { - const subscriptions = getSubscriptionsIn(target); - subscriptions.push({ - event, - run: descriptor.value!, - }); +repositories { + mavenCentral() + gradlePluginPortal() + maven("https://maven.floofy.dev/repo/releases") +} - Reflect.defineMetadata(MetadataKeys.Subscribe, subscriptions, target); - }; +dependencies { + implementation("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.2") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.5.0") + implementation("io.kotest:kotest-gradle-plugin:0.3.9") + implementation("gay.floof.utils:gradle-utils:1.3.0") + implementation(kotlin("gradle-plugin", version = "1.6.21")) + implementation(kotlin("serialization", version = "1.6.21")) + implementation(gradleApi()) } diff --git a/buildSrc/src/main/kotlin/Project.kt b/buildSrc/src/main/kotlin/Project.kt new file mode 100644 index 00000000..a102a3e6 --- /dev/null +++ b/buildSrc/src/main/kotlin/Project.kt @@ -0,0 +1,39 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import gay.floof.gradle.utils.* +import java.io.File +import java.util.concurrent.TimeUnit + +val current = Version(2, 0, 0, 0, ReleaseType.Beta) +val commitHash by lazy { + val cmd = "git rev-parse --short HEAD".split("\\s".toRegex()) + val proc = ProcessBuilder(cmd) + .directory(File(".")) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + proc.waitFor(1, TimeUnit.MINUTES) + proc.inputStream.bufferedReader().readText().trim() +} diff --git a/buildSrc/src/main/kotlin/nino-module.gradle.kts b/buildSrc/src/main/kotlin/nino-module.gradle.kts new file mode 100644 index 00000000..89027b22 --- /dev/null +++ b/buildSrc/src/main/kotlin/nino-module.gradle.kts @@ -0,0 +1,97 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import gay.floof.gradle.utils.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("plugin.serialization") + id("com.diffplug.spotless") + id("kotlinx-atomicfu") + id("io.kotest") + kotlin("jvm") +} + +val javaVersion = JavaVersion.VERSION_17 + +group = "sh.nino.bot" +version = if (project.version != "unspecified") project.version else "$current" + +repositories { + //noelware(snapshots = true) + maven("https://repo.perfectdreams.net/") + mavenCentral() + mavenLocal() + noel() +} + +dependencies { + // Testing utilities + testImplementation(platform("io.kotest:kotest-bom:5.2.3")) + testImplementation("io.kotest:kotest-runner-junit5") + testImplementation("io.kotest:kotest-assertions-core") + testImplementation("io.kotest:kotest-property") + + // do not link :bot:commons to the project itself + if (name != "commons") { + implementation(project(":commons")) + } +} + +// Setup Spotless in all subprojects +spotless { + kotlin { + trimTrailingWhitespace() + licenseHeaderFile("${rootProject.projectDir}/assets/HEADING") + endWithNewline() + + // We can't use the .editorconfig file, so we'll have to specify it here + // issue: https://github.com/diffplug/spotless/issues/142 + // ktlint 0.35.0 (default for Spotless) doesn't support trailing commas + ktlint("0.43.0").userData( + mapOf( + "no-consecutive-blank-lines" to "true", + "no-unit-return" to "true", + "disabled_rules" to "no-wildcard-imports,colon-spacing", + "indent_size" to "4" + ) + ) + } +} + +tasks { + withType { + kotlinOptions { + jvmTarget = javaVersion.toString() + javaParameters = true + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } + } +} + +java { +// sourceCompatibility = javaVersion +// targetCompatibility = javaVersion + + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) +} diff --git a/src/@types/json.d.ts b/commands/legacy/build.gradle.kts similarity index 88% rename from src/@types/json.d.ts rename to commands/legacy/build.gradle.kts index 655c723b..bfc0a556 100644 --- a/src/@types/json.d.ts +++ b/commands/legacy/build.gradle.kts @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,7 +21,6 @@ * SOFTWARE. */ -/** */ -interface JSON { - parse(content: string, reviver?: (this: any, key: string, value: any) => any): T; +plugins { + `nino-module` } diff --git a/src/entities/UserEntity.ts b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/AbstractCommand.kt similarity index 76% rename from src/entities/UserEntity.ts rename to commands/legacy/src/main/kotlin/sh/nino/discord/commands/AbstractCommand.kt index 2c8ed18a..a1f7a48f 100644 --- a/src/entities/UserEntity.ts +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/AbstractCommand.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +21,14 @@ * SOFTWARE. */ -import { Entity, Column, PrimaryColumn } from 'typeorm'; - -@Entity({ name: 'users' }) -export default class UserEntity { - @Column({ default: 'en_US' }) - public language!: string; - - @Column({ array: true, type: 'text' }) - public prefixes!: string[]; +package sh.nino.discord.commands - @PrimaryColumn({ name: 'user_id' }) - public id!: string; +/** + * Represents a main entrypoint for constructing commands. + */ +abstract class AbstractCommand { + /** + * The main execution point of this legacy command. + */ + abstract suspend fun execute() } diff --git a/src/singletons/Http.ts b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/Command.kt similarity index 82% rename from src/singletons/Http.ts rename to commands/legacy/src/main/kotlin/sh/nino/discord/commands/Command.kt index 64eeae72..90fea9f8 100644 --- a/src/singletons/Http.ts +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/Command.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +21,9 @@ * SOFTWARE. */ -import { HttpClient } from '@augu/orchid'; -import { version } from '../util/Constants'; +package sh.nino.discord.commands -export default new HttpClient({ - userAgent: `Nino (v${version}, https://github.com/NinoDiscord/Nino)`, -}); +/** + * **Command** is represented as the main center of a "legacy command." + */ +class Command diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandCategory.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandCategory.kt new file mode 100644 index 00000000..042310cd --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandCategory.kt @@ -0,0 +1,34 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands + +enum class CommandCategory(val emoji: String, val key: String, val localeKey: String = "") { + ADMIN("⚒️", "Administration", "admin"), + CORE("ℹ", "Core", "core"), + EASTER_EGG("", "Easter Egg"), + MODERATION("\uD83D\uDD28", "Moderation", "moderation"), + SYSTEM("", "System Administration"), + THREADS("\uD83E\uDDF5", "Channel Thread Moderation", "thread"), + VOICE("\uD83D\uDD08", "Voice Channel Moderation", "voice"); +} diff --git a/src/services/ListenerService.ts b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandHandler.kt similarity index 79% rename from src/services/ListenerService.ts rename to commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandHandler.kt index 40240575..6ff45433 100644 --- a/src/services/ListenerService.ts +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandHandler.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,13 +21,10 @@ * SOFTWARE. */ -import { Service } from '@augu/lilith'; -import { join } from 'path'; +package sh.nino.discord.commands -@Service({ - priority: 0, - children: join(process.cwd(), 'listeners'), - name: 'listeners', -}) -// a noop service to register all listeners -export default class ListenerService {} +/** + * Represents the handler for all commands, and where the legacy-based command + * execution is at. + */ +class CommandHandler diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandMessage.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandMessage.kt new file mode 100644 index 00000000..7605fd1e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/CommandMessage.kt @@ -0,0 +1,30 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands + +/** + * Represents the message that is represented when the [CommandHandler] has proceeded + * with the constraints that returned `false`. + */ +class CommandMessage diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/NinoCoreExtensions.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/NinoCoreExtensions.kt new file mode 100644 index 00000000..66cb3ff2 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/NinoCoreExtensions.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/Subcommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/Subcommand.kt new file mode 100644 index 00000000..66cb3ff2 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/Subcommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/AutomodCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/AutomodCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/AutomodCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/BanAppealsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/BanAppealsCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/BanAppealsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ExportCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ExportCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ExportCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ImportCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ImportCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/ImportCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/LoggingCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/LoggingCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/LoggingCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/PrefixCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/PrefixCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/PrefixCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/RoleConfigCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/RoleConfigCommand.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/RoleConfigCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/koinModule.kt new file mode 100644 index 00000000..aa833c23 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/administration/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.administration diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/ButtonClickAction.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/ButtonClickAction.kt new file mode 100644 index 00000000..ccbfbc04 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/ButtonClickAction.kt @@ -0,0 +1,35 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.annotations + +/** + * Represents an action that is represented when a button component was executed by the command. The [id] has + * to be the suffix after `nino:button:`. + * + * - If the full ID was `nino:button::accepted`, then [id] must be "accepted" + * + * This is a method annotation and the method MUST have a `suspend` modifier. + */ +@Target(AnnotationTarget.FUNCTION) +annotation class ButtonClickAction(val id: String) diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/Command.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/Command.kt new file mode 100644 index 00000000..64c038a6 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/Command.kt @@ -0,0 +1,58 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.annotations + +import sh.nino.discord.commands.CommandCategory + +/** + * Represents the metadata of a legacy-based command. + * @param name The name of the command, this cannot be over 32 characters. + * @param description This must be the path to the localisation of the description itself. + * @param aliases A list of aliases that the command can be executed with; same rules apply with the [name] attribute. + * @param examples A list of examples that is shown with the `--help`/`-h` flags or when invoked with the `help` command. + * This uses the templating language to support items like `{prefix}` and `{name}` in the examples. + * + * @param category The [category][CommandCategory] this command belongs to. + * @param cooldown The cooldown of the command in seconds, later, it will support custom bucket types (i.e, guild, channel) + * and possibly use Redis as a "cache?" + * + * @param ownerOnly If true, only the owners of the bot (defined under the `owners` config variable) can execute this command. + * @param userPermissions A list of permissions that the user needs to execute this command. Due to constraints with Kotlin, + * this has to be with the `Long` data type instead of Kord's `Permission` class due to annotations + * being processed at compile time and not at runtime where it can be evaluated. + * + * @param botPermissions A list of permissions that Nino requires to execute this command. Read the KDoc on the [userPermissions] + * attribute on why it is an [LongArray] and not an array of `Permission` objects. + */ +annotation class Command( + val name: String, + val description: String, + val aliases: Array = [], + val examples: Array = [], + val category: CommandCategory = CommandCategory.CORE, + val cooldown: Int = 5, + val ownerOnly: Boolean = false, + val userPermissions: LongArray = [], + val botPermissions: LongArray = [], +) diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/SelectMenuAction.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/SelectMenuAction.kt new file mode 100644 index 00000000..93dfdcb7 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/SelectMenuAction.kt @@ -0,0 +1,35 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.annotations + +/** + * Represents an action that a select menu option was clicked on when a select menu prompt + * has been executed by the command. The ID of the selection menu must be the suffix after + * `nino:selection::`, the prefix `nino:selection:` is already + * auto-generated when you use the [sh.nino.discord.commands.CommandMessage.promptSelectMenu] + * + * This is a method annotation and the method MUST have a `suspend` modifier. + */ +@Target(AnnotationTarget.FUNCTION) +annotation class SelectMenuAction(val id: String) diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/TextPromptAction.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/TextPromptAction.kt new file mode 100644 index 00000000..3223819d --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/annotations/TextPromptAction.kt @@ -0,0 +1,35 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.annotations + +/** + * Represents an action that a select menu option was clicked on when a select menu prompt + * has been executed by the command. The ID of the selection menu must be the suffix after + * `nino:text.prompt::`, the prefix `nino:text.prompt:` is already + * auto-generated when you use the [sh.nino.discord.commands.CommandMessage.promptTextPrompt] + * + * This is a method annotation and the method MUST have a `suspend` modifier. + */ +@Target(AnnotationTarget.FUNCTION) +annotation class TextPromptAction(val id: String) diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/Argument.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/Argument.kt new file mode 100644 index 00000000..6a3b08be --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/Argument.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentContainer.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentContainer.kt new file mode 100644 index 00000000..6a3b08be --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentContainer.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentReader.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentReader.kt new file mode 100644 index 00000000..6a3b08be --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/ArgumentReader.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/EnumArgumentReader.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/EnumArgumentReader.kt new file mode 100644 index 00000000..99d9dcb0 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/EnumArgumentReader.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments.readers diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/IntegerArgumentReader.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/IntegerArgumentReader.kt new file mode 100644 index 00000000..99d9dcb0 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/IntegerArgumentReader.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments.readers diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/MultiArgumentReader.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/MultiArgumentReader.kt new file mode 100644 index 00000000..99d9dcb0 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/MultiArgumentReader.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments.readers diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/StringArgumentReader.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/StringArgumentReader.kt new file mode 100644 index 00000000..99d9dcb0 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/arguments/readers/StringArgumentReader.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.arguments.readers diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/ButtonPaginationEmbed.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/ButtonPaginationEmbed.kt new file mode 100644 index 00000000..36114d6e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/ButtonPaginationEmbed.kt @@ -0,0 +1,328 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components + +import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.ComponentType +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.InteractionType +import dev.kord.core.Kord +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.edit +import dev.kord.core.entity.Message +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.on +import dev.kord.rest.builder.message.EmbedBuilder +import dev.kord.rest.builder.message.create.actionRow +import dev.kord.rest.builder.message.modify.actionRow +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import sh.nino.commons.RandomId +import sh.nino.commons.extensions.inject +import sh.nino.discord.commands.CommandMessage + +/** + * Extension method for [CommandMessage] to create a new [ButtonPaginationEmbed] without + * calling the constructor method of [ButtonPaginationEmbed] and invoking the [run method][ButtonPaginationEmbed.run] + */ +suspend fun CommandMessage.createButtonEmbed(embeds: List) { + // this is just for bleps and giggles + val embed = ButtonPaginationEmbed(null as TextChannel, null as User, embeds) + return embed.run() +} + +/** + * Represents a paginated embed with using the Buttons component. Since with new instance + * of [ButtonPaginationEmbed], a random ID is generated to identify the embed, so please, + * create a new one when you need it; do not re-use this one. + */ +class ButtonPaginationEmbed( + private val channel: TextChannel, + private val invoker: User, + private var embeds: List, +) { + companion object { + val REACTIONS = mapOf( + "stop" to "\u23F9\uFE0F", + "right" to "\u27A1\uFE0F", + "left" to "\u2B05\uFE0F", + "first" to "\u23EE\uFE0F", + "last" to "\u23ED\uFE0F" + ) + } + + private val kord: Kord by inject() + private var index = 0 + private val uniqueId = RandomId.generate(4) + private lateinit var job: Job + private lateinit var message: Message + private val listening: Boolean + get() = if (!this::job.isInitialized) false else this.job.isActive + + suspend fun close() { + // nop this + if (!listening) return + + message.delete("[Paginated Embed for ${invoker.tag}] Embed was destroyed by request.") + job.cancelAndJoin() + } + + suspend fun run() { + if (this::job.isInitialized) throw IllegalStateException("Embed is already running") + + val self = this // used for \/ + message = channel.createMessage { + embeds += self.embeds[index].apply { + footer { + text = "Page ${index + 1}/${self.embeds.size}" + } + } + + actionRow { + // Since when the embed is constructed, `disabled` for the first button + // is always disabled, but it will be enabled if needed. + interactionButton(ButtonStyle.Secondary, "nino:pagination:$uniqueId:first") { + emoji = DiscordPartialEmoji(id = null, REACTIONS["first"]!!) + disabled = true + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:left") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:stop") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:right") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:last") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + disabled = index >= self.embeds.size + } + } + } + + job = kord.on { + onInteractionReceive(this) + } + } + + private suspend fun onInteractionReceive(event: InteractionCreateEvent) { + // Do not do anything if the interaction type is NOT a Component. + if (event.interaction.type != InteractionType.Component) return + + // cast it at compile time + event as ComponentInteractionCreateEvent + + // Is it a button? If not, let's skip it! + if (event.interaction.componentType != ComponentType.Button) return + + // Do not run any actions if it doesn't start with `nino:selection:$uniqueId`. + if (!event.interaction.componentId.startsWith("nino:selection:$uniqueId")) return + + // Is the member who clicked the button the same as the one who invoked it? + if (event.interaction.data.member.value != null && event.interaction.data.member.value!!.userId != invoker.id) return + + // Acknowledge that we plan to update the message + event.interaction.deferPublicMessageUpdate() + + val self = this + + // Get the action to use + when (event.interaction.componentId.split(":").last()) { + "stop" -> close() + "left" -> { + index -= 1 + if (index < 0) index = embeds.size - 1 + + message.edit { + embeds?.plusAssign( + self.embeds[index].apply { + footer { + text = "Page ${index + 1}/${self.embeds.size}" + } + } + ) + + actionRow { + // Since when the embed is constructed, `disabled` for the first button + // is always disabled, but it will be enabled if needed. + interactionButton(ButtonStyle.Secondary, "nino:pagination:$uniqueId:first") { + emoji = DiscordPartialEmoji(id = null, REACTIONS["first"]!!) + disabled = index == 0 + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:left") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:stop") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:right") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:last") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + disabled = index >= self.embeds.size + } + } + } + } + + "right" -> { + index++ + if (index == embeds.size) index = 0 + + message.edit { + embeds?.plusAssign( + self.embeds[index].apply { + footer { + text = "Page ${index + 1}/${self.embeds.size}" + } + } + ) + + actionRow { + // Since when the embed is constructed, `disabled` for the first button + // is always disabled, but it will be enabled if needed. + interactionButton(ButtonStyle.Secondary, "nino:pagination:$uniqueId:first") { + emoji = DiscordPartialEmoji(id = null, REACTIONS["first"]!!) + disabled = index == 0 + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:left") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:stop") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:right") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:last") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + disabled = index >= self.embeds.size + } + } + } + } + + "first" -> { + // We shouldn't be able to press this (since it's guarded when we use the `run` method) + // but this is just a safety guard just in case we need it. + if (index == 0) return + + index = 0 + message.edit { + embeds?.plusAssign( + self.embeds[index].apply { + footer { + text = "Page ${index + 1}/${self.embeds.size}" + } + } + ) + + actionRow { + // Since when the embed is constructed, `disabled` for the first button + // is always disabled, but it will be enabled if needed. + interactionButton(ButtonStyle.Secondary, "nino:pagination:$uniqueId:first") { + emoji = DiscordPartialEmoji(id = null, REACTIONS["first"]!!) + disabled = true + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:left") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:stop") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:right") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:last") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + disabled = index >= self.embeds.size + } + } + } + } + + "last" -> { + val lastIndex = embeds.size - 1 + if (index == lastIndex) return + + index = lastIndex + message.edit { + embeds?.plusAssign( + self.embeds[index].apply { + footer { + text = "Page ${index + 1}/${self.embeds.size}" + } + } + ) + + actionRow { + // Since when the embed is constructed, `disabled` for the first button + // is always disabled, but it will be enabled if needed. + interactionButton(ButtonStyle.Secondary, "nino:pagination:$uniqueId:first") { + emoji = DiscordPartialEmoji(id = null, REACTIONS["first"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:left") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:stop") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:right") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + } + + interactionButton(ButtonStyle.Secondary, "nino:selection:$uniqueId:last") { + emoji = DiscordPartialEmoji(id = null, name = REACTIONS["left"]!!) + disabled = true + } + } + } + } + } + } +} diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/InteractionsHandler.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/InteractionsHandler.kt new file mode 100644 index 00000000..e5c8a942 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/InteractionsHandler.kt @@ -0,0 +1,43 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components + +import dev.kord.core.Kord +import gay.floof.utils.slf4j.logging +import sh.nino.discord.commands.CommandHandler + +/** + * Represents the handler for handling button clicks, select menu and text prompts. + */ +class InteractionsHandler(private val kord: Kord, private val commandHandler: CommandHandler) { + private val log by logging() + + init { + install() + } + + private fun install() { + log.debug("Installing the interactions handler...") + } +} diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/SelectMenuPrompt.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/SelectMenuPrompt.kt new file mode 100644 index 00000000..91caa853 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/SelectMenuPrompt.kt @@ -0,0 +1,140 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components + +import dev.kord.common.entity.ComponentType +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.InteractionType +import dev.kord.core.Kord +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Message +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.TextChannel +import dev.kord.core.event.interaction.ComponentInteractionCreateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.on +import dev.kord.rest.builder.message.create.UserMessageCreateBuilder +import dev.kord.rest.builder.message.create.actionRow +import kotlinx.coroutines.Job +import sh.nino.commons.RandomId +import sh.nino.commons.extensions.inject +import sh.nino.discord.commands.Command +import kotlin.reflect.KCallable + +/** + * Represents the prompt value for a [SelectMenuPrompt]. + * @param name The name of the select menu that is used. + * @param description The description of the select menu that is constructed. + */ +data class SelectMenuPromptValue( + val name: String, + val description: String, + val methodID: String, + val emoji: DiscordPartialEmoji? = null +) + +/** + * A prompt for invoking select menus with the current [Command][sh.nino.discord.commands.Command]. + * @param command The command that created this prompt. + * @param channel The channel that the select menu was executed in. + * @param invoker The [User] who invoked this prompt. + * @param values A list of values that was constructed. + * @param methodsToInvoke A [Map] of method IDs -> [KCallable] that is represented on the command. + */ +class SelectMenuPrompt( + private val command: Command, + private val channel: TextChannel, + private val invoker: User, + private val values: List, + private val methodsToInvoke: Map> +) { + private lateinit var message: Message + private lateinit var job: Job + private val uniqueId = RandomId.generate(4) + private val kord: Kord by inject() + + private val listening: Boolean + get() = if (!this::job.isInitialized) false else job.isActive + + suspend fun execute(builder: UserMessageCreateBuilder.() -> Unit) { + val data = UserMessageCreateBuilder().apply(builder) + message = channel.createMessage { + if (data.content != null) { + content = data.content + } + + if (data.embeds.isNotEmpty()) { + embeds.plusAssign(data.embeds) + } + + actionRow { + selectMenu("nino:select:menu:$uniqueId") { + allowedValues = 1..25 + + for ((i, value) in values.withIndex()) { + option(value.name, "nino:select:menu:$uniqueId:${value.methodID}") { + if (value.emoji != null) emoji = value.emoji + default = i == 0 + } + } + } + } + } + + job = kord.on { + onInteractionReceive(this) + } + } + + private suspend fun onInteractionReceive(event: InteractionCreateEvent) { + // Do not do anything if the interaction type is NOT a Component. + if (event.interaction.type != InteractionType.Component) return + + // cast it at compile time + event as ComponentInteractionCreateEvent + + // Is it a button? If not, let's skip it! + if (event.interaction.componentType != ComponentType.SelectMenu) return + + // Is the member who clicked the button the same as the one who invoked it? + if (event.interaction.data.member.value != null && event.interaction.data.member.value!!.userId != invoker.id) return + + // Check if the select menu ID is the same as this prompt + if (event.interaction.data.data.customId.value != null && event.interaction.data.data.customId.value!! != "nino:select:menu:$uniqueId") return + + // Acknowledge that we plan to update the message + event.interaction.deferPublicMessageUpdate() + } +} + +/* + private val kord: Kord by inject() + private var index = 0 + private val uniqueId = RandomId.generate(4) + private lateinit var job: Job + private lateinit var message: Message + private val listening: Boolean + get() = if (!this::job.isInitialized) false else this.job.isActive + + */ diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/TextModalPrompt.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/TextModalPrompt.kt new file mode 100644 index 00000000..f73db92a --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/TextModalPrompt.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/SelectMenuContext.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/SelectMenuContext.kt new file mode 100644 index 00000000..40276020 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/SelectMenuContext.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components.context diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/TextPromptContext.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/TextPromptContext.kt new file mode 100644 index 00000000..40276020 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/components/context/TextPromptContext.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.components.context diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/HelpCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/HelpCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/HelpCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/InviteMeCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/InviteMeCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/InviteMeCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/PingCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/PingCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/PingCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/ShardInfoCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/ShardInfoCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/ShardInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/SourceCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/SourceCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/SourceCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/StatisticsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/StatisticsCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/StatisticsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/UptimeCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/UptimeCommand.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/UptimeCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/koinModule.kt new file mode 100644 index 00000000..4e73b860 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/core/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.core diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/KadiCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/KadiCommand.kt new file mode 100644 index 00000000..b0bcabd8 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/KadiCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.easteregg diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/TestCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/TestCommand.kt new file mode 100644 index 00000000..b0bcabd8 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/TestCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.easteregg diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/WahCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/WahCommand.kt new file mode 100644 index 00000000..b0bcabd8 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/WahCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.easteregg diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/koinModule.kt new file mode 100644 index 00000000..b0bcabd8 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/easteregg/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.easteregg diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/Flag.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/Flag.kt new file mode 100644 index 00000000..5b26b7fd --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/Flag.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.flags diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/FlagContainer.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/FlagContainer.kt new file mode 100644 index 00000000..5b26b7fd --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/flags/FlagContainer.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.flags diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/koinModule.kt new file mode 100644 index 00000000..c1bd3ddd --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/koinModule.kt @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands + +import org.koin.dsl.module +import sh.nino.discord.commands.components.InteractionsHandler + +val legacyCommandsModule = module { + single { CommandHandler() } + single { InteractionsHandler(get(), get()) } +} diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/BanCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/BanCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/BanCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/CaseCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/CaseCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/CaseCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/HistoryCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/HistoryCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/HistoryCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/KickModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/KickModule.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/KickModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/LockdownCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/LockdownCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/LockdownCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/MuteCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/MuteCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/MuteCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PardonCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PardonCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PardonCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PurgeCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PurgeCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/PurgeCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/ReasonCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/ReasonCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/ReasonCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/SoftbanCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/SoftbanCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/SoftbanCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/TimeoutsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/TimeoutsCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/TimeoutsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/UnmuteCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/UnmuteCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/UnmuteCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/VoiceKickBotsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/VoiceKickBotsCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/VoiceKickBotsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarnCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarnCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarnCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarningsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarningsCommand.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/WarningsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/koinModule.kt new file mode 100644 index 00000000..2522356e --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/MuteThreadsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/MuteThreadsCommand.kt new file mode 100644 index 00000000..66cbd294 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/MuteThreadsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation.threads diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/UnmuteThreadsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/UnmuteThreadsCommand.kt new file mode 100644 index 00000000..66cbd294 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/moderation/threads/UnmuteThreadsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.moderation.threads diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/DumpThreadsCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/DumpThreadsCommand.kt new file mode 100644 index 00000000..1ff41e66 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/DumpThreadsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.system diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/EvalCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/EvalCommand.kt new file mode 100644 index 00000000..1ff41e66 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/EvalCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.system diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/GlobalBansCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/GlobalBansCommand.kt new file mode 100644 index 00000000..1ff41e66 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/GlobalBansCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.system diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/ShellCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/ShellCommand.kt new file mode 100644 index 00000000..1ff41e66 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/ShellCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.system diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/koinModule.kt new file mode 100644 index 00000000..1ff41e66 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/system/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.system diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ChannelInfoCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ChannelInfoCommand.kt new file mode 100644 index 00000000..98cce305 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ChannelInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.utilities diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/RoleInfoCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/RoleInfoCommand.kt new file mode 100644 index 00000000..98cce305 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/RoleInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.utilities diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ServerInfoCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ServerInfoCommand.kt new file mode 100644 index 00000000..98cce305 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/ServerInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.utilities diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/UserInfoCommand.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/UserInfoCommand.kt new file mode 100644 index 00000000..98cce305 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/UserInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.utilities diff --git a/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/koinModule.kt b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/koinModule.kt new file mode 100644 index 00000000..98cce305 --- /dev/null +++ b/commands/legacy/src/main/kotlin/sh/nino/discord/commands/utilities/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.commands.utilities diff --git a/commands/slash/build.gradle.kts b/commands/slash/build.gradle.kts new file mode 100644 index 00000000..bfc0a556 --- /dev/null +++ b/commands/slash/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractApplicationCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractApplicationCommand.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractApplicationCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommand.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommandGroup.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommandGroup.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/AbstractSubcommandGroup.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandCategory.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandCategory.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandCategory.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandHandler.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandHandler.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandHandler.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandOption.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandOption.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/CommandOption.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/AutomodCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/AutomodCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/AutomodCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/BanAppealsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/BanAppealsCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/BanAppealsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ExportCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ExportCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ExportCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ImportCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ImportCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/ImportCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/LoggingCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/LoggingCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/LoggingCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/RoleConfigCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/RoleConfigCommand.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/RoleConfigCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/koinModule.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/koinModule.kt new file mode 100644 index 00000000..3cc8673d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/administration/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.administration diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/ButtonClickAction.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/ButtonClickAction.kt new file mode 100644 index 00000000..b138cf94 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/ButtonClickAction.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.annotations diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/Command.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/Command.kt new file mode 100644 index 00000000..b138cf94 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/Command.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.annotations diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/SelectMenuAction.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/SelectMenuAction.kt new file mode 100644 index 00000000..b138cf94 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/SelectMenuAction.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.annotations diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/TextPromptAction.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/TextPromptAction.kt new file mode 100644 index 00000000..b138cf94 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/annotations/TextPromptAction.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.annotations diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/HelpCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/HelpCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/HelpCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/InviteCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/InviteCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/InviteCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/ShardInfoCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/ShardInfoCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/ShardInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/SourceCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/SourceCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/SourceCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/StatisticsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/StatisticsCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/StatisticsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/UptimeCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/UptimeCommand.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/UptimeCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/koinModule.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/koinModule.kt new file mode 100644 index 00000000..24e8180f --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/core/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.core diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/koinModule.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/koinModule.kt new file mode 100644 index 00000000..d4456887 --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/BanCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/BanCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/BanCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/CaseCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/CaseCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/CaseCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/HistoryCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/HistoryCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/HistoryCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/KickCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/KickCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/KickCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/LockdownCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/LockdownCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/LockdownCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/MuteCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/MuteCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/MuteCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/PardonCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/PardonCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/PardonCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/SoftbanCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/SoftbanCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/SoftbanCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/TimeoutsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/TimeoutsCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/TimeoutsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/UnmuteCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/UnmuteCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/UnmuteCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/VoiceKickBotsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/VoiceKickBotsCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/VoiceKickBotsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarnCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarnCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarnCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarningsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarningsCommand.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/WarningsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/koinModule.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/koinModule.kt new file mode 100644 index 00000000..77a5613a --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/MuteThreadsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/MuteThreadsCommand.kt new file mode 100644 index 00000000..55ed9a7d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/MuteThreadsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation.threads diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/UnmuteThreadsCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/UnmuteThreadsCommand.kt new file mode 100644 index 00000000..55ed9a7d --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/moderation/threads/UnmuteThreadsCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.moderation.threads diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ChannelInfoCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ChannelInfoCommand.kt new file mode 100644 index 00000000..a02ab7ac --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ChannelInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.utilities diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/RoleInfoCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/RoleInfoCommand.kt new file mode 100644 index 00000000..a02ab7ac --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/RoleInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.utilities diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ServerInfoCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ServerInfoCommand.kt new file mode 100644 index 00000000..a02ab7ac --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/ServerInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.utilities diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/UserInfoCommand.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/UserInfoCommand.kt new file mode 100644 index 00000000..a02ab7ac --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/UserInfoCommand.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.utilities diff --git a/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/koinModule.kt b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/koinModule.kt new file mode 100644 index 00000000..a02ab7ac --- /dev/null +++ b/commands/slash/src/main/kotlin/sh/nino/discord/slash/commands/utilities/koinModule.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.discord.slash.commands.utilities diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts new file mode 100644 index 00000000..9019cecd --- /dev/null +++ b/commons/build.gradle.kts @@ -0,0 +1,94 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + // Kotlin libraries + api(kotlin("reflect")) + + // BOMs + api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.3.2")) + api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1")) + api(platform("org.jetbrains.exposed:exposed-bom:0.38.2")) + api(platform("io.ktor:ktor-bom:2.0.0")) + + // kotlinx.coroutines + api("org.jetbrains.kotlinx:kotlinx-coroutines-core") + api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8") + + // kotlinx.serialization + api("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") + api("org.jetbrains.kotlinx:kotlinx-serialization-json") + api("org.jetbrains.kotlinx:kotlinx-serialization-core") + + // kotlinx.datetime + api("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2") + + // Noel's Utilities + api("gay.floof.commons", "commons-slf4j", "1.3.0") + + // Apache Utilities + api("org.apache.commons:commons-lang3:3.12.0") + + // Koin + api("io.insert-koin:koin-core:3.1.6") + + // Ktor (client) + api("io.ktor:ktor-serialization-kotlinx-json") + api("io.ktor:ktor-client-content-negotiation") + api("io.ktor:ktor-client-websockets") + api("io.ktor:ktor-client-okhttp") + api("io.ktor:ktor-client-core") + api("com.squareup.okhttp3:okhttp:4.9.3") + + // Kord + api("dev.kord:kord-core:0.8.0-M13") + api("dev.kord.x:emoji:0.5.0") + + // Database (PostgreSQL) + api("org.jetbrains.exposed:exposed-core") + api("org.jetbrains.exposed:exposed-jdbc") + api("org.jetbrains.exposed:exposed-dao") + + // PostgreSQL driver + api("org.postgresql:postgresql:42.3.4") + + // Connection pooling + api("com.zaxxer:HikariCP:5.0.1") + + // SLF4J + api("org.slf4j:slf4j-api:1.7.36") + + // Sentry + api("io.sentry:sentry-kotlin-extensions:5.7.3") + api("io.sentry:sentry:5.7.3") + + // Conditional logic for logback + api("org.codehaus.janino:janino:3.1.7") + + // Redis (Lettuce) + api("io.lettuce:lettuce-core:6.1.8.RELEASE") +} diff --git a/commons/src/main/kotlin/sh/nino/commons/Constants.kt b/commons/src/main/kotlin/sh/nino/commons/Constants.kt new file mode 100644 index 00000000..679006a9 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/Constants.kt @@ -0,0 +1,62 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons + +import sh.nino.commons.extensions.asKordColor +import java.awt.Color + +/** + * Represents constant variables throughout Nino subprojects. + */ +object Constants { + /** + * Returns the dedicated node from the JVM property `winterfox.dediNode`, from the + * environment variable: `WINTERFOX_DEDI_NODE`, or null if none were found. + */ + val dediNode by lazy { + // Check if we have a system property of `winterfox.dediNode`, + // if we do, let's just use it. + val node1 = System.getProperty("winterfox.dediNode", "")!! + if (node1.isNotEmpty()) return@lazy node1 + + // Is it in the system environment variables? + val node2 = try { + System.getenv("WINTERFOX_DEDI_NODE") + } catch (e: NullPointerException) { + null + } + + if (node2 != null) return@lazy node2 + null + } + + val COLOR = Color.decode("#ff69bd").asKordColor() + val UserDiscrimRegex = "^(\\w.+)#(\\d{4})$".toRegex() + val DiscordInviteRegex = "(http(s)?://(www.)?)?(discord.gg|discord.io|discord.me|discord.link|invite.gg)/\\w+".toRegex() + val UserMentionRegex = "^<@!?([0-9]+)>$".toRegex() + val ChannelMentionRegex = "<#([0-9]+)>$".toRegex() + val QuoteRegex = "['\"]".toRegex() + val IdRegex = "^\\d+$".toRegex() + val FlagRegex = "(?:--?|—)([\\w]+)(=?(\\w+|['\"].*['\"]))?".toRegex(RegexOption.IGNORE_CASE) +} diff --git a/commons/src/main/kotlin/sh/nino/commons/DiscordUtils.kt b/commons/src/main/kotlin/sh/nino/commons/DiscordUtils.kt new file mode 100644 index 00000000..0b97e622 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/DiscordUtils.kt @@ -0,0 +1,162 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons + +import dev.kord.cache.api.query +import dev.kord.core.Kord +import dev.kord.core.cache.data.UserData +import dev.kord.core.cache.idEq +import dev.kord.core.entity.Member +import dev.kord.core.entity.Role +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.Channel +import kotlinx.coroutines.flow.firstOrNull +import sh.nino.commons.extensions.asSnowflake +import sh.nino.commons.extensions.inject +import sh.nino.commons.extensions.sortWith + +/** + * Calculates a list of users from the specified arguments provided. It will try to find + * the user from cache, and you can retrieve users the following ways: + * + * - Using a user mention (`<@!280158289667555328>`) + * - Passing in "User#Discrim" (August#5820) + * - Passing in a Snowflake (`280158289667555328`) + * + * @param args The arguments from the command parser to retrieve the users. + * @return a list of [users][User], if any were found. + */ +suspend fun getMultipleUsersFromArgs(args: List): List { + // Grab the current Kord instance + val kord by inject() + val users = mutableListOf() + + // Check if we can get any by the mention + val mentions = args.filter { it.matches(Constants.UserMentionRegex) } + for (mention in mentions) { + val matcher = Constants.UserMentionRegex.toPattern().matcher(mention) + if (!matcher.matches()) continue + + val id = matcher.group(1) + val user = kord.getUser(id.asSnowflake()) + if (user != null) { + users.add(user) + } + } + + // Maybe by ID? + val ids = args.filter { it.matches(Constants.IdRegex) } + for (id in ids) { + val user = kord.getUser(id.asSnowflake()) + if (user != null) { + users.add(user) + } + } + + // Check if we need to get by User#Discrim + val userDiscrims = args.filter { it.matches(Constants.UserDiscrimRegex) } + for (u in userDiscrims) { + val ud = u.split("#") + + // Check if we can query it from local cache + val user = kord.cache.query { + idEq(UserData::username, ud.first()) + idEq(UserData::discriminator, ud.last()) + }.singleOrNull() + + if (user != null) { + val usr = User(user, kord) + users.add(usr) + } + } + + return users + .distinct() // remove dups + .toList() // immutable +} + +/** + * Calculates a list of channels from the specified arguments provided. It will try to find + * the channel in cache, and you can retrieve channels by: + * + * - Passing a channel mention (`<#794101954158526474>`) + * - Passing a snowflake (`794101954158526474`) + * + * @param args The arguments from the command parser to retrieve the channels. + * @return a list of [channels][Channel], if any were found. + */ +suspend fun getMultipleChannelsFromArgs(args: List): List { + // Grab the current Kord instance + val kord by inject() + val channels = mutableListOf() + + // Check if we can get the mention of the channel + val mentions = args.filter { it.matches(Constants.ChannelMentionRegex) } + for (mention in mentions) { + val matcher = Constants.ChannelMentionRegex.toPattern().matcher(mention) + if (!matcher.matches()) continue + + val id = matcher.group(1) + val channel = kord.getChannel(id.asSnowflake()) + if (channel != null) channels.add(channel) + } + + // Check if we can get the channel ID + val ids = args.filter { it.matches(Constants.IdRegex) } + for (id in ids) { + val channel = kord.getChannel(id.asSnowflake()) + if (channel != null) channels.add(channel) + } + + return channels + .distinct() // remove dups + .toList() // immutable +} + +/** + * Returns the highest role this [member] has. + * @param member The member to check the highest role. + * @returns The [Role] that was found or `null` if none was found. + */ +suspend fun getTopRoleOf(member: Member): Role? = member + .roles + .sortWith { a, b -> b.rawPosition - a.rawPosition } + .firstOrNull() + +/** + * Checks if [role A][a] is above [role B][b] in hierarchy (or vice-versa). + * @param a The first role + * @param b The second role + * @return If the first role is higher than the second role. + */ +fun isRoleAbove(a: Role?, b: Role?): Boolean { + if (a == null || b == null) return false + + return a.rawPosition > b.rawPosition +} + +/** + * Checks if [member A][a] is above [member B][b] in hierarchy (or vice-versa) + */ +suspend fun isMemberAbove(a: Member, b: Member): Boolean = isRoleAbove(getTopRoleOf(a), getTopRoleOf(b)) diff --git a/commons/src/main/kotlin/sh/nino/commons/NinoInfo.kt b/commons/src/main/kotlin/sh/nino/commons/NinoInfo.kt new file mode 100644 index 00000000..786b9139 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/NinoInfo.kt @@ -0,0 +1,54 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.* + +@OptIn(ExperimentalSerializationApi::class) +object NinoInfo { + /** + * Returns the version of **helm-server**. + */ + val VERSION: String + + /** + * Returns the commit SHA of **helm-server** that was built. + */ + val COMMIT_HASH: String + + /** + * Returns when **helm-server** was built at. + */ + val BUILD_DATE: String + + init { + val stream = this::class.java.getResourceAsStream("/build-info.json")!! + val data = Json.decodeFromStream(JsonObject.serializer(), stream) + + VERSION = data["version"]?.jsonPrimitive?.content ?: error("Unable to retrieve `version` from build-info.json!") + COMMIT_HASH = data["commit_sha"]?.jsonPrimitive?.content ?: error("Unable to retrieve `commit.sha` from build-info.json!") + BUILD_DATE = data["build_date"]?.jsonPrimitive?.content ?: error("Unable to retrieve `build.date` from build-info.json!") + } +} diff --git a/src/@types/eris.d.ts b/commons/src/main/kotlin/sh/nino/commons/RandomId.kt similarity index 64% rename from src/@types/eris.d.ts rename to commons/src/main/kotlin/sh/nino/commons/RandomId.kt index ea2e68b7..2f6450ab 100644 --- a/src/@types/eris.d.ts +++ b/commons/src/main/kotlin/sh/nino/commons/RandomId.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,25 +21,22 @@ * SOFTWARE. */ -import Eris from 'eris'; +package sh.nino.commons -declare module 'eris' { - interface Collection extends Map { - get(key: string | number): T | undefined; - get(key: string | number): V; +import java.security.SecureRandom - values(): IterableIterator; - values(): IterableIterator; +object RandomId { + private const val ALPHA_CHARS = "abcdefghijklmnopqrstuvxyz0123456789" + private val random by lazy { SecureRandom() } - filter(func: (i: T) => boolean): T[]; - filter(func: (i: V) => boolean): V[]; - } + fun generate(len: Int = 8): String { + val builder = StringBuilder() + for (i in 0 until len) { + val index = random.nextInt(ALPHA_CHARS.length) + val char = ALPHA_CHARS[index] + builder.append(char) + } - interface Guild { - channels: Collection; - } - - interface User { - tag: string; - } + return builder.toString() + } } diff --git a/commons/src/main/kotlin/sh/nino/commons/StringOrList.kt b/commons/src/main/kotlin/sh/nino/commons/StringOrList.kt new file mode 100644 index 00000000..e5db4fa0 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/StringOrList.kt @@ -0,0 +1,53 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons + +import kotlinx.serialization.Serializable +import sh.nino.commons.extensions.every +import sh.nino.commons.serialization.StringOrListSerializer + +@Serializable(with = StringOrListSerializer::class) +class StringOrList(private val value: Any) { + init { + // Check if it's a List<*> or String + check(value is List<*> || value is String) { "value $value was not a supplied string or list." } + + // If the value was a List, check if every value is a string + if (value is List<*>) { + check(value.every { it is String }) { "Not every value of the list was a string." } + } + } + + @Suppress("UNCHECKED_CAST") + val asList: List + get() = value as? List ?: error("Value was not an instance of `List`.") + + @Suppress("UNCHECKED_CAST") + val asString: String + get() = value as? String ?: error("Value was not an instance of `String`") + + @Suppress("UNCHECKED_CAST") + val asListOrNull: List? + get() = value as? List +} diff --git a/commons/src/main/kotlin/sh/nino/commons/data/APIConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/APIConfig.kt new file mode 100644 index 00000000..63fbd341 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/APIConfig.kt @@ -0,0 +1,104 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import kotlinx.serialization.* + +/** + * Represents the configuration for the API, which is a standalone + * application. + */ +@Serializable +data class APIConfig( + /** + * Represents the secret key for JWT signing, this CANNOT BE EMPTY + * OR A VALUE THAT IS EASY TO REMEMBER. + */ + @SerialName("jwt_secret_key") + val jwtSecretKey: String, + + /** + * If we should add additional security headers to the response. + */ + @SerialName("security_headers") + val securityHeaders: Boolean = true, + + /** + * Size of the queue to store all the application call instances + * that cannot be immediately processed. + */ + @SerialName("request_queue_limit") + val requestQueueLimit: Int = 16, + + /** + * Number of concurrently running requests from the same HTTP pipeline + */ + @SerialName("running_limit") + val runningLimit: Int = 10, + + /** + * Do not create separate call event groups and reuse worker + * groups for processing calls. + */ + @SerialName("share_work_group") + val shareWorkGroup: Boolean = false, + + /** + * Timeout in seconds for sending responses to the client. + */ + @SerialName("response_write_timeout") + val responseWriteTimeoutSeconds: Int = 10, + + /** + * Timeout in seconds to read incoming requests from the client, "0" = infinite. + */ + @SerialName("request_read_timeout") + val requestReadTimeout: Int = 0, + + /** + * If this is set to `true`, this will enable TCP keep alive for + * connections that are so-called "dead" and can be easily discarded. + * + * The timeout period is configured by the system, so configure + * the end host accordingly. + */ + @SerialName("keep_alive") + val tcpKeepAlive: Boolean = false, + + /** + * Append extra headers when sending out a response. + */ + @SerialName("extra_headers") + val extraHeaders: Map = mapOf(), + + /** + * The host address to use when creating a TCP listener. + */ + val host: String = "0.0.0.0", + + /** + * The port of the address to use when creating a TCP listener. + */ + val port: Int = 9393 +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/BotlistsConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/BotlistsConfig.kt new file mode 100644 index 00000000..2ae5acaf --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/BotlistsConfig.kt @@ -0,0 +1,48 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class BotlistsConfig( + @SerialName("dservices") + val discordServicesToken: String? = null, + + @SerialName("dboats") + val discordBoatsToken: String? = null, + + @SerialName("dbots") + val discordBotsToken: String? = null, + + @SerialName("topgg") + val topGGToken: String? = null, + + @SerialName("delly") + val dellyToken: String? = null, + + @SerialName("discords") + val discordsToken: String? = null +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/Config.kt b/commons/src/main/kotlin/sh/nino/commons/data/Config.kt new file mode 100644 index 00000000..ed5f1336 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/Config.kt @@ -0,0 +1,62 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import dev.kord.common.entity.ActivityType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +enum class Environment { + @SerialName("development") + Development, + + @SerialName("production") + Production +} + +@Serializable +data class Config( + @SerialName("default_locale") + val defaultLocale: String = "en_US", + val environment: Environment = Environment.Development, + + @SerialName("sentry_dsn") + val sentryDsn: String? = null, + val prefixes: List = listOf("x!"), + val botlists: BotlistsConfig? = null, + val database: PostgresConfig = PostgresConfig(), + val instatus: InstatusConfig? = null, + val timeouts: TimeoutsConfig, + val metrics: Boolean = false, + val owners: List = listOf(), + val status: StatusConfig = StatusConfig( + type = ActivityType.Game, + status = "with {guilds} guilds [#{shard_id}] https://nino.sh" + ), + val redis: RedisConfig, + val token: String, + val ravy: String? = null, + val api: APIConfig? = null +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/InstatusConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/InstatusConfig.kt new file mode 100644 index 00000000..f7e37e20 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/InstatusConfig.kt @@ -0,0 +1,37 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class InstatusConfig( + @SerialName("gateway_metric_id") + val gatewayMetricId: String? = null, + + @SerialName("page_id") + val pageId: String = "", + val token: String +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/PostgresConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/PostgresConfig.kt new file mode 100644 index 00000000..e07d01ca --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/PostgresConfig.kt @@ -0,0 +1,36 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import kotlinx.serialization.Serializable + +@Serializable +data class PostgresConfig( + val username: String = "postgres", + val password: String = "postgres", + val schema: String = "public", + val host: String = "localhost", + val port: Int = 5432, + val name: String = "nino" +) diff --git a/src/entities/BlacklistEntity.ts b/commons/src/main/kotlin/sh/nino/commons/data/RedisConfig.kt similarity index 70% rename from src/entities/BlacklistEntity.ts rename to commons/src/main/kotlin/sh/nino/commons/data/RedisConfig.kt index b9c179d9..8769f92e 100644 --- a/src/entities/BlacklistEntity.ts +++ b/commons/src/main/kotlin/sh/nino/commons/data/RedisConfig.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,24 +21,17 @@ * SOFTWARE. */ -import { Entity, PrimaryColumn, Column } from 'typeorm'; +package sh.nino.commons.data -export enum BlacklistType { - Guild, - User, -} +import kotlinx.serialization.Serializable -@Entity({ name: 'blacklists' }) -export default class BlacklistEntity { - @Column({ nullable: true }) - public reason?: string; - - @Column() - public issuer!: string; - - @Column({ type: 'enum', enum: BlacklistType }) - public type!: BlacklistType; - - @PrimaryColumn() - public id!: string; -} +@Serializable +data class RedisConfig( + val sentinels: List = listOf(), + val master: String? = null, + val password: String? = null, + val index: Int = 5, + val host: String = "localhost", + val port: Int = 6379, + val ssl: Boolean = false +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/ShardOrchestratorConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/ShardOrchestratorConfig.kt new file mode 100644 index 00000000..e3fc6376 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/ShardOrchestratorConfig.kt @@ -0,0 +1,24 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data diff --git a/src/commands/core/SourceCommand.ts b/commons/src/main/kotlin/sh/nino/commons/data/StatusConfig.kt similarity index 72% rename from src/commands/core/SourceCommand.ts rename to commons/src/main/kotlin/sh/nino/commons/data/StatusConfig.kt index b6476c5e..cee06353 100644 --- a/src/commands/core/SourceCommand.ts +++ b/commons/src/main/kotlin/sh/nino/commons/data/StatusConfig.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,18 +21,15 @@ * SOFTWARE. */ -import { Command, CommandMessage } from '../../structures'; +package sh.nino.commons.data -export default class SourceCommand extends Command { - constructor() { - super({ - description: 'descriptions.source', - aliases: ['git', 'github', 'sauce', 'oss'], - name: 'source', - }); - } +import dev.kord.common.entity.ActivityType +import dev.kord.common.entity.PresenceStatus +import kotlinx.serialization.Serializable - run(msg: CommandMessage) { - return msg.reply(':eyes: https://github.com/NinoDiscord/Nino'); - } -} +@Serializable +data class StatusConfig( + val presence: PresenceStatus = PresenceStatus.Online, + val status: String, + val type: ActivityType +) diff --git a/commons/src/main/kotlin/sh/nino/commons/data/TimeoutsConfig.kt b/commons/src/main/kotlin/sh/nino/commons/data/TimeoutsConfig.kt new file mode 100644 index 00000000..a927a7c9 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/data/TimeoutsConfig.kt @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.data + +import kotlinx.serialization.Serializable + +@Serializable +data class TimeoutsConfig( + val auth: String? = null, + val uri: String +) diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/FlowExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/FlowExtensions.kt new file mode 100644 index 00000000..3f83e5c3 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/FlowExtensions.kt @@ -0,0 +1,50 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +import kotlinx.coroutines.flow.* + +/** + * Sorts the [flow] from the [comparator] callback. This will emit entities to + * returned as a flow. + */ +fun Flow.sortWith(comparator: (T, T) -> Int): Flow = flow { + for (entity in toList().sortedWith(comparator)) emit(entity) +} + +/** + * Returns if the original Flow contains an entity + */ +suspend fun Flow.contains(value: T): Boolean = filter { it == value }.firstOrNull() != null + +suspend fun Flow.reduceWith(initialValue: U, operation: suspend (U, T) -> U): U { + var value: Any? = initialValue + collect { + @Suppress("UNCHECKED_CAST") + value = operation(value as U, it) + } + + @Suppress("UNCHECKED_CAST") + return value as U +} diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/FormatExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/FormatExtensions.kt new file mode 100644 index 00000000..444e08b8 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/FormatExtensions.kt @@ -0,0 +1,66 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +/** + * Format this [Long] into a readable byte format. + */ +fun Long.formatSize(): String { + val kilo = this / 1024L + val mega = kilo / 1024L + val giga = mega / 1024L + + return when { + kilo < 1024 -> "${kilo}KB" + mega < 1024 -> "${mega}MB" + else -> "${giga}GB" + } +} + +/** + * Returns the humanized time for a [java.lang.Long] instance + * @credit https://github.com/DV8FromTheWorld/Yui/blob/master/src/main/java/net/dv8tion/discord/commands/UptimeCommand.java#L34 + */ +fun Long.humanize(long: Boolean = false, includeMs: Boolean = false): String { + val months = this / 2592000000L % 12 + val weeks = this / 604800000L % 7 + val days = this / 86400000L % 30 + val hours = this / 3600000L % 24 + val minutes = this / 60000L % 60 + val seconds = this / 1000L % 60 + + val str = StringBuilder() + if (months > 0) str.append(if (long) "$months month${if (months == 1L) "" else "s"}, " else "${months}mo") + if (weeks > 0) str.append(if (long) "$weeks week${if (weeks == 1L) "" else "s"}, " else "${weeks}w") + if (days > 0) str.append(if (long) "$days day${if (days == 1L) "" else "s"}, " else "${days}d") + if (hours > 0) str.append(if (long) "$hours hour${if (hours == 1L) "" else "s"}, " else "${hours}h") + if (minutes > 0) str.append(if (long) "$minutes minute${if (minutes == 1L) "" else "s"}, " else "${minutes}m") + if (seconds > 0) str.append(if (long) "$seconds second${if (seconds == 1L) "" else "s"}${if (includeMs && this < 1000) ", " else ""}" else "${seconds}s") + + // Check if this is not over 1000 milliseconds (1 second), so we don't display + // 1 second, 1893 milliseconds + if (includeMs && this < 1000) str.append(if (long) "$this millisecond${if (this == 1L) "" else "s"}" else "${this}ms") + + return str.toString() +} diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/KoinExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/KoinExtensions.kt new file mode 100644 index 00000000..20293f63 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/KoinExtensions.kt @@ -0,0 +1,58 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +import org.koin.core.context.GlobalContext +import kotlin.properties.ReadOnlyProperty + +/** + * Injects a singleton into a property. + * ```kt + * class Owo { + * val kord: Kord by inject() + * } + * ``` + */ +inline fun inject(): ReadOnlyProperty = + ReadOnlyProperty { _, _ -> + val koin = GlobalContext.get() + koin.get() + } + +/** + * Retrieve a singleton from the Koin application without chaining `.get()` methods twice. + * ```kt + * val kord: Kord = GlobalContext.retrieve() + * ``` + */ +inline fun GlobalContext.retrieve(): T = get().get() + +/** + * Returns a list of singletons that match with type [T]. + * ```kt + * val commands: List = GlobalContext.retrieveAll() + * // => List [ ... ] + * ``` + */ +inline fun GlobalContext.retrieveAll(): List = get().getAll() diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/KordExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/KordExtensions.kt new file mode 100644 index 00000000..2808c6b0 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/KordExtensions.kt @@ -0,0 +1,177 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +import dev.kord.common.annotation.KordPreview +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Snowflake +import dev.kord.core.event.Event +import dev.kord.core.event.channel.* +import dev.kord.core.event.channel.thread.* +import dev.kord.core.event.gateway.ReadyEvent +import dev.kord.core.event.gateway.ResumedEvent +import dev.kord.core.event.guild.* +import dev.kord.core.event.interaction.ApplicationCommandCreateEvent +import dev.kord.core.event.interaction.ApplicationCommandDeleteEvent +import dev.kord.core.event.interaction.ApplicationCommandUpdateEvent +import dev.kord.core.event.interaction.InteractionCreateEvent +import dev.kord.core.event.message.* +import dev.kord.core.event.role.RoleCreateEvent +import dev.kord.core.event.role.RoleDeleteEvent +import dev.kord.core.event.role.RoleUpdateEvent +import dev.kord.core.event.user.PresenceUpdateEvent +import dev.kord.core.event.user.UserUpdateEvent +import dev.kord.core.event.user.VoiceStateUpdateEvent +import kotlinx.datetime.Instant +import java.awt.Color +import kotlin.math.floor + +/** + * Converts a Java [Color][AwtColor] into a Kord [color][Color]. + */ +fun Color.asKordColor(): dev.kord.common.Color = dev.kord.common.Color(this.red, this.green, this.blue) + +/** + * Short method to create a [Snowflake] based off this string. + */ +fun String.asSnowflake(): Snowflake = Snowflake(this) + +/** + * Short method to create a [Snowflake] based off a [Long]. + */ +fun Long.asSnowflake(): Snowflake = Snowflake(this) + +/** + * Returns a [Instant] on when this [Snowflake] was created at. + */ +val Snowflake.createdAt: Instant + get() = Instant.fromEpochMilliseconds(floor((this.value.toLong() / 4194304).toDouble()).toLong() + 1420070400000L) + +/** + * Returns the event name, so it can be used with Prometheus. + */ +@OptIn(KordPreview::class) +val Event.name: String + get() = when (this) { + is ResumedEvent -> "RESUMED" + is ReadyEvent -> "READY" + is ChannelCreateEvent -> "CHANNEL_CREATE" + is ChannelUpdateEvent -> "CHANNEL_UPDATE" + is ChannelDeleteEvent -> "CHANNEL_DELETE" + is ChannelPinsUpdateEvent -> "CHANNEL_PINS_UPDATE" + is TypingStartEvent -> "TYPING_START" + is GuildCreateEvent -> "GUILD_CREATE" + is GuildUpdateEvent -> "GUILD_UPDATE" + is GuildDeleteEvent -> "GUILD_DELETE" + is BanAddEvent -> "GUILD_BAN_ADD" + is BanRemoveEvent -> "GUILD_BAN_REMOVE" + is EmojisUpdateEvent -> "GUILD_EMOJIS_UPDATE" + is IntegrationsUpdateEvent -> "GUILD_INTEGRATIONS_UPDATE" + is MemberJoinEvent -> "GUILD_MEMBER_ADD" + is MemberLeaveEvent -> "GUILD_MEMBER_REMOVE" + is MemberUpdateEvent -> "GUILD_MEMBER_UPDATE" + is RoleCreateEvent -> "GUILD_ROLE_CREATE" + is RoleDeleteEvent -> "GUILD_ROLE_DELETE" + is RoleUpdateEvent -> "GUILD_ROLE_UPDATE" + is MembersChunkEvent -> "GUILD_MEMBERS_CHUNK" + is InviteCreateEvent -> "INVITE_CREATE" + is InviteDeleteEvent -> "INVITE_DELETE" + is MessageCreateEvent -> "MESSAGE_CREATE" + is MessageUpdateEvent -> "MESSAGE_UPDATE" + is MessageDeleteEvent -> "MESSAGE_DELETE" + is MessageBulkDeleteEvent -> "MESSAGE_DELETE_BULK" + is ReactionAddEvent -> "MESSAGE_REACTION_ADD" + is ReactionRemoveEvent -> "MESSAGE_REACTION_REMOVE" + is ReactionRemoveEmojiEvent -> "MESSAGE_REACTION_REMOVE_EMOJI" + is PresenceUpdateEvent -> "PRESENCE_UPDATE" + is UserUpdateEvent -> "USER_UPDATE" + is VoiceStateUpdateEvent -> "VOICE_STATE_UPDATE" + is VoiceServerUpdateEvent -> "VOICE_SERVER_UPDATE" + is WebhookUpdateEvent -> "WEBHOOKS_UPDATE" + is InteractionCreateEvent -> "INTERACTION_CREATE" + is ApplicationCommandCreateEvent -> "APPLICATION_COMMAND_CREATE" + is ApplicationCommandDeleteEvent -> "APPLICATION_COMMAND_DELETE" + is ApplicationCommandUpdateEvent -> "APPLICATION_COMMAND_UPDATE" + is ThreadChannelCreateEvent -> "THREAD_CREATE" + is ThreadChannelDeleteEvent -> "THREAD_DELETE" + is ThreadUpdateEvent -> "THREAD_UPDATE" + is ThreadListSyncEvent -> "THREAD_LIST_SYNC" + is ThreadMemberUpdateEvent -> "THREAD_MEMBER_UPDATE" + is ThreadMembersUpdateEvent -> "THREAD_MEMBERS_UPDATE" + is GuildScheduledEventCreateEvent -> "GUILD_SCHEDULED_EVENT_CREATE" + is GuildScheduledEventDeleteEvent -> "GUILD_SCHEDULED_EVENT_DELETE" + is GuildScheduledEventUpdateEvent -> "GUILD_SCHEDULED_EVENT_UPDATE" + is GuildScheduledEventUserAddEvent -> "GUILD_SCHEDULED_EVENT_USER_ADD" + is GuildScheduledEventUserRemoveEvent -> "GUILD_SCHEDULED_EVENT_USER_REMOVE" + else -> "UNKNOWN (${this::class})" + } + +/** + * Returns a stringified version of a [Permission]. + */ +fun Permission.asString(): String = when (this) { + is Permission.CreateInstantInvite -> "Create Instant Invite" + is Permission.KickMembers -> "Kick Members" + is Permission.BanMembers -> "Ban Members" + is Permission.Administrator -> "Administrator" + is Permission.ManageChannels -> "Manage Channels" + is Permission.AddReactions -> "Add Reactions" + is Permission.ViewAuditLog -> "View Audit Log" + is Permission.Stream -> "Stream in Voice Channels" + is Permission.ViewChannel -> "Read Messages in Guild Channels" + is Permission.SendMessages -> "Send Messages in Guild Channels" + is Permission.SendTTSMessages -> "Send Text-to-Speech Messages in Guild Channels" + is Permission.EmbedLinks -> "Embed Links" + is Permission.AttachFiles -> "Attach Files to Messages" + is Permission.ReadMessageHistory -> "Read Message History in Guild Channels" + is Permission.MentionEveryone -> "Mention Everyone" + is Permission.UseExternalEmojis -> "Use External Emojis in Messages" + is Permission.ViewGuildInsights -> "View Guild Insights" + is Permission.Connect -> "Connect in Voice Channels" + is Permission.Speak -> "Speak in Voice Channels" + is Permission.MuteMembers -> "Mute Members in Voice Channels" + is Permission.DeafenMembers -> "Deafen Members in Voice Channels" + is Permission.MoveMembers -> "Move Members in Voice Channels" + is Permission.UseVAD -> "Use VAD" + is Permission.PrioritySpeaker -> "Priority Speaker" + is Permission.ChangeNickname -> "Change Nickname" + is Permission.ManageNicknames -> "Manage Member Nicknames" + is Permission.ManageRoles -> "Manage Guild Roles" + is Permission.ManageWebhooks -> "Manage Guild Webhooks" + is Permission.ManageThreads -> "Manage Channel Threads" + is Permission.CreatePrivateThreads -> "Create Private Threads" + is Permission.CreatePublicThreads -> "Create Public Threads" + is Permission.SendMessagesInThreads -> "Send Messages in Threads" + is Permission.ManageGuild -> "Manage Guild" + is Permission.ManageMessages -> "Manage Messages" + is Permission.RequestToSpeak -> "Request To Speak" + is Permission.ManageEvents -> "Manage Guild Events" + is Permission.ModerateMembers -> "Moderate Guild Members" + is Permission.ManageEmojisAndStickers -> "Manage Guild Emojis and Stickers" + is Permission.UseApplicationCommands -> "Use Application Commands" + is Permission.UseExternalStickers -> "Use External Stickers" + is Permission.UseEmbeddedActivities -> "Use Embedded Guild Activities" + is Permission.Unknown -> "void" + is Permission.All -> "All" +} diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/KotlinExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/KotlinExtensions.kt new file mode 100644 index 00000000..463cd909 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/KotlinExtensions.kt @@ -0,0 +1,29 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +/** + * Calls the [block] if [T] is not null, returns as [U]. + */ +fun T?.ifNotNull(block: (T) -> U): U? = if (this != null) block(this) else null diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/ListExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/ListExtensions.kt new file mode 100644 index 00000000..ae8bc3c4 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/ListExtensions.kt @@ -0,0 +1,87 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +/** + * This extension creates a new [Map] of a [List] with [Pair]s. + * ```kt + * val owo = listOf(Pair("first", "second"), Pair("third", "fourth")) + * owo.asMap() + * // { "first": "second", "third": "fourth" } + * ``` + */ +fun List>.asMap(): Map { + val map = mutableMapOf() + for (item in this) { + map[item.first] = item.second + } + + return map.toMap() +} + +/** + * This extension returns a [Pair] from a list which the first item + * is from [List.first] while the second item is a [List] of the underlying + * data left over. + */ +fun List.pairUp(): Pair> = Pair(first(), drop(1)) + +/** + * Returns a [Boolean] if every element appears to be true from the [predicate] function. + */ +fun List.every(predicate: (T) -> Boolean): Boolean { + for (item in this) { + if (!predicate(item)) return false + } + + return true +} + +/** + * Returns the index of an item from a [predicate] function. + * @param predicate The lambda function to find the item you need. + * @return If the item was found, it'll return the index in the [List], + * or -1 if nothing was found. + */ +fun List.findIndex(predicate: (T) -> Boolean): Int { + for ((index, item) in this.withIndex()) { + if (predicate(item)) + return index + } + + return -1 +} + +/** + * Returns the index of an item from a [predicate] function. + * @param predicate The lambda function to find the item you need. + * @return If the item was found, it'll return the index in the [List], + * or -1 if nothing was found. + */ +fun Array.findIndex(predicate: (T) -> Boolean): Int = this.toList().findIndex(predicate) + +/** + * Removes the first item of the list. + */ +fun List.removeFirst(): List = drop(1) diff --git a/commons/src/main/kotlin/sh/nino/commons/extensions/StringExtensions.kt b/commons/src/main/kotlin/sh/nino/commons/extensions/StringExtensions.kt new file mode 100644 index 00000000..03a49b2a --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/extensions/StringExtensions.kt @@ -0,0 +1,66 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.extensions + +import java.io.File +import java.util.* +import java.util.concurrent.TimeUnit + +fun String.shell(): String { + val parts = this.split("\\s".toRegex()) + val process = ProcessBuilder(*parts.toTypedArray()) + .directory(File(".")) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + process.waitFor(60, TimeUnit.SECONDS) + return process.inputStream.bufferedReader().readText() +} + +fun String.titleCase(): String { + if (isNotEmpty()) { + val first = this[0] + if (first.isLowerCase()) { + return buildString { + val titleChar = first.titlecaseChar() + if (titleChar != first.uppercaseChar()) { + append(titleChar) + } else { + append(this@titleCase.substring(0, 1).uppercase(Locale.getDefault())) + } + + append(this@titleCase.substring(1)) + } + } + } + + return this +} + +fun String.elipsis(textLen: Int = 1995): String = if (this.length > textLen) { + "${this.slice(0..textLen)}..." +} else { + this +} diff --git a/commons/src/main/kotlin/sh/nino/commons/ms.kt b/commons/src/main/kotlin/sh/nino/commons/ms.kt new file mode 100644 index 00000000..96649f2b --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/ms.kt @@ -0,0 +1,95 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons + +import java.util.regex.Pattern +import kotlin.math.round + +// This is a Kotlin port of the NPM package: ms +// Project: https://github.com/vercel/ms/blob/master/src/index.ts + +private const val SECONDS = 1000 +private const val MINUTES = SECONDS * 60 +private const val HOURS = MINUTES * 60 +private const val DAYS = HOURS * 24 +private const val WEEKS = DAYS * 7 +private const val YEARS = DAYS * 365.25 +private val MS_REGEX = Pattern.compile("^(-?(?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?\$", Pattern.CASE_INSENSITIVE) + +object ms { + /** + * Converts the [value] into the milliseconds needed. + * @param value The value to convert + * @throws NumberFormatException If `value` is not a non-empty number. + * @throws IllegalStateException If `value` is not a valid string. + */ + fun fromString(value: String): Long { + if (value.length > 100) throw IllegalStateException("Value exceeds the max length of 100 chars.") + + val matcher = MS_REGEX.matcher(value) + if (!matcher.matches()) throw IllegalStateException("Invalid value: `$value` (regex=$MS_REGEX)") + + val n = java.lang.Float.parseFloat(matcher.group(1)) + + return when (val type = (matcher.group(2) ?: "ms").lowercase()) { + "years", "year", "yrs", "yr", "y" -> (n * YEARS).toLong() + "weeks", "week", "w" -> (n * WEEKS).toLong() + "days", "day", "d" -> (n * DAYS).toLong() + "hours", "hour", "hrs", "hr", "h" -> (n * HOURS).toLong() + "minutes", "minute", "mins", "min", "m" -> (n * MINUTES).toLong() + "seconds", "second", "secs", "sec", "s" -> (n * SECONDS).toLong() + "milliseconds", "millisecond", "msecs", "msec", "ms" -> n.toLong() + else -> throw IllegalStateException("Unit $type was matched, but no matching cases exists.") + } + } + + /** + * Parse the given [value] to return a unified time string. + * + * @param value The value to convert from + * @param long Set to `true` to use verbose formatting. Defaults to `false`. + */ + fun fromLong(value: Long, long: Boolean = true): String = if (long) { + fun pluralize(ms: Long, msAbs: Long, n: Int, name: String): String { + val isPlural = msAbs >= n * 1.5 + return "${round((ms / n).toDouble())} $name${if (isPlural) "s" else ""}" + } + + val msAbs = kotlin.math.abs(value) + if (msAbs >= DAYS) pluralize(value, msAbs, DAYS, "day") + if (msAbs >= HOURS) pluralize(value, msAbs, DAYS, "hour") + if (msAbs >= MINUTES) pluralize(value, msAbs, DAYS, "minute") + if (msAbs >= SECONDS) pluralize(value, msAbs, DAYS, "second") + + "$value ms" + } else { + val msAbs = kotlin.math.abs(value) + if (msAbs >= DAYS) "${round((value / DAYS).toDouble())}d" + if (msAbs >= HOURS) "${round((value / HOURS).toDouble())}h" + if (msAbs >= MINUTES) "${round((value / MINUTES).toDouble())}m" + if (msAbs >= SECONDS) "${round((value / SECONDS).toDouble())}s" + + "${value}ms" + } +} diff --git a/commons/src/main/kotlin/sh/nino/commons/serialization/StringOrListSerializer.kt b/commons/src/main/kotlin/sh/nino/commons/serialization/StringOrListSerializer.kt new file mode 100644 index 00000000..0ba530b5 --- /dev/null +++ b/commons/src/main/kotlin/sh/nino/commons/serialization/StringOrListSerializer.kt @@ -0,0 +1,59 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.commons.serialization + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.serializer +import sh.nino.commons.StringOrList + +object StringOrListSerializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("nino.StringOrArray") + + override fun deserialize(decoder: Decoder): StringOrList = try { + StringOrList(decoder.decodeSerializableValue(ListSerializer(serializer()))) + } catch (_: Exception) { + try { + StringOrList(decoder.decodeString()) + } catch (e: Exception) { + throw e + } + } + + override fun serialize(encoder: Encoder, value: StringOrList) { + try { + encoder.encodeSerializableValue(ListSerializer(serializer()), value.asList) + } catch (_: Exception) { + try { + encoder.encodeString(value.asString) + } catch (e: Exception) { + throw e + } + } + } +} diff --git a/commons/src/test/kotlin/sh/nino/tests/commons/DiscordUtilsTest.kt b/commons/src/test/kotlin/sh/nino/tests/commons/DiscordUtilsTest.kt new file mode 100644 index 00000000..a4a18a6c --- /dev/null +++ b/commons/src/test/kotlin/sh/nino/tests/commons/DiscordUtilsTest.kt @@ -0,0 +1,43 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:Suppress("UNUSED") + +package sh.nino.tests.commons + +import io.kotest.core.spec.style.AnnotationSpec +import io.kotest.matchers.shouldBe +import sh.nino.commons.Constants + +class DiscordUtilsTest: AnnotationSpec() { + @Test + fun `dedi node should be null`() { + Constants.dediNode shouldBe null + } + + @Test + fun `user discrim regex`() { + "August#5820".matches(Constants.UserDiscrimRegex) shouldBe true + "owo da uwu".matches(Constants.UserDiscrimRegex) shouldBe false + } +} diff --git a/commons/src/test/kotlin/sh/nino/tests/commons/StringOrListTest.kt b/commons/src/test/kotlin/sh/nino/tests/commons/StringOrListTest.kt new file mode 100644 index 00000000..34b0ff38 --- /dev/null +++ b/commons/src/test/kotlin/sh/nino/tests/commons/StringOrListTest.kt @@ -0,0 +1,111 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:Suppress("UNUSED") + +package sh.nino.tests.commons + +import io.kotest.assertions.throwables.shouldNotThrow +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.AnnotationSpec +import io.kotest.matchers.shouldBe +import kotlinx.serialization.json.Json +import sh.nino.commons.StringOrList + +class StringOrListTest: AnnotationSpec() { + @Test + fun `should not throw an error if initialized`() { + shouldNotThrow { + StringOrList("uwu") + } + + shouldNotThrow { + StringOrList(listOf("uwu", "owo")) + } + } + + @Test + fun `should throw an error if initialized`() { + shouldThrow { + StringOrList(69420) + } + + shouldThrow { + StringOrList(listOf("2222", 1)) + } + } + + @Test + fun `should not throw if StringOrList dot asList was called, but throw if StringOrList dot asString was called`() { + val instance = StringOrList(listOf("owo", "uwu")) + shouldNotThrow { + instance.asList + } + + shouldThrow { + instance.asString + } + } + + @Test + fun `should not throw if StringOrArray dot asString is called, but throw if StringOrArray dot asList is called`() { + val instance = StringOrList("owos da uwu") + shouldThrow { + instance.asList + } + + shouldNotThrow { + instance.asString + } + } + + @Test + fun `should encode successfully (serialization)`() { + val encoded = Json.encodeToString(StringOrList.serializer(), StringOrList("owo")) + encoded shouldBe "\"owo\"" + + val encodedString = Json.encodeToString(StringOrList.serializer(), StringOrList(listOf("owo"))) + encodedString shouldBe "[\"owo\"]" + } + + @Test + fun `should decode successfully (serialization)`() { + val decoded = Json.decodeFromString(StringOrList.serializer(), "\"owo\"") + shouldNotThrow { + decoded.asString + } + + shouldThrow { + decoded.asList + } + + val decodedList = Json.decodeFromString(StringOrList.serializer(), "[\"owo\"]") + shouldNotThrow { + decodedList.asList + } + + shouldThrow { + decodedList.asString + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000..5c9dfe9d --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules:metrics")) + implementation(project(":modules")) +} diff --git a/core/src/main/kotlin/sh/nino/core/NinoBot.kt b/core/src/main/kotlin/sh/nino/core/NinoBot.kt new file mode 100644 index 00000000..45ed7e03 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/NinoBot.kt @@ -0,0 +1,125 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core + +import dev.kord.common.annotation.KordExperimental +import dev.kord.common.annotation.KordUnsafe +import dev.kord.common.entity.ActivityType +import dev.kord.common.entity.DiscordBotActivity +import dev.kord.common.entity.PresenceStatus +import dev.kord.core.Kord +import dev.kord.gateway.DiscordPresence +import dev.kord.gateway.Intent +import dev.kord.gateway.Intents +import dev.kord.gateway.PrivilegedIntent +import dev.kord.rest.route.Route +import gay.floof.utils.slf4j.logging +import org.koin.core.context.GlobalContext +import sh.nino.commons.Constants +import sh.nino.commons.NinoInfo +import sh.nino.commons.extensions.formatSize +import sh.nino.commons.extensions.retrieve +import sh.nino.commons.extensions.retrieveAll +import sh.nino.core.listeners.applyGenericEvents +import sh.nino.core.timers.Job +import sh.nino.core.timers.Manager +import java.lang.management.ManagementFactory +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class NinoBot { + companion object { + val executorPool: ExecutorService = Executors.newCachedThreadPool(NinoThreadFactory) + } + + private val log by logging() + val bootTime = System.currentTimeMillis() + + @OptIn(KordUnsafe::class, KordExperimental::class, PrivilegedIntent::class) + suspend fun start() { + val runtime = Runtime.getRuntime() + val os = ManagementFactory.getOperatingSystemMXBean() + val threads = ManagementFactory.getThreadMXBean() + + log.info("+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+") + log.info("Runtime Information:") + log.info(" * Free / Total Memory [Max]: ${runtime.freeMemory().formatSize()}/${runtime.totalMemory().formatSize()} [${runtime.maxMemory().formatSize()}]") + log.info(" * Threads: ${threads.threadCount} (${threads.daemonThreadCount} background threads)") + log.info(" * Operating System: ${os.name} with ${os.availableProcessors} processors (${os.arch}; ${os.version})") + log.info(" * Versions:") + log.info(" * JVM [JRE]: v${System.getProperty("java.version", "Unknown")} (${System.getProperty("java.vendor", "Unknown")}) [${Runtime.version()}]") + log.info(" * Kotlin: v${KotlinVersion.CURRENT}") + log.info(" * Nino: v${NinoInfo.VERSION} (${NinoInfo.COMMIT_HASH} -- ${NinoInfo.BUILD_DATE})") + + if (Constants.dediNode != null) + log.info(" * Dedicated Node: ${Constants.dediNode}") + + log.info("+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+") + + val kord = GlobalContext.retrieve() + val gatewayInfo = kord.rest.unsafe(Route.GatewayBotGet) {} + + log.info("+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+") + log.info("Sharding Information:") + log.info(" * Using shard orchestrator: ") + log.info(" * Shards to Launch: ${gatewayInfo.shards}") + log.info(" * Session Limit: ${gatewayInfo.sessionStartLimit.remaining} / ${gatewayInfo.sessionStartLimit.total}") + log.info("+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+") + + // Schedule all timers + val scheduler = GlobalContext.retrieve() + val jobs = GlobalContext.retrieveAll() + scheduler.bulkSchedule(*jobs.toTypedArray()) + + // Apply Kord events we need + kord.applyGenericEvents() +// kord.applyGuildEvents() +// kord.applyGuildMemberEvents() +// kord.applyUserEvents() +// kord.applyVoiceStateEvents() +// kord.applyGuildBanEvents() + + // Launch! + kord.login { + presence = DiscordPresence( + status = PresenceStatus.Idle, + game = DiscordBotActivity( + name = "server fans go whirr...", + type = ActivityType.Listening + ), + + afk = true, + since = System.currentTimeMillis() + ) + + intents = Intents { + +Intent.Guilds + +Intent.GuildMessages + +Intent.GuildBans + +Intent.GuildVoiceStates + +Intent.GuildMembers + } + } + } +} diff --git a/core/src/main/kotlin/sh/nino/core/NinoScope.kt b/core/src/main/kotlin/sh/nino/core/NinoScope.kt new file mode 100644 index 00000000..95cb61a4 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/NinoScope.kt @@ -0,0 +1,36 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core + +import io.sentry.Sentry +import io.sentry.kotlin.SentryContext +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +object NinoScope: CoroutineScope { + override val coroutineContext: CoroutineContext = SupervisorJob() + NinoBot.executorPool.asCoroutineDispatcher() +} + +fun NinoScope.launchIn(start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job = + if (Sentry.isEnabled()) launch(SentryContext() + coroutineContext, start, block) else launch(coroutineContext, start, block) diff --git a/core/src/main/kotlin/sh/nino/core/NinoThreadFactory.kt b/core/src/main/kotlin/sh/nino/core/NinoThreadFactory.kt new file mode 100644 index 00000000..05e1b150 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/NinoThreadFactory.kt @@ -0,0 +1,42 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core + +import kotlinx.atomicfu.atomic +import java.util.concurrent.ThreadFactory + +object NinoThreadFactory: ThreadFactory { + private val threadIdCounter = atomic(0) + private val threadGroup: ThreadGroup = Thread.currentThread().threadGroup + + override fun newThread(r: Runnable): Thread { + val name = "Nino-ExecutorThread[${threadIdCounter.incrementAndGet()}]" + val thread = Thread(threadGroup, r, name) + + if (thread.priority != Thread.NORM_PRIORITY) + thread.priority = Thread.NORM_PRIORITY + + return thread + } +} diff --git a/core/src/main/kotlin/sh/nino/core/interceptors/LogInterceptor.kt b/core/src/main/kotlin/sh/nino/core/interceptors/LogInterceptor.kt new file mode 100644 index 00000000..77d0d67b --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/interceptors/LogInterceptor.kt @@ -0,0 +1,46 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.interceptors + +import gay.floof.utils.slf4j.logging +import okhttp3.Interceptor +import okhttp3.Response +import org.apache.commons.lang3.time.StopWatch +import java.util.concurrent.TimeUnit + +class LogInterceptor: Interceptor { + private val log by logging() + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val watch = StopWatch.createStarted() + + log.info("-> ${request.method.uppercase()} ${request.url}") + val res = chain.proceed(request) + watch.stop() + + log.info("<- [${res.code} ${res.message.ifEmpty { "OK" }} / ${res.protocol.toString().replace("h2", "http/2")}] ${request.method.uppercase()} ${request.url} [${watch.getTime(TimeUnit.MILLISECONDS)}ms]") + return res + } +} diff --git a/core/src/main/kotlin/sh/nino/core/interceptors/SentryInterceptor.kt b/core/src/main/kotlin/sh/nino/core/interceptors/SentryInterceptor.kt new file mode 100644 index 00000000..bf34caff --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/interceptors/SentryInterceptor.kt @@ -0,0 +1,69 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.interceptors + +import io.sentry.* +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +class SentryInterceptor: Interceptor { + private val hub: IHub = HubAdapter.getInstance() + + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + val url = "${request.method} ${request.url.encodedPath}" + val span = hub.span?.startChild("nino.http.client", "Request $url") + var statusCode = 200 + var response: Response? = null + + return try { + span?.toSentryTrace()?.let { + request = request + .newBuilder() + .addHeader(it.name, it.value) + .build() + } + + response = chain.proceed(request) + statusCode = response.code + span?.status = SpanStatus.fromHttpStatusCode(statusCode) + + response + } catch (e: IOException) { + span?.apply { + this.throwable = e + this.status = SpanStatus.INTERNAL_ERROR + } + + throw e + } finally { + span?.finish() + + val breb = Breadcrumb.http(request.url.toString(), request.method, statusCode) + breb.level = if (response?.isSuccessful == true) SentryLevel.FATAL else SentryLevel.ERROR + hub.addBreadcrumb(breb) + } + } +} diff --git a/core/src/main/kotlin/sh/nino/core/jobs/BotListsJob.kt b/core/src/main/kotlin/sh/nino/core/jobs/BotListsJob.kt new file mode 100644 index 00000000..cde50ac5 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/jobs/BotListsJob.kt @@ -0,0 +1,270 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.jobs + +import dev.kord.core.Kord +import gay.floof.utils.slf4j.logging +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.apache.commons.lang3.time.StopWatch +import sh.nino.commons.data.Config +import sh.nino.core.timers.Job +import java.util.concurrent.TimeUnit + +private data class BotlistResult( + val name: String, + val success: Boolean, + val time: Long, + val data: JsonObject +) + +class BotListsJob( + private val config: Config, + private val httpClient: HttpClient, + private val kord: Kord +): Job( + name = "botlists", + interval = 86400000 +) { + private val logger by logging() + + override suspend fun execute() { + if (config.botlists == null) return + + val guilds = kord.guilds.toList().size + val shardCount = kord.gateway.gateways.size + val data = mutableListOf() + val botlistWatch = StopWatch.createStarted() + + if (config.botlists!!.discordServicesToken != null) { + logger.info("* Found discordservices.net token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://api.discordservices.net/bot/${kord.selfId}/stats") { + body + + header("Authorization", config.botlists!!.discordServicesToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "discordservices.net", + success, + stopwatch.time, + json + ) + ) + } + + if (config.botlists!!.discordBoatsToken != null) { + logger.info("* Found discord.boats token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://discord.boats/api/bot/${kord.selfId}") { + setBody( + JsonObject( + mapOf( + "server_count" to JsonPrimitive(guilds) + ) + ) + ) + + header("Authorization", config.botlists!!.discordBoatsToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "discord.boats", + success, + stopwatch.time, + json + ) + ) + } + + if (config.botlists!!.discordBotsToken != null) { + logger.info("* Found discord.bots.gg token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://discord.bots.gg/api/v1/bots/${kord.selfId}/stats") { + setBody( + JsonObject( + mapOf( + "guildCount" to JsonPrimitive(guilds), + "shardCount" to JsonPrimitive(shardCount) + ) + ) + ) + + header("Authorization", config.botlists!!.discordBotsToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "discord.bots.gg", + success, + stopwatch.time, + json + ) + ) + } + + if (config.botlists!!.discordsToken != null) { + logger.info("* Found discords.com token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://discords.com/bots/api/${kord.selfId}") { + setBody( + JsonObject( + mapOf( + "server_count" to JsonPrimitive(guilds) + ) + ) + ) + + header("Authorization", config.botlists!!.discordsToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "discords.com", + success, + stopwatch.time, + json + ) + ) + } + + if (config.botlists!!.topGGToken != null) { + logger.info("* Found top.gg token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://top.gg/api/bots/${kord.selfId}/stats") { + setBody( + JsonObject( + mapOf( + "server_count" to JsonPrimitive(guilds), + "shard_count" to JsonPrimitive(shardCount) + ) + ) + ) + + header("Authorization", config.botlists!!.topGGToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "top.gg", + success, + stopwatch.time, + json + ) + ) + } + + // botlist by a cute fox, a carrot, and a funny api blob + if (config.botlists!!.dellyToken != null) { + logger.info("* Found Delly (Discord Extreme List) token!") + + val stopwatch = StopWatch.createStarted() + val res: HttpResponse = httpClient.post("https://api.discordextremelist.xyz/v2/bot/${kord.selfId}/stats") { + setBody( + JsonObject( + mapOf( + "guildCount" to JsonPrimitive(guilds), + "shardCount" to JsonPrimitive(shardCount) + ) + ) + ) + + header("Authorization", config.botlists!!.dellyToken) + } + + stopwatch.stop() + val success = res.status.isSuccess() + val json = withContext(Dispatchers.IO) { + res.body() + } + + data.add( + BotlistResult( + "Delly", + success, + stopwatch.time, + json + ) + ) + } + + botlistWatch.stop() + logger.info("Took ${botlistWatch.getTime(TimeUnit.MILLISECONDS)}ms to post to ${data.size} bot lists.") + + logger.info("----------") + for (list in data) { + logger.info("|- ${list.name}") + logger.info("\\- Took ${list.time}ms to post data.") + logger.info("\\- ${if (list.success) "and it was successful" else "was not successful"}") + logger.info(list.data.toString()) + } + logger.info("----------") + } +} diff --git a/core/src/main/kotlin/sh/nino/core/jobs/GatewayPingJob.kt b/core/src/main/kotlin/sh/nino/core/jobs/GatewayPingJob.kt new file mode 100644 index 00000000..026d47bc --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/jobs/GatewayPingJob.kt @@ -0,0 +1,87 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.jobs + +import dev.kord.core.Kord +import gay.floof.utils.slf4j.logging +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.serialization.Serializable +import sh.nino.commons.data.Config +import sh.nino.core.timers.Job +import sh.nino.modules.Registry +import sh.nino.modules.metrics.MetricsModule +import kotlin.time.Duration +import kotlin.time.DurationUnit + +@Serializable +data class InstatusPostMetricBody( + val timestamp: Long, + val value: Long +) + +class GatewayPingJob( + private val config: Config, + private val httpClient: HttpClient, + private val kord: Kord +): Job( + "gateway.ping", + 5000 +) { + private val metrics: MetricsModule? by Registry.inject() + private val log by logging() + + override suspend fun execute() { + if (metrics?.enabled == true) { + val averagePing = kord.gateway.averagePing ?: Duration.ZERO + metrics?.gatewayLatency?.observe(averagePing.inWholeMilliseconds.toDouble()) + + // Log the duration for all shards + for ((shardId, shard) in kord.gateway.gateways) { + metrics?.gatewayPing?.labels("$shardId")?.observe((shard.ping.value ?: Duration.ZERO).inWholeMilliseconds.toDouble()) + } + } + + if (config.instatus != null && config.instatus?.gatewayMetricId != null) { + log.debug("Instatus configuration is available, now posting to Instatus...") + val res: HttpResponse = httpClient.post("https://api.instatus.com/v1/${config.instatus!!.pageId}/metrics/${config.instatus!!.gatewayMetricId}") { + setBody( + InstatusPostMetricBody( + timestamp = System.currentTimeMillis(), + value = (kord.gateway.averagePing ?: Duration.ZERO).toLong(DurationUnit.MILLISECONDS) + ) + ) + + header("Authorization", config.instatus!!.token) + } + + if (!res.status.isSuccess()) { + log.warn("Unable to post to Instatus (${res.status.value} ${res.status.description}): ${res.body()}") + } + } + } +} diff --git a/src/structures/index.ts b/core/src/main/kotlin/sh/nino/core/jobs/koinModule.kt similarity index 74% rename from src/structures/index.ts rename to core/src/main/kotlin/sh/nino/core/jobs/koinModule.kt index 4fbd611d..8d59fb8f 100644 --- a/src/structures/index.ts +++ b/core/src/main/kotlin/sh/nino/core/jobs/koinModule.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +21,13 @@ * SOFTWARE. */ -export { default as Subcommand } from './decorators/Subcommand'; -export { default as Subscribe } from './decorators/Subscribe'; +package sh.nino.core.jobs -export { default as CommandMessage } from './CommandMessage'; -export { default as EmbedBuilder } from './EmbedBuilder'; -export { default as Command } from './Command'; -export { Automod } from './Automod'; +import org.koin.dsl.bind +import org.koin.dsl.module +import sh.nino.core.timers.Job + +val jobsModule = module { + single { GatewayPingJob(get(), get(), get()) } bind Job::class + single { BotListsJob(get(), get(), get()) } bind Job::class +} diff --git a/core/src/main/kotlin/sh/nino/core/koinModule.kt b/core/src/main/kotlin/sh/nino/core/koinModule.kt new file mode 100644 index 00000000..164910b9 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/koinModule.kt @@ -0,0 +1,33 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core + +import org.koin.dsl.module +import sh.nino.core.timers.Manager + +val globalModule = module { + single { + Manager() + } +} diff --git a/core/src/main/kotlin/sh/nino/core/listeners/GenericListener.kt b/core/src/main/kotlin/sh/nino/core/listeners/GenericListener.kt new file mode 100644 index 00000000..8eed67ca --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/listeners/GenericListener.kt @@ -0,0 +1,117 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("GenericListenerKt") + +package sh.nino.core.listeners + +import dev.kord.common.entity.ActivityType +import dev.kord.core.Kord +import dev.kord.core.event.Event +import dev.kord.core.event.gateway.DisconnectEvent +import dev.kord.core.event.gateway.ReadyEvent +import dev.kord.core.on +import kotlinx.coroutines.flow.count +import org.koin.core.context.GlobalContext +import org.slf4j.LoggerFactory +import sh.nino.commons.data.Config +import sh.nino.commons.extensions.humanize +import sh.nino.commons.extensions.inject +import sh.nino.commons.extensions.name +import sh.nino.commons.extensions.retrieve +import sh.nino.core.NinoBot +import sh.nino.modules.Registry +import sh.nino.modules.metrics.MetricsModule +import sh.nino.modules.metrics.incEvent + +fun Kord.applyGenericEvents() { + val log = LoggerFactory.getLogger("sh.nino.core.listeners.kord.GenericListenerKt") + val nino: NinoBot by inject() + val metrics by Registry.inject() + + on { + log.info("Successfully launched bot as ${this.self.tag} (${this.self.id}) on shard #${this.shard} in ${(System.currentTimeMillis() - nino.bootTime).humanize(true, includeMs = true)}ms") + log.info("Ready in ${this.guilds.size} guilds! | Gateway v${this.gatewayVersion}") + + val config = GlobalContext.retrieve() + val guildCount = kord.guilds.count() + val currStatus = config.status.status + .replace("{shard_id}", "$shard") + .replace("{guilds}", "$guildCount") + + if (metrics?.enabled == true) { + metrics?.guildCount?.set(guildCount.toDouble()) + } + + kord.editPresence { + status = config.status.presence + when (config.status.type) { + ActivityType.Listening -> listening(currStatus) + ActivityType.Game -> playing(currStatus) + ActivityType.Competing -> competing(currStatus) + ActivityType.Watching -> watching(currStatus) + else -> { + playing(currStatus) + } + } + } + } + + on { + val reason = buildString { + if (this@on is DisconnectEvent.DetachEvent) + append("Shard #${this@on.shard} has been detached.") + + if (this@on is DisconnectEvent.UserCloseEvent) + append("Closed by you.") + + if (this@on is DisconnectEvent.TimeoutEvent) + append("Possible internet connection loss; something was timed out. :<") + + if (this@on is DisconnectEvent.DiscordCloseEvent) { + val event = this@on + append("Discord closed off our connection (${event.closeCode.name} ~ ${event.closeCode.code}; recoverable=${if (event.recoverable) "yes" else "no"})") + } + + if (this@on is DisconnectEvent.RetryLimitReachedEvent) + append("Failed to established connection too many times.") + + if (this@on is DisconnectEvent.ReconnectingEvent) + append("Requested reconnect from Discord.") + + if (this@on is DisconnectEvent.SessionReset) + append("Gateway was closed; attempting to start new session.") + + if (this@on is DisconnectEvent.ZombieConnectionEvent) + append("Discord is no longer responding to gateway commands.") + } + + log.warn("Shard #${this.shard} has disconnected from the world: $reason") + } + + on { + metrics?.incEvent(shard, name) + } + + log.info("✔ Registered all generic events :3") +} diff --git a/core/src/main/kotlin/sh/nino/core/redis/Manager.kt b/core/src/main/kotlin/sh/nino/core/redis/Manager.kt new file mode 100644 index 00000000..17e03716 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/redis/Manager.kt @@ -0,0 +1,150 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.redis + +import gay.floof.utils.slf4j.logging +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI +import io.lettuce.core.api.StatefulRedisConnection +import io.lettuce.core.api.async.RedisAsyncCommands +import kotlinx.coroutines.future.await +import org.apache.commons.lang3.time.StopWatch +import sh.nino.commons.data.Config +import sh.nino.commons.extensions.asMap +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +data class RedisStats( + val serverStats: Map, + val stats: Map, + val ping: Duration +) + +class Manager(config: Config): AutoCloseable { + private lateinit var connection: StatefulRedisConnection + lateinit var commands: RedisAsyncCommands + private val logger by logging() + val client: RedisClient + + init { + logger.info("Creating Redis client...") + + val redisUri: RedisURI = if (config.redis.sentinels.isNotEmpty()) { + val builder = RedisURI.builder() + val sentinelRedisUri = RedisURI.builder() + .withSentinelMasterId(config.redis.master!!) + .withDatabase(config.redis.index) + + for (host in config.redis.sentinels) { + val (h, port) = host.split(":") + sentinelRedisUri.withSentinel(h, Integer.parseInt(port)) + } + + if (config.redis.password != null) + sentinelRedisUri.withPassword(config.redis.password!!.toCharArray()) + + builder + .withSentinel(sentinelRedisUri.build()) + .build() + } else { + val builder = RedisURI + .builder() + .withHost(config.redis.host) + .withPort(config.redis.port) + .withDatabase(config.redis.index) + + if (config.redis.password != null) + builder.withPassword(config.redis.password!!.toCharArray()) + + builder.build() + } + + client = RedisClient.create(redisUri) + } + + override fun close() { + // If the connection was never established, skip. + if (!::connection.isInitialized) return + + logger.warn("Closing Redis connection...") + connection.close() + client.shutdown() + } + + fun connect() { + // If it was already established, let's not skip. + if (::connection.isInitialized) return + + logger.info("Creating connection...") + connection = client.connect() + commands = connection.async() + + logger.info("Connected!") + } + + suspend fun getPing(): Duration { + // If the connection wasn't established, + // let's return Duration.ZERO + if (::connection.isInitialized) return Duration.ZERO + + val watch = StopWatch.createStarted() + commands.ping().await() + watch.stop() + + return watch.time.toDuration(DurationUnit.MILLISECONDS) + } + + suspend fun getStats(): RedisStats { + val ping = getPing() + + // get stats from connection + val serverStats = commands.info("server").await() + val stats = commands.info("stats").await() + + val mappedServerStats = serverStats!! + .split("\r\n?".toRegex()) + .drop(1) + .dropLast(1) + .map { + val (key, value) = it.split(":") + key to value + }.asMap() + + val mappedStats = stats!! + .split("\r\n?".toRegex()) + .drop(1) + .dropLast(1) + .map { + val (key, value) = it.split(":") + key to value + }.asMap() + + return RedisStats( + mappedServerStats, + mappedStats, + ping + ) + } +} diff --git a/core/src/main/kotlin/sh/nino/core/sentry/SentryLogger.kt b/core/src/main/kotlin/sh/nino/core/sentry/SentryLogger.kt new file mode 100644 index 00000000..506d5beb --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/sentry/SentryLogger.kt @@ -0,0 +1,97 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.sentry + +import gay.floof.utils.slf4j.logging +import io.sentry.ILogger +import io.sentry.SentryLevel + +class SentryLogger(private val debug: Boolean): ILogger { + private val log by logging() + + /** + * Logs a message with the specified level, message and optional arguments. + * + * @param level The SentryLevel. + * @param message The message. + * @param args The optional arguments to format the message. + */ + override fun log(level: SentryLevel, message: String, vararg args: Any?) { + if (!isEnabled(level)) return + + return when (level) { + SentryLevel.WARNING -> log.warn(message, args) + SentryLevel.DEBUG -> log.debug(message, args) + SentryLevel.ERROR -> log.error(message, args) + SentryLevel.FATAL -> log.error(message, args) + SentryLevel.INFO -> log.info(message, args) + } + } + + /** + * Logs a message with the specified level, message and optional arguments. + * + * @param level The SentryLevel. + * @param message The message. + * @param throwable The throwable to log. + */ + override fun log(level: SentryLevel, message: String, throwable: Throwable?) { + if (!isEnabled(level)) return + + return when (level) { + SentryLevel.ERROR -> log.error(message, throwable) + SentryLevel.FATAL -> log.error(message, throwable) + else -> {} // nop + } + } + + /** + * Logs a message with the specified level, throwable, message and optional arguments. + * + * @param level The SentryLevel. + * @param throwable The throwable to log. + * @param message The message. + * @param args the formatting arguments + */ + override fun log(level: SentryLevel, throwable: Throwable?, message: String, vararg args: Any?) { + if (!isEnabled(level)) return + + return when (level) { + SentryLevel.ERROR -> log.error(message, throwable, args) + SentryLevel.FATAL -> log.error(message, throwable, args) + else -> {} // nop + } + } + + /** + * Whether this logger is enabled for the specified SentryLevel. + * + * @param level The SentryLevel to test against. + * @return True if a log message would be recorded for the level. Otherwise false. + */ + override fun isEnabled(level: SentryLevel?): Boolean = when (level) { + SentryLevel.DEBUG -> debug + else -> true + } +} diff --git a/scripts/util/getCaseType.js b/core/src/main/kotlin/sh/nino/core/timers/Job.kt similarity index 57% rename from scripts/util/getCaseType.js rename to core/src/main/kotlin/sh/nino/core/timers/Job.kt index 99188470..4408fc45 100644 --- a/scripts/util/getCaseType.js +++ b/core/src/main/kotlin/sh/nino/core/timers/Job.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +21,27 @@ * SOFTWARE. */ -const { PunishmentType } = require('../../build/entities/PunishmentsEntity'); +package sh.nino.core.timers + +import kotlinx.coroutines.Job as CoroutineJob /** - * Determines the type from v0.x to v1.x - * @param {'warning remove' | 'warning add' | 'unmute' | 'kick' | 'mute' | 'ban'} type The type to serialize - * @returns {string} The punishment type + * Represents a base instance of a job that can be timed per basis. This abstract class + * takes in a [name], which is... self-explanatory and the [interval] to tick to call + * the [execute] function. */ -module.exports = (type) => { - switch (type) { - case 'warning remove': - return PunishmentType.WarningRemoved; - - case 'warning add': - return PunishmentType.WarningAdded; - - case 'unmute': - return PunishmentType.Unmute; - - case 'kick': - return PunishmentType.Kick; - - case 'mute': - return PunishmentType.Mute; +abstract class Job( + val name: String, + val interval: Int +) { + /** + * Represents the current coroutine [job][CoroutineJob] that is being executed. This + * can be `null` if the job was never scheduled or was unscheduled. + */ + var coroutineJob: CoroutineJob? = null - case 'ban': - return PunishmentType.Ban; - } -}; + /** + * The executor function to call every tick of the [interval] specified. + */ + abstract suspend fun execute() +} diff --git a/core/src/main/kotlin/sh/nino/core/timers/Manager.kt b/core/src/main/kotlin/sh/nino/core/timers/Manager.kt new file mode 100644 index 00000000..cfe6eb9f --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/timers/Manager.kt @@ -0,0 +1,57 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.timers + +import gay.floof.utils.slf4j.logging +import io.ktor.utils.io.* +import kotlin.time.Duration.Companion.seconds + +/** + * The timer manager is the main manager to schedule and unschedule all the timers + * that were registered. + */ +class Manager { + private val scope = Scope() + private val logger by logging() + private val jobs: MutableList = mutableListOf() + + fun schedule(job: Job) { + logger.info("Scheduled job ${job.name} for every ${job.interval.seconds} seconds!") + val coroutineJob = scope.launch(job) + + job.coroutineJob = coroutineJob + coroutineJob.start() + + jobs.add(job) + } + + fun bulkSchedule(vararg jobs: Job) { + for (job in jobs) schedule(job) + } + + fun unschedule() { + logger.warn("Unscheduled all timer jobs...") + for (job in jobs) job.coroutineJob!!.cancel(CancellationException("Unscheduled by program")) + } +} diff --git a/core/src/main/kotlin/sh/nino/core/timers/Scope.kt b/core/src/main/kotlin/sh/nino/core/timers/Scope.kt new file mode 100644 index 00000000..75204c02 --- /dev/null +++ b/core/src/main/kotlin/sh/nino/core/timers/Scope.kt @@ -0,0 +1,57 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.core.timers + +import gay.floof.utils.slf4j.logging +import kotlinx.coroutines.* +import sh.nino.core.NinoThreadFactory +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.Job as CoroutineJob + +/** + * The timer scope is a coroutine scope that uses a single-threaded executor pool, + * that it can be easily used with kotlinx.coroutines! + */ +internal class Scope: CoroutineScope { + private val executorPool: ExecutorService = Executors.newSingleThreadExecutor(NinoThreadFactory) + private val logger by logging() + + override val coroutineContext: CoroutineContext = SupervisorJob() + executorPool.asCoroutineDispatcher() + fun launch(job: Job): CoroutineJob { + return launch(start = CoroutineStart.LAZY) { + delay(job.interval.toLong()) + while (isActive) { + try { + job.execute() + } catch (e: Exception) { + logger.error("Unable to run job '${job.name}':", e) + } + + delay(job.interval.toLong()) + } + } + } +} diff --git a/database/build.gradle.kts b/database/build.gradle.kts new file mode 100644 index 00000000..e617d3aa --- /dev/null +++ b/database/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":core")) + api("net.perfectdreams.exposedpowerutils:postgres-power-utils:1.0.0") + api("org.jetbrains.exposed:exposed-kotlin-datetime") +} diff --git a/database/src/main/kotlin/sh/nino/database/AsyncTransaction.kt b/database/src/main/kotlin/sh/nino/database/AsyncTransaction.kt new file mode 100644 index 00000000..fa45a494 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/AsyncTransaction.kt @@ -0,0 +1,64 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database + +import io.sentry.Sentry +import io.sentry.kotlin.SentryContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.future.await +import kotlinx.coroutines.future.future +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.transactions.transaction +import sh.nino.core.NinoScope +import kotlin.coroutines.CoroutineContext + +/** + * Represents an asynchronous [transaction][Transaction]. + */ +class AsyncTransaction(val block: Transaction.() -> T) { + /** + * Executes the [block] that was passed down to be executed. This will report + * any errors towards Sentry if the coroutine has failed to execute. + * + * @return The represented object of this [transaction][AsyncTransaction] + */ + suspend fun execute(): T { + var coroutineContext: CoroutineContext = NinoScope.coroutineContext + + if (Sentry.isEnabled()) { + val newCtx = SentryContext() + coroutineContext + coroutineContext = newCtx + } + + return CoroutineScope(coroutineContext).future { + transaction { block() } + }.await() + } +} + +/** + * Creates a new [AsyncTransaction] and executes the [block]. If Sentry is enabled, + * this will report any errors to Sentry if the coroutine has failed. + */ +suspend fun asyncTransaction(block: Transaction.() -> T): T = AsyncTransaction(block).execute() diff --git a/database/src/main/kotlin/sh/nino/database/Enums.kt b/database/src/main/kotlin/sh/nino/database/Enums.kt new file mode 100644 index 00000000..f59538cf --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/Enums.kt @@ -0,0 +1,62 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database + +enum class GlobalBanType { + GUILD, + USER; +} + +enum class PunishmentType { + THREAD_MESSAGES_REMOVED, + THREAD_MESSAGES_ADDED, + WARNING_REMOVED, + VOICE_UNDEAFEN, + WARNING_ADDED, + VOICE_DEAFEN, + VOICE_UNMUTE, + VOICE_MUTE, + ROLE_REMOVE, + ROLE_ADD, + UNMUTE, + UNBAN, + MUTE, + KICK, + BAN; +} + +enum class LogEvent { + VOICE_MEMBER_DEAFEN, + VOICE_CHANNEL_SWITCH, + VOICE_CHANNEL_LEAVE, + VOICE_CHANNEL_JOIN, + VOICE_MEMBER_MUTED, + MESSAGE_UPDATED, + MESSAGE_DELETED, + MEMBER_UNBOOSTED, + MEMBER_BOOSTED, + THREAD_ARCHIVED, + THREAD_CREATED, + THREAD_DELETED; +} diff --git a/database/src/main/kotlin/sh/nino/database/columns/ArrayColumnType.kt b/database/src/main/kotlin/sh/nino/database/columns/ArrayColumnType.kt new file mode 100644 index 00000000..49b28289 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/columns/ArrayColumnType.kt @@ -0,0 +1,111 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.columns + +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.jdbc.JdbcConnectionImpl +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.postgresql.jdbc.PgArray + +fun Table.array(name: String, type: ColumnType): Column> = registerColumn(name, ArrayColumnType(type)) + +class ArrayColumnType(private val type: ColumnType): ColumnType() { + override fun sqlType(): String = "${type.sqlType()} ARRAY" + + override fun valueToDB(value: Any?): Any? = + if (value is Array<*>) { + val columnType = type.sqlType().split("(")[0] + val connection = (TransactionManager.current().connection as JdbcConnectionImpl).connection + connection.createArrayOf(columnType, value) + } else { + super.valueToDB(value) + } + + @Suppress("UNCHECKED_CAST") + override fun valueFromDB(value: Any): Array<*> { + if (value is PgArray) { + return if (type.sqlType().endsWith("Enum")) { + (value.array as Array<*>).filterNotNull().map { + type.valueFromDB(it) + }.toTypedArray() + } else { + value.array as Array<*> + } + } + + if (value is java.sql.Array) { + return if (type.sqlType().endsWith("Enum")) { + (value.array as Array<*>).filterNotNull().map { + type.valueFromDB(it) + }.toTypedArray() + } else { + value.array as Array<*> + } + } + + if (value is Array<*>) return value + + error("Unable to return an Array from a non-array value. ($value, ${value::class})") + } + + override fun notNullValueToDB(value: Any): Any { + if (value is Array<*>) { + if (value.isEmpty()) return "'{}'" + + val columnType = type.sqlType().split("(")[0] + val connection = (TransactionManager.current().connection as JdbcConnectionImpl).connection + return connection.createArrayOf(columnType, value) + } else { + return super.notNullValueToDB(value) + } + } +} + +private class ContainsOp(expr1: Expression<*>, expr2: Expression<*>): ComparisonOp(expr1, expr2, "@>") +infix fun ExpressionWithColumnType.contains(array: Array): Op = ContainsOp(this, QueryParameter(array, columnType)) + +class AnyOp(val expr1: Expression<*>, val expr2: Expression<*>): Op() { + override fun toQueryBuilder(queryBuilder: QueryBuilder) { + if (expr2 is OrOp) { + queryBuilder.append("(").append(expr2).append(")") + } else { + queryBuilder.append(expr2) + } + + queryBuilder.append(" = ANY (") + if (expr1 is OrOp) { + queryBuilder.append("(").append(expr1).append(")") + } else { + queryBuilder.append(expr1) + } + + queryBuilder.append(")") + } +} + +infix fun ExpressionWithColumnType.any(v: S): Op = if (v == null) { + IsNullOp(this) +} else { + AnyOp(this, QueryParameter(v, columnType)) +} diff --git a/database/src/main/kotlin/sh/nino/database/dao/SnowflakeTable.kt b/database/src/main/kotlin/sh/nino/database/dao/SnowflakeTable.kt new file mode 100644 index 00000000..3448710d --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/dao/SnowflakeTable.kt @@ -0,0 +1,33 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.dao + +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.sql.Column + +open class SnowflakeTable(name: String): IdTable(name) { + override val id: Column> = long("id").entityId() + override val primaryKey: PrimaryKey = PrimaryKey(id, name = "PK_$name") +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/AutomodEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/AutomodEntity.kt new file mode 100644 index 00000000..a1c868e0 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/AutomodEntity.kt @@ -0,0 +1,51 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.AutomodTable + +class AutomodEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(AutomodTable) + + var accountAgeThreshold by AutomodTable.accountAgeThreshold + var mentionThreshold by AutomodTable.mentionsThreshold + var blacklistedWords by AutomodTable.blacklistedWords + var omittedChannels by AutomodTable.omittedChannels + var omittedRoles by AutomodTable.omittedRoles + var omittedUsers by AutomodTable.omittedUsers + var messageLinks by AutomodTable.messageLinks + val accountAge by AutomodTable.accountAge + var dehoisting by AutomodTable.dehoisting + var shortlinks by AutomodTable.shortlinks + var blacklist by AutomodTable.blacklist + var toxicity by AutomodTable.toxicity + var phishing by AutomodTable.phishing + var mentions by AutomodTable.mentions + var invites by AutomodTable.invites + var spam by AutomodTable.spam + var raid by AutomodTable.raid +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/CaseEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/CaseEntity.kt new file mode 100644 index 00000000..eefb279c --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/CaseEntity.kt @@ -0,0 +1,45 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.CasesTable + +class CaseEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(CasesTable) + + var attachments by CasesTable.attachments + var moderatorId by CasesTable.moderatorId + var messageId by CasesTable.messageId + var createdAt by CasesTable.createdAt + var updatedAt by CasesTable.updatedAt + var victimId by CasesTable.victimId + var reason by CasesTable.reason + var index by CasesTable.index + var type by CasesTable.type + var soft by CasesTable.soft + var time by CasesTable.time +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/GlobalBanEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/GlobalBanEntity.kt new file mode 100644 index 00000000..553a6cf4 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/GlobalBanEntity.kt @@ -0,0 +1,38 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.GlobalBansTable + +class GlobalBanEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(GlobalBansTable) + + val createdAt by GlobalBansTable.createdAt + var reason by GlobalBansTable.reason + val issuer by GlobalBansTable.issuer + val type by GlobalBansTable.type +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/GuildEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/GuildEntity.kt new file mode 100644 index 00000000..896ec0fb --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/GuildEntity.kt @@ -0,0 +1,41 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.GuildsTable + +class GuildEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(GuildsTable) + + var usePlainModlogMessage by GuildsTable.usePlainModlogMessage + var modlogWebhookUri by GuildsTable.modlogWebhookUri + var noThreadsRoleId by GuildsTable.noThreadsRoleId + var modlogChannelId by GuildsTable.modlogChannelId + var mutedRoleId by GuildsTable.mutedRoleId + var language by GuildsTable.language + var prefixes by GuildsTable.prefixes +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/GuildTagEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/GuildTagEntity.kt new file mode 100644 index 00000000..fd3619c8 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/GuildTagEntity.kt @@ -0,0 +1,40 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.TagsTable + +class GuildTagEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(TagsTable) + + var tagExecutor by TagsTable.tagExecutor + var isComplex by TagsTable.isComplex + var updatedAt by TagsTable.updatedAt + val createdAt by TagsTable.createdAt + val author by TagsTable.author + var name by TagsTable.name +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/LoggingEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/LoggingEntity.kt new file mode 100644 index 00000000..aa38e45c --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/LoggingEntity.kt @@ -0,0 +1,40 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.LoggingTable + +class LoggingEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(LoggingTable) + + var ignoreChannels by LoggingTable.ignoreChannels + var ignoredUsers by LoggingTable.ignoredUsers + var ignoredRoles by LoggingTable.ignoredRoles + var channelId by LoggingTable.channelId + var enabled by LoggingTable.enabled + var events by LoggingTable.events +} diff --git a/database/src/main/kotlin/sh/nino/database/entities/PunishmentEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/PunishmentEntity.kt new file mode 100644 index 00000000..f1ac004d --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/PunishmentEntity.kt @@ -0,0 +1,39 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.PunishmentsTable + +class PunishmentEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(PunishmentsTable) + + val warnings by PunishmentsTable.warnings + val roleIds by PunishmentsTable.roleIds + var soft by PunishmentsTable.soft + var time by PunishmentsTable.time + var type by PunishmentsTable.type +} diff --git a/src/entities/WarningsEntity.ts b/database/src/main/kotlin/sh/nino/database/entities/UserEntity.kt similarity index 67% rename from src/entities/WarningsEntity.ts rename to database/src/main/kotlin/sh/nino/database/entities/UserEntity.kt index a0c0e247..f35091f4 100644 --- a/src/entities/WarningsEntity.ts +++ b/database/src/main/kotlin/sh/nino/database/entities/UserEntity.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +21,16 @@ * SOFTWARE. */ -import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm'; +package sh.nino.database.entities -@Entity({ name: 'warnings' }) -export default class WarningsEntity { - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.UsersTable - @Column({ default: undefined, nullable: true }) - public reason?: string; +class UserEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(UsersTable) - @Column({ default: 0 }) - public amount!: number; - - @Column({ name: 'user_id' }) - public userID!: string; - - @PrimaryGeneratedColumn() - public id!: number; + var prefixes by UsersTable.prefixes + var language by UsersTable.language } diff --git a/database/src/main/kotlin/sh/nino/database/entities/WarningEntity.kt b/database/src/main/kotlin/sh/nino/database/entities/WarningEntity.kt new file mode 100644 index 00000000..f79b0167 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/entities/WarningEntity.kt @@ -0,0 +1,39 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.entities + +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import sh.nino.database.tables.WarningsTable + +class WarningEntity(id: EntityID): LongEntity(id) { + companion object: LongEntityClass(WarningsTable) + + var receivedAt by WarningsTable.receivedAt + var expiresIn by WarningsTable.expiresIn + var reason by WarningsTable.reason + var guild by WarningsTable.guild + var amount by WarningsTable.amount +} diff --git a/database/src/main/kotlin/sh/nino/database/registerOrUpdateEnums.kt b/database/src/main/kotlin/sh/nino/database/registerOrUpdateEnums.kt new file mode 100644 index 00000000..7e00f38f --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/registerOrUpdateEnums.kt @@ -0,0 +1,46 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database + +import net.perfectdreams.exposedpowerutils.sql.createOrUpdatePostgreSQLEnum + +/** + * Registers or modifies the enums that is used in the database models. + */ +suspend fun registerOrUpdateEnums() { + asyncTransaction { + // GlobalBanType + createOrUpdatePostgreSQLEnum(GlobalBanType.values()) + } + + asyncTransaction { + // PunishmentType + createOrUpdatePostgreSQLEnum(PunishmentType.values()) + } + + asyncTransaction { + // LogEvent + createOrUpdatePostgreSQLEnum(LogEvent.values()) + } +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/AutomodTable.kt b/database/src/main/kotlin/sh/nino/database/tables/AutomodTable.kt new file mode 100644 index 00000000..ba7e696a --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/AutomodTable.kt @@ -0,0 +1,49 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import org.jetbrains.exposed.sql.LongColumnType +import org.jetbrains.exposed.sql.TextColumnType +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable + +object AutomodTable: SnowflakeTable("automod") { + var accountAgeThreshold = integer("account_age_threshold").default(4) + var mentionsThreshold = integer("mentions_threshold").default(4) + var blacklistedWords = array("blacklisted_words", TextColumnType()).default(arrayOf()) + var omittedChannels = array("omitted_channels", LongColumnType()).default(arrayOf()) + var omittedUsers = array("omitted_users", LongColumnType()).default(arrayOf()) + var omittedRoles = array("omitted_roles", LongColumnType()).default(arrayOf()) + var messageLinks = bool("message_links").default(false) + var accountAge = bool("account_age").default(false) + var dehoisting = bool("dehoisting").default(false) + var shortlinks = bool("shortlinks").default(false) + var blacklist = bool("blacklist").default(false) + var toxicity = bool("toxicity").default(false) + var phishing = bool("phishing").default(false) + var mentions = bool("mentions").default(false) + var invites = bool("invites").default(false) + var spam = bool("spam").default(false) + var raid = bool("raid").default(false) +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/CasesTable.kt b/database/src/main/kotlin/sh/nino/database/tables/CasesTable.kt new file mode 100644 index 00000000..9edc78da --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/CasesTable.kt @@ -0,0 +1,50 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import net.perfectdreams.exposedpowerutils.sql.postgresEnumeration +import org.jetbrains.exposed.sql.TextColumnType +import org.jetbrains.exposed.sql.kotlin.datetime.datetime +import sh.nino.database.PunishmentType +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable + +object CasesTable: SnowflakeTable("guild_cases") { + val attachments = array("attachments", TextColumnType()).default(arrayOf()) + val moderatorId = long("moderator_id") + val messageId = long("message_id").nullable() + val createdAt = datetime("created_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val updatedAt = datetime("updated_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val victimId = long("victim_id") + val reason = text("reason").nullable() + val index = integer("index").autoIncrement() + val soft = bool("soft").default(false) + val time = long("time").nullable().default(null) + val type = postgresEnumeration("type") + + override val primaryKey: PrimaryKey = PrimaryKey(id, index, name = "PK_GuildCases_ID") +} diff --git a/src/commands/core/InviteCommand.ts b/database/src/main/kotlin/sh/nino/database/tables/GlobalBansTable.kt similarity index 58% rename from src/commands/core/InviteCommand.ts rename to database/src/main/kotlin/sh/nino/database/tables/GlobalBansTable.kt index e62b0b4f..0a0c5dcd 100644 --- a/src/commands/core/InviteCommand.ts +++ b/database/src/main/kotlin/sh/nino/database/tables/GlobalBansTable.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,29 +21,19 @@ * SOFTWARE. */ -import { Command, CommandMessage } from '../../structures'; -import { Inject } from '@augu/lilith'; -import Discord from '../../components/Discord'; +package sh.nino.database.tables -export default class InviteCommand extends Command { - @Inject - private discord!: Discord; +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import net.perfectdreams.exposedpowerutils.sql.postgresEnumeration +import org.jetbrains.exposed.sql.kotlin.datetime.datetime +import sh.nino.database.GlobalBanType +import sh.nino.database.dao.SnowflakeTable - constructor() { - super({ - description: 'descriptions.invite', - aliases: ['inviteme', 'inv'], - cooldown: 2, - name: 'invite', - }); - } - - run(msg: CommandMessage) { - return msg.translate('commands.invite', [ - msg.author.tag, - `https://discord.com/oauth2/authorize?client_id=${this.discord.client.user.id}&scope=bot`, - 'https://discord.com/oauth2/authorize?client_id=613907896622907425&scope=bot', - 'https://discord.gg/ATmjFH9kMH', - ]); - } +object GlobalBansTable: SnowflakeTable("global_bans") { + val createdAt = datetime("created_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val reason = varchar("reason", 256).nullable() + var issuer = long("issuer_id") + val type = postgresEnumeration("type") } diff --git a/database/src/main/kotlin/sh/nino/database/tables/GuildsTable.kt b/database/src/main/kotlin/sh/nino/database/tables/GuildsTable.kt new file mode 100644 index 00000000..e5b229c2 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/GuildsTable.kt @@ -0,0 +1,38 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import org.jetbrains.exposed.sql.VarCharColumnType +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable + +object GuildsTable: SnowflakeTable("guilds") { + val usePlainModlogMessage = bool("use_plain_modlog_message").default(false) + val modlogWebhookUri = text("modlog_webhook_uri").nullable().default(null) + val noThreadsRoleId = long("no_threads_role_id").nullable().default(null) + val modlogChannelId = long("modlog_channel_id").nullable().default(null) + val mutedRoleId = long("muted_role_id").nullable().default(null) + val language = text("language").default("en_US") + val prefixes = array("prefixes", VarCharColumnType(25)).default(arrayOf()) +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/LoggingTable.kt b/database/src/main/kotlin/sh/nino/database/tables/LoggingTable.kt new file mode 100644 index 00000000..405fa150 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/LoggingTable.kt @@ -0,0 +1,50 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import net.perfectdreams.exposedpowerutils.sql.PGEnum +import org.jetbrains.exposed.sql.LongColumnType +import org.jetbrains.exposed.sql.StringColumnType +import sh.nino.database.LogEvent +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable +import kotlin.reflect.full.isSubclassOf + +object LoggingTable: SnowflakeTable("guild_logging") { + val ignoreChannels = array("ignored_channels", LongColumnType()).default(arrayOf()) + val ignoredUsers = array("ignored_users", LongColumnType()).default(arrayOf()) + val ignoredRoles = array("ignored_roles", LongColumnType()).default(arrayOf()) + val channelId = long("channel_id").nullable().default(null) + val enabled = bool("enabled").default(false) + val events = array( + "events", + object: StringColumnType() { + /** Returns the SQL type of this column. */ + override fun sqlType(): String = LogEvent::class.simpleName!!.lowercase() + override fun notNullValueToDB(value: Any): Any = PGEnum(LogEvent::class.simpleName!!.lowercase(), value as LogEvent) + override fun valueFromDB(value: Any): Any = + if (value::class.isSubclassOf(Enum::class)) value as LogEvent else enumValueOf(value as String) + } + ) +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/PunishmentsTable.kt b/database/src/main/kotlin/sh/nino/database/tables/PunishmentsTable.kt new file mode 100644 index 00000000..8763f1b3 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/PunishmentsTable.kt @@ -0,0 +1,38 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import net.perfectdreams.exposedpowerutils.sql.postgresEnumeration +import org.jetbrains.exposed.sql.LongColumnType +import sh.nino.database.PunishmentType +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable + +object PunishmentsTable: SnowflakeTable("guild_punishments") { + val warnings = integer("warnings").default(1) + val roleIds = array("role_ids", LongColumnType()) + val soft = bool("soft").nullable() + val time = long("time").nullable() + val type = postgresEnumeration("type") +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/TagsTable.kt b/database/src/main/kotlin/sh/nino/database/tables/TagsTable.kt new file mode 100644 index 00000000..00cf8c9c --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/TagsTable.kt @@ -0,0 +1,43 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.sql.kotlin.datetime.datetime +import sh.nino.database.dao.SnowflakeTable + +object TagsTable: SnowflakeTable("guild_tags") { + // `tagExecutor` can be two things: + // - if `isComplex` is true: It will refer to the path from the filesystem or S3 + // to the Ruby script. + // - else: It will embed the template here. + val tagExecutor = text("executor") + val isComplex = bool("is_complex").default(false) + val updatedAt = datetime("updated_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val createdAt = datetime("created_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val author = long("author") + val name = varchar("name", 32) +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/UsersTable.kt b/database/src/main/kotlin/sh/nino/database/tables/UsersTable.kt new file mode 100644 index 00000000..ad664a22 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/UsersTable.kt @@ -0,0 +1,33 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import org.jetbrains.exposed.sql.VarCharColumnType +import sh.nino.database.columns.array +import sh.nino.database.dao.SnowflakeTable + +object UsersTable: SnowflakeTable("users") { + val prefixes = array("prefixes", VarCharColumnType(25)).default(arrayOf()) + val language = text("language").default("en_US") +} diff --git a/database/src/main/kotlin/sh/nino/database/tables/WarningsTable.kt b/database/src/main/kotlin/sh/nino/database/tables/WarningsTable.kt new file mode 100644 index 00000000..3198a738 --- /dev/null +++ b/database/src/main/kotlin/sh/nino/database/tables/WarningsTable.kt @@ -0,0 +1,40 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.database.tables + +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.sql.kotlin.datetime.datetime +import sh.nino.database.dao.SnowflakeTable + +object WarningsTable: SnowflakeTable("warnings") { + val receivedAt = datetime("received_at").default(Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())) + val expiresIn = datetime("expires_in").nullable() + var reason = text("reason").nullable() + val guild = long("guild_id") + val amount = integer("amount").default(1) + + override val primaryKey: PrimaryKey = PrimaryKey(id, guild, name = "PK_Warnings") +} diff --git a/docker-compose.yml b/docker-compose.yml index 91d4fdfb..6ee72130 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,36 +1,35 @@ version: '3.8' services: - bot: + nino: + build: . container_name: nino restart: always - build: . depends_on: - - postgresql + - database - prometheus - timeouts + - cluster_operator - redis networks: - nino volumes: - - ./config.yml:/opt/Nino/config.yml:ro # Read-only - + - /run/media/noel/Storage/Projects/Nino/Nino/config.yml:/app/Nino/config.yml:ro redis: container_name: redis restart: always - image: redis:latest + image: redis ports: - - 6379:6379 + - "6379:6379" networks: - nino volumes: - redis:/data - - postgresql: - container_name: postgres + database: + container_name: database restart: always image: postgres:latest ports: - - 5432:5432 + - "5432:5432" networks: - nino volumes: @@ -38,8 +37,7 @@ services: environment: POSTGRES_USER: ${DATABASE_USERNAME} POSTGRES_PASSWORD: ${DATABASE_PASSWORD} - POSTGRES_DB: ${DATABASE_NAME} - + POSTGRES_DB: nino prometheus: container_name: prometheus build: ./docker/prometheus @@ -47,21 +45,29 @@ services: networks: - default +# cluster-operator: +# container_name: cluster-operator +# image: noelware/cluster-operator:81dd82c23ed00592de4293f6b222fcbad4948eee +# ports: +# - "3010:3010" +# networks: +# - nino +# volumes: +# - /run/media/noel/Storage/Projects/Nino/Nino/docker/cluster-operator/config.json:/app/config.json:ro + timeouts: container_name: timeouts restart: always image: docker.pkg.github.com/ninodiscord/timeouts/timeouts:latest ports: - - 4025:4025 + - "4025:4025" networks: - nino environment: AUTH: ${TIMEOUTS_AUTH} - volumes: redis: postgres: - networks: nino: internal: true diff --git a/docker/cluster-operator/Dockerfile b/docker/cluster-operator/Dockerfile new file mode 100644 index 00000000..f8a99895 --- /dev/null +++ b/docker/cluster-operator/Dockerfile @@ -0,0 +1,3 @@ +FROM noelware/cluster-operator:latest + +COPY config.json /app/config.json diff --git a/docker/cluster-operator/config.example.json b/docker/cluster-operator/config.example.json new file mode 100644 index 00000000..5641379f --- /dev/null +++ b/docker/cluster-operator/config.example.json @@ -0,0 +1,11 @@ +{ + "env": "Prod", + "clusters": 1, + "shards": 1, + "auth": "", + "webhook": "", + "metricsPrefix": "nino_", + "mergeMetrics": true, + "logEvents": false, + "exportDefaultMetrics": false +} diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 00000000..08036965 --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Copyright (c) 2019-2022 Nino +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +set -o errexit +set -o nounset +set -o pipefail + +. /app/noelware/nino/scripts/liblog.sh + +info "" +info " Welcome to the ${BOLD}Nino${RESET} container image!" +info " Subscribe to the project for updates: https://github.com/NinoDiscord/Nino" +info " Submit issues if any bugs occur: https://github.com/NinoDiscord/Nino/issues" +info "" + +exec "$@" diff --git a/docker/run.sh b/docker/run.sh new file mode 100644 index 00000000..d2754631 --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright (c) 2019-2022 Nino +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +set -o errexit +set -o nounset +set -o pipefail + +. /app/noelware/nino/scripts/liblog.sh + +info "*** Starting Nino! ***" +debug " => Custom Logback Location: ${NINO_CUSTOM_LOGBACK_FILE:-unknown}" +debug " => Using Custom Gateway: ${NINO_USE_GATEWAY:-false}" +debug " => Dedicated Node: ${WINTERFOX_DEDI_NODE:-none}" + +JAVA_OPTS=("-XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8") + +if [[ -z "${NINO_CUSTOM_LOGBACK_FILE:-}" ]] + JAVA_OPTS+=("-Dlogback.configurationFile=${NINO_CUSTOM_LOGBACK_FILE} ") + +if [[ -z "${WINTERFOX_DEDI_NODE:-}" ]] + JAVA_OPTS+=("-Pwinterfox.dediNode=${WINTERFOX_DEDI_NODE} ") + +JAVA_OPTS+=("$@") + +/app/noelware/nino/bot/bin/bot diff --git a/docker/scripts/liblog.sh b/docker/scripts/liblog.sh new file mode 100644 index 00000000..db93c131 --- /dev/null +++ b/docker/scripts/liblog.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Copyright (c) 2019-2022 Nino +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +BLUE='\033[38;2;81;81;140m' +GREEN='\033[38;2;165;204;165m' +PINK='\033[38;2;241;204;209m' +RESET='\033[0m' +BOLD='\033[1m' +UNDERLINE='\033[4m' + +info() { + timestamp=$(date +"%D ~ %r") + printf "%b\\n" "${GREEN}${BOLD}info${RESET} | ${PINK}${BOLD}${timestamp}${RESET} ~ $1" +} + +debug() { + local debug="${NINO_DEBUG:-false}" + shopt -s nocasematch + timestamp=$(date +"%D ~%r") + + if ! [[ "$debug" = "1" || "$debug" =~ ^(no|false)$ ]]; then + printf "%b\\n" "${BLUE}${BOLD}debug${RESET} | ${PINK}${BOLD}${timestamp}${RESET} ~ $1" + fi +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..80f20bf9 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,25 @@ +# ? Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +kotlin.code.style=official +org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.appname=Nino diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7454180f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..aa991fce --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..744e882e --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/locales/en_US.json b/locales/en_US.json index 63497331..3a21d830 100644 --- a/locales/en_US.json +++ b/locales/en_US.json @@ -2,146 +2,184 @@ "meta": { "contributors": ["239790360728043520"], "translator": "280158289667555328", - "aliases": ["en", "en_us", "english"], - "code": "en_US", - "full": "English (US)", - "flag": ":flag_us:" + "name": "English (United States)", + "flag": ":flag_us:", + "code": "en_US" }, "strings": { - "descriptions": { - "unknown": "This command doesn't have a description tied.", + "generic.enabled": "Enabled", + "generic.disabled": "Disabled", + "generic.lonely": "Heh... it's pretty empty, eh? I guess it's like a set of hearts, only one can be the chosen one! But, I shouldn't give you an extensional crisis right now. Go have fun! ...and maybe touch grass, you really seem to be lacking in that department x3", + "generic.nothing": "Nothing to show.", + "generic.yes": "Yes", + "generic.no": "No", - "help": "Returns a list of Nino's commands or documentation of a specific command or module.", - "invite": "Returns the invitation link to invite Nino into your guild! Also, support if you really need it.", - "locale": "Sets, resets, or views the current translations for Nino.", - "ping": "Returns the latency of anything that could cause me being... slighly un-usable...", - "shardinfo": "Returns shard information for Nino.", - "source": "Returns the source code URL of Nino, maybe to contribute or give a :star:?", - "statistics": "Returns nerd statistics related to Nino.", - "uptime": "How long I have been up for!", + "errors.ownerOnly": "You are not allowed to invoke the **${name}** command.", + "errors.missingPermsBot": "I am currently missing the following permissions: **${perms}**", + "errors.missingPermsUser": "You are currently missing the following permissions: **${perms}**", + "errors.cooldown": "You are currently on cooldown for command **${command}** for **${time}**.", + "errors.unknown.dev": [ + "Sorry! I was not able to execute the ${prefix} **${command}**! :<", + "If this keeps erroring, please report it to:", + "${owners}", + "", + "Since you are in development mode, the stacktrace can be seen below", + "and in the console!", + "", + "```kotlin", + "${stacktrace}", + "```" + ], - "ban": "Bans a user from this guild or outside the guild.", - "case": "Returns a mini embed of the case you searched up.", - "kick": ":boot: Kicks a member from this guild.", - "mute": "Mutes a member from any textable channel.", - "pardon": "Removes one or multiple warnings from a user in this guild.", - "purge": "Purges a certain amount of messages by user(s), system account, bot(s), or specific messages.", - "reason": "Changes the reason of a specific case.", - "softban": "Softly bans a user from this guild.", - "timeouts": "Returns a list of timeouts based on the type of action.", - "unban": "Unbans a user from this guild.", - "unmute": "Unmutes a user from this guild.", - "warn": "Warns a specific user.", - "warnings": "Shows a list of warnings from a specific user.", - "voice_mute": "Mutes a person in the voice channel you're in.", - "voice_deafen": "Deafens a person in the voice channel you're in.", - "voice_undeafen": "Undeafens a person in the voice channel you're in.", - "voice_unmute": "Unmutes a person in the voice channel you're in.", - "voice_kick": "Kicks any member(s) or bot(s) in a voice channel you're in.", + "errors.unknown.prod": [ + "Sorry! I was not able to execute the ${prefix} **${command}**! :<", + "If this keeps erroring, please report it to:", + "${owners}", + "on the Noelware Discord server under the <#824071651486335036> channel:", + "> https://discord.gg/ATmjFH9kMH" + ], - "automod": "Enables, disables, or views any automod utility available.", - "logging": "Enables or disable the Logging feature.", - "modlog": "Set or reset the mod log channel.", - "muted_role": "Sets or resets the Muted role.", - "prefix": "View, change, or reset a guild or user prefix.", - "punishments": "View, change, or remove a guild punishment.", - "settings": "Views a list of all the settings in this guild." - }, - "commands": { - "help": { - "embed": { - "title": "%s | Commands List", - "description": [ - ":pencil2: **For more documentation, you can type `%shelp ` with `` being the command or module in this list.**", - "", - "More information and a prettier UI for commands or modules can be viewed on the [website](https://nino.floofy.dev).", - "There are currently **%d** available commands." - ], - "fields": { - "moderation": "• Moderation [%d]", - "core": "• Core [%d]", - "settings": "• Settings [%d]" - } - }, - "command": { - "not_found": ":question: Command or module **%s** was not found.", - "embed": { - "title": "[ :pencil2: Command \"%s\" ]", - "description": "> **%s**", - "fields": { - "syntax": "• Syntax", - "category": "• Category", - "aliases": "• Aliases", - "owner_only": "• Owner Only", - "cooldown": "• Cooldown", - "user_perms": "• Required User Permissions", - "bot_perms": "• Required Bot Permissions", - "examples": "• Examples" - } - } - }, - "module": { - "embed": { - "title": "[ ·̩̩̥͙**•̩̩͙✩•̩̩͙* Module \"%s\" ˚*•̩̩͙✩•̩̩͙*˚*·̩̩̥͙ ]" - } - }, - "usage_title": "Command Usage", - "usage": [ - "So, if you're not familar with the command syntax, here's a breakdown:", - "", - "> A simple command might look like: `%shelp [cmdOrMod | \"usage\"]`", - "", - "```", - "x! help [cmdOrMod | \"usage\"]", - "^ ^ ^ ^ ^", - "| | | | |", - "| | | | |", - "| | | | |", - "prefix cmd parameter(s) \"or\" literal", - "```", - "", - "Parameters are easy to understand. If they are \"piped\" with a `|`, it means it's a \"or\", or what you can provide. If they are a literal", - "parameter, it means if you typed \"usage\" out, it'll print something else.", - "", - "- A parameter wrapped in `[]` means it's optional, but you can add additional arguments to make it run something else", - "- A parameter wrapped in `<>` means it's required, which means *you* have to add that argument to make the command perform correctly.", - "", - ":question: **Still stuck? There is always examples in the command's short overview to show how you can run that specific command.**" - ] - }, - "invite": [ - ":wave: Heyo **%s**! You want to invite me into your own server? That's pretty cool if I do say so myself~", - "You can do so by clicking this link: **%s**", - "", - "If you want to see bleeding edge features, you can also invite the beta instance:", - "**%s**", - "", - "Need any support? Join the **Noelware** Discord server under <#824071651486335036> to get help with me!", - "%s" - ] - }, - "generic": {}, - "automod": { - "blacklist": "Hey, I don't think you should say that here! (☍﹏⁰)。", - "invites": "Hey! You're not allowed to post server links here... o(╥﹏╥)o", - "mentions": "Uhm, I don't think that'll get their attention... (;¬_¬)", - "shortlinks": "You shouldn't send that kind of link here... (⁎˃ᆺ˂)", - "spam": "I don't think spamming will get you anywhere... o(╥﹏╥)o", - "raid": { - "locked": "Hey all! I decided to close off all channels you had access in for ~10 seconds due to someone being rude and raiding! ૮( ᵒ̌▱๋ᵒ̌ )ა", - "unlocked": "Heyo! I restored all channels, I hope there is no more raiders.. :3." - } - }, - "errors": { - "blacklists": { - "guild": [ - "Guild **${guild}** is blacklisted from using me by **${user}**", - "> **${reason}**", - "", - ":question: If there was an issue or a misunderstanding in the blacklist, contact us here:" - ], - "user": [] - } - } + "descriptions.admin.automod": "Command to update your automod settings!", + "descriptions.automod.messageLinks": "Toggles the Message Links automod -- read more [here](https://nino.sh/docs/automod/message-links)", + "descriptions.automod.accountAge": "Toggles the Account Age automod or sets a threshold -- read more [here](https://nino.sh/docs/automod/account-age)", + "descriptions.automod.dehoist": "Toggles the Dehoisting automod -- read more [here](https://nino.sh/docs/automod/dehoisting)", + "descriptions.automod.blacklist": "Toggles the Blacklist automod, or add/remove/list blacklisted words. -- read more [here](https://nino.sh/docs/automod/blacklist)", + "descriptions.automod.phishing": "Toggles the Phishing Links automod -- read more [here](https://nino.sh/docs/automod/phishing)", + "descriptions.automod.toxicity": "Toggles the Toxicity automod -- read more [here](https://nino.sh/docs/automod/toxicity)", + "descriptions.automod.spam": "Toggles the Spam automod -- read more [here](https://nino.sh/docs/automod/spam)", + "descriptions.automod.raid": "Toggles the Raid automod -- read more [here](https://nino.sh/docs/automod/raid)", + "descriptions.automod.invites": "Toggles the Invites automod -- read more [here](https://nino.sh/docs/automod/invites)", + "descriptions.automod.mentions": "Toggles the Mentions automod, or sets a mention threshold. -- read more [here](https://nino.sh/docs/automod/mentions)", + "descriptions.admin.export": "Exports your guild settings to be easily imported later on.", + "descriptions.admin.import": "Import your exported guild settings easily. You can't revert back once you import back.", + "descriptions.admin.logging": "Administrate your logging settings here!", + "descriptions.logging.omitUsers": "Add or remove users to omit from being logged in the specified log channel.", + "descriptions.logging.omitChannels": "Add or remove text channels to omit from being logged in the specified log channel.", + "descriptions.logging.config": "Shows the current logging configuration", + "descriptions.logging.events": "Toggle specific logging events -- read more [here](https://nino.sh/docs/logging)", + "descriptions.prefix": "Creates, views, and deletes the guild or your prefixes.", + "descriptions.prefix.set": "Adds a prefix to the list of prefixes that you or the guild can use, depends on your desire!", + "descriptions.prefix.delete": "Removes a prefix from the list of prefixes that you or the guild doesn't like, but it doesn't hurt to delete any... right?", + "descriptions.core.help": "Returns documentation on a command or module, or a list of commands you can use.", + "descriptions.core.invite": "Returns the link to invite me to your guild, master!", + "descriptions.core.ping": "Returns the latency from Discord to us", + "descriptions.core.shardinfo": "Returns dedicated information about all shards available.", + "descriptions.core.source": "Returns the link to Nino's source code", + "descriptions.core.statistics": "Returns miscellanous statistics about Nino, useless or not!", + "descriptions.core.uptime": "Returns how long Nino has been up.", + + "commands.automod.toggle": "${emoji} ${toggle} the **${name}** automod!", + "commands.admin.logging.toggle": "${emoji} ${toggle} the **Logging** feature.", + "commands.admin.logging.omitUsers.embed.title": "[ Omitted Users ]", + "commands.admin.logging.omitUsers.embed.description": [ + "Here is a list of users that are omitted from being logged:", + "", + "${list}" + ], + "commands.admin.logging.omitUsers.add.missingArgs": "You are missing users to omit from logging! You can use one or more mentions or user IDs.", + "commands.admin.logging.omitUsers.404": "I was unable to find the users to omit. Did you specify a mention or user ID?", + "commands.admin.logging.omitUsers.success": "${operation} **${users}** user${suffix}!", + "commands.admin.logging.omitUsers.del.missingArgs": "Missing user(s) to remove from the omitted list.", + "commands.admin.logging.omitChannels.404": "I was unable to find the channels to omit. Did you specify a mention or channel ID? Did you specify only voice channels?", + "commands.admin.logging.omitChannels.add.missingArgs": "You are missing text channels to omit from logging! You can use one or more mentions or channel IDs.", + "commands.admin.logging.omitChannels.success": "${operation} **${users}** text channel${suffix}!", + "commands.admin.logging.omitChannels.del.missingArgs": "Missing channel(s) to remove from the omitted list.", + "commands.admin.logging.omitChannels.embed.title": "[ Omitted Users ]", + "commands.admin.logging.omitChannels.embed.description": [ + "Here is a list of text channels that are omitted from being logged:", + "", + "${list}" + ], + "commands.admin.logging.success": "${emoji} Successfully set the logging channel to ${channel}!", + "commands.admin.logging.invalid": "Argument **${arg}** was not a valid channel mention or ID.", + "commands.admin.logging.config.message": [ + "```md", + "# Logging Configuration for ${name}", + "", + "• Enabled: ${enabled}", + "• Channel: ${channel}", + "```" + ], + "commands.admin.logging.events.list.embed.title": "Enabled Events for ${name}", + "commands.admin.logging.events.list.embed.description": [ + "This is a list of logging events that are enabled or disabled.", + "Read our [Terms of Service](https://nino.sh/terms) & [Privacy Policy](https://nino.sh/privacy) before enabling!", + "", + "${list}" + ], + + "commands.admin.prefix.user.list": [ + "```md", + "# Prefixes for ${user}", + "${list}", + "", + "## Note", + "> You can also use the following default prefixes as well:", + "", + "${prefixes}", + "```" + ], + + "commands.admin.prefix.guild.list": [ + "```md", + "# Prefixes for ${name}", + "${list}", + "", + "## Note", + "> You can also use the following default prefixes as well:", + "", + "${prefixes}", + "```" + ], + + "commands.admin.prefix.set.noPermission": "You are currently missing the **Manage Guild** permission to modify guild prefixes.", + "commands.admin.prefix.set.missingPrefix": "You are missing a prefix to set. You can use `\"` to use spaces in your prefixes. (example: `\"nino is the best\"` -> `nino is the best help`)", + "commands.admin.prefix.set.maxLengthExceeded": "You went **${chars.over}** characters over the limit with the prefix you supplied: ${prefix}", + "commands.admin.prefix.set.alreadySet": "Prefix ${prefix} is already available!", + "commands.admin.prefix.set.available": "Prefix ${prefix} is now available. :3", + "commands.admin.prefix.reset.alreadyRemoved": "Prefix ${prefix} is already unavailable...", + "commands.admin.prefix.reset.unavailable": "Prefix ${prefix} is now unavailable, if you want to add it back, then say so!", + "commands.admin.prefix.reset.guild.embed.title": "[ Prefixes for guild ${name} ]", + "commands.admin.prefix.reset.guild.embed.description": [ + "Hello again! I am here to remind you that you are missing a prefix to remove.", + "But don't worry, I will list them again for you... don't expect this to happen again!", + "", + "Just specify the prefix below to remove it (example):", + "> prefix reset \"nino is the best\"", + "", + "```md", + "${prefixes}", + "```" + ], + + "commands.admin.prefix.reset.user.embed.title": "[ Prefixes for ${name} ]", + "commands.admin.prefix.reset.user.embed.description": [ + "Hello again! I am here to remind you that you are missing a prefix to remove.", + "But don't worry, I will list them again for you... don't expect this to happen again!", + "", + "Just specify the prefix below to remove it (example):", + "> prefix reset \"nino is the best\"", + "", + "```md", + "${prefixes}", + "```" + ], + + "commands.admin.rolecfg.list": [ + "```md", + "# Role Configuration for ${guild}", + "• No Threads Role: ${noThreadsRole}", + "• Muted Role: ${mutedRole}", + "```" + ], + + "commands.admin.rolecfg.muted.noArgs": "You must provide a role ID to set a Muted role or you can use the reset subcommand to reset it.", + "commands.admin.rolecfg.muted.noMutedRole": "Cannot reset muted role due to one being reset or not configured.", + "commands.admin.rolecfg.muted.set.success": "Successfully set the Muted role to **${name}**", + "commands.admin.rolecfg.muted.reset.success": "Successfully reset the Muted role.", + "commands.admin.rolecfg.noThreads.noArgs": "You must provide a role ID to set a Muted role or you can use the reset subcommand to reset it.", + "commands.admin.rolecfg.noThreads.noRoleId": "Cannot reset No Threads role due to one being reset or not configured.", + "commands.admin.rolecfg.noThreads.set.success": "Successfully set the No Threads role to **${name}**", + "commands.admin.rolecfg.noThreads.reset.success": "Successfully reset the No Threads role." } } diff --git a/locales/fr_FR.json b/locales/fr_FR.json deleted file mode 100644 index 0b8db6dd..00000000 --- a/locales/fr_FR.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "meta": { - "contributors": [], - "translator": "424921636492279808", - "aliases": ["french", "francias", "françias", "fr"], - "code": "fr_FR", - "full": "Françias (FR)", - "flag": ":flag_fr:" - }, - "strings": { - "descriptions": { - "help": "Returns a list of Nino's commands or documentation of a specific command or module.", - "invite": "Returns the invitation link to invite Nino into your guild! Also, support if you really need it.", - "locale": "Sets, resets, or views the current translations for Nino.", - "ping": "Returns the latency of anything that could cause me being... slighly un-usable...", - "shardinfo": "Returns shard information for Nino.", - "source": "Returns the source code URL of Nino, maybe to contribute or give a :star:?", - "statistics": "Returns nerd statistics related to Nino.", - "uptime": "How long I have been up for!", - - "ban": "Bans a user from this guild or outside the guild.", - "case": "Returns a mini embed of the case you searched up.", - "kick": ":boot: Kicks a member from this guild.", - "mute": "Mutes a member from any textable channel.", - "pardon": "Removes one or multiple warnings from a user in this guild.", - "purge": "Purges a certain amount of messages by user(s), system account, bot(s), or specific messages.", - "reason": "Changes the reason of a specific case.", - "softban": "Softly bans a user from this guild.", - "timeouts": "Returns a list of timeouts based on the type of action.", - "unban": "Unbans a user from this guild.", - "unmute": "Unmutes a user from this guild.", - "warn": "Warns a specific user.", - "warnings": "Shows a list of warnings from a specific user.", - "voice_mute": "Mutes a person in the voice channel you're in.", - "voice_deafen": "Deafens a person in the voice channel you're in.", - "voice_undeafen": "Undeafens a person in the voice channel you're in.", - "voice_unmute": "Unmutes a person in the voice channel you're in.", - "voice_kick": "Kicks any member(s) or bot(s) in a voice channel you're in.", - - "automod": "Enables, disables, or views any automod utility available.", - "logging": "Enables or disable the Logging feature.", - "modlog": "Set or reset the mod log channel.", - "muted_role": "Sets or resets the Muted role.", - "prefix": "View, change, or reset a guild or user prefix.", - "punishments": "View, change, or remove a guild punishment.", - "settings": "Views a list of all the settings in this guild." - }, - "commands": { - "help": { - "embed": { - "title": "%s | Commands List", - "description": [ - ":pencil2: **For more documentation, you can type `%shelp ` with `` being the command or module in this list.**", - "", - "More information and a prettier UI for commands or modules can be viewed on the [website](https://nino.floofy.dev).", - "There are currently **%d** available commands." - ], - "fields": { - "moderation": "• Moderation [%d]", - "core": "• Core [%d]", - "settings": "• Settings [%d]" - } - }, - "command": { - "not_found": ":question: Command or module **%s** was not found.", - "embed": { - "title": "[ :pencil2: Command \"%s\" ]", - "description": "> **%s**", - "fields": { - "syntax": "• Syntax", - "category": "• Category", - "aliases": "• Aliases", - "owner_only": "• Owner Only", - "cooldown": "• Cooldown", - "user_perms": "• Required User Permissions", - "bot_perms": "• Required Bot Permissions", - "examples": "• Examples" - } - } - }, - "module": { - "embed": { - "title": "[ ·̩̩̥͙**•̩̩͙✩•̩̩͙* Module \"%s\" ˚*•̩̩͙✩•̩̩͙*˚*·̩̩̥͙ ]" - } - }, - "usage_title": "Command Usage", - "usage": [ - "So, if you're not familar with the command syntax, here's a breakdown:", - "", - "> A simple command might look like: `%shelp [cmdOrMod | \"usage\"]`", - "", - "```", - "x! help [cmdOrMod | \"usage\"]", - "^ ^ ^ ^ ^", - "| | | | |", - "| | | | |", - "| | | | |", - "prefix cmd parameter(s) \"or\" literal", - "```", - "", - "Parameters are easy to understand. If they are \"piped\" with a `|`, it means it's a \"or\", or what you can provide. If they are a literal", - "parameter, it means if you typed \"usage\" out, it'll print something else.", - "", - "- A parameter wrapped in `[]` means it's optional, but you can add additional arguments to make it run something else", - "- A parameter wrapped in `<>` means it's required, which means *you* have to add that argument to make the command perform correctly.", - "", - ":question: **Still stuck? There is always examples in the command's short overview to show how you can run that specific command.**" - ] - } - }, - "generic": {}, - "automod": { - "blacklist": "Hey, I don't think you should say that here! (☍﹏⁰)。", - "invites": "Hey! You're not allowed to post server links here... o(╥﹏╥)o", - "mentions": "Uhm, I don't think that'll get their attention... (;¬_¬)", - "shortlinks": "You shouldn't send that kind of link here... (⁎˃ᆺ˂)", - "spam": "I don't think spamming will get you anywhere... o(╥﹏╥)o", - "raid": { - "locked": "Hey all! I decided to close off all channels you had access in for ~10 seconds due to someone being rude and raiding! ૮( ᵒ̌▱๋ᵒ̌ )ა", - "unlocked": "Heyo! I restored all channels, I hope there is no more raiders.. :3." - } - }, - "errors": { - "blacklists": { - "guild": [ - "Guild **${guild}** is blacklisted from using me by **${user}**", - "> **${reason}**", - "", - ":question: If there was an issue or a misunderstanding in the blacklist, contact us here:" - ], - "user": [] - } - } - } -} diff --git a/locales/pt_BR.json b/locales/pt_BR.json deleted file mode 100644 index 92a2af0f..00000000 --- a/locales/pt_BR.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "meta": { - "contributors": [], - "translator": "197448151064379393", - "aliases": ["port", "portuges", "português"], - "code": "pt_BR", - "full": "Português (BR)", - "flag": ":flag_br:" - }, - "strings": { - "descriptions": { - "help": "Returns a list of Nino's commands or documentation of a specific command or module.", - "invite": "Returns the invitation link to invite Nino into your guild! Also, support if you really need it.", - "locale": "Sets, resets, or views the current translations for Nino.", - "ping": "Returns the latency of anything that could cause me being... slighly un-usable...", - "shardinfo": "Returns shard information for Nino.", - "source": "Returns the source code URL of Nino, maybe to contribute or give a :star:?", - "statistics": "Returns nerd statistics related to Nino.", - "uptime": "How long I have been up for!", - - "ban": "Bans a user from this guild or outside the guild.", - "case": "Returns a mini embed of the case you searched up.", - "kick": ":boot: Kicks a member from this guild.", - "mute": "Mutes a member from any textable channel.", - "pardon": "Removes one or multiple warnings from a user in this guild.", - "purge": "Purges a certain amount of messages by user(s), system account, bot(s), or specific messages.", - "reason": "Changes the reason of a specific case.", - "softban": "Softly bans a user from this guild.", - "timeouts": "Returns a list of timeouts based on the type of action.", - "unban": "Unbans a user from this guild.", - "unmute": "Unmutes a user from this guild.", - "warn": "Warns a specific user.", - "warnings": "Shows a list of warnings from a specific user.", - "voice_mute": "Mutes a person in the voice channel you're in.", - "voice_deafen": "Deafens a person in the voice channel you're in.", - "voice_undeafen": "Undeafens a person in the voice channel you're in.", - "voice_unmute": "Unmutes a person in the voice channel you're in.", - "voice_kick": "Kicks any member(s) or bot(s) in a voice channel you're in.", - - "automod": "Enables, disables, or views any automod utility available.", - "logging": "Enables or disable the Logging feature.", - "modlog": "Set or reset the mod log channel.", - "muted_role": "Sets or resets the Muted role.", - "prefix": "View, change, or reset a guild or user prefix.", - "punishments": "View, change, or remove a guild punishment.", - "settings": "Views a list of all the settings in this guild." - }, - "commands": { - "help": { - "embed": { - "title": "%s | Commands List", - "description": [ - ":pencil2: **For more documentation, you can type `%shelp ` with `` being the command or module in this list.**", - "", - "More information and a prettier UI for commands or modules can be viewed on the [website](https://nino.floofy.dev).", - "There are currently **%d** available commands." - ], - "fields": { - "moderation": "• Moderation [%d]", - "core": "• Core [%d]", - "settings": "• Settings [%d]" - } - }, - "command": { - "not_found": ":question: Command or module **%s** was not found.", - "embed": { - "title": "[ :pencil2: Command \"%s\" ]", - "description": "> **%s**", - "fields": { - "syntax": "• Syntax", - "category": "• Category", - "aliases": "• Aliases", - "owner_only": "• Owner Only", - "cooldown": "• Cooldown", - "user_perms": "• Required User Permissions", - "bot_perms": "• Required Bot Permissions", - "examples": "• Examples" - } - } - }, - "module": { - "embed": { - "title": "[ ·̩̩̥͙**•̩̩͙✩•̩̩͙* Module \"%s\" ˚*•̩̩͙✩•̩̩͙*˚*·̩̩̥͙ ]" - } - }, - "usage_title": "Command Usage", - "usage": [ - "So, if you're not familar with the command syntax, here's a breakdown:", - "", - "> A simple command might look like: `%shelp [cmdOrMod | \"usage\"]`", - "", - "```", - "x! help [cmdOrMod | \"usage\"]", - "^ ^ ^ ^ ^", - "| | | | |", - "| | | | |", - "| | | | |", - "prefix cmd parameter(s) \"or\" literal", - "```", - "", - "Parameters are easy to understand. If they are \"piped\" with a `|`, it means it's a \"or\", or what you can provide. If they are a literal", - "parameter, it means if you typed \"usage\" out, it'll print something else.", - "", - "- A parameter wrapped in `[]` means it's optional, but you can add additional arguments to make it run something else", - "- A parameter wrapped in `<>` means it's required, which means *you* have to add that argument to make the command perform correctly.", - "", - ":question: **Still stuck? There is always examples in the command's short overview to show how you can run that specific command.**" - ] - } - }, - "generic": {}, - "automod": { - "blacklist": "Hey, I don't think you should say that here! (☍﹏⁰)。", - "invites": "Hey! You're not allowed to post server links here... o(╥﹏╥)o", - "mentions": "Uhm, I don't think that'll get their attention... (;¬_¬)", - "shortlinks": "You shouldn't send that kind of link here... (⁎˃ᆺ˂)", - "spam": "I don't think spamming will get you anywhere... o(╥﹏╥)o", - "raid": { - "locked": "Hey all! I decided to close off all channels you had access in for ~10 seconds due to someone being rude and raiding! ૮( ᵒ̌▱๋ᵒ̌ )ა", - "unlocked": "Heyo! I restored all channels, I hope there is no more raiders.. :3." - } - }, - "errors": { - "blacklists": { - "guild": [ - "Guild **${guild}** is blacklisted from using me by **${user}**", - "> **${reason}**", - "", - ":question: If there was an issue or a misunderstanding in the blacklist, contact us here:" - ], - "user": [] - } - } - } -} diff --git a/modules/README.md b/modules/README.md new file mode 100644 index 00000000..6e44c4b2 --- /dev/null +++ b/modules/README.md @@ -0,0 +1,9 @@ +# Nino Modules +This is a collection of modules that is used by multiple subprojects at once. + +## Modules +- `:modules:localisation` - Localisation module for slash and legacy text-based commands. +- `:modules:punishments` - The core punishments module. +- `:modules:scripting` - Scripting module to execute Ruby and/or Kotlin scripts for guild policies and tags. +- `:modules:timeouts` - Timeouts module to connect to the [Timeouts microservice](https://github.com/NinoDiscord/timeouts) +- `:modules` - This is core subproject, where each module is packaged with metadata. diff --git a/modules/build.gradle.kts b/modules/build.gradle.kts new file mode 100644 index 00000000..bfc0a556 --- /dev/null +++ b/modules/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} diff --git a/modules/localisation/build.gradle.kts b/modules/localisation/build.gradle.kts new file mode 100644 index 00000000..f04d81eb --- /dev/null +++ b/modules/localisation/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation("com.charleskorn.kaml:kaml:0.43.0") + implementation(project(":modules")) +} diff --git a/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/Locale.kt b/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/Locale.kt new file mode 100644 index 00000000..c1417787 --- /dev/null +++ b/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/Locale.kt @@ -0,0 +1,73 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.localisation + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import sh.nino.commons.StringOrList +import java.io.File +import java.util.regex.Pattern + +/** + * Represents the metadata of a [Locale] object. + * + * @param contributors A list of contributors by their ID that contributed to this language + * @param translator The translator's ID that translated this language. + * @param aliases A list of aliases when setting this [Locale]. + * @param code The IANA code that is used for this [Locale]. + * @param flag The flag emoji (i.e, `:flag_us:`) for presentation purposes. + * @param name The locale's full name. + */ +@Serializable +data class LocalizationMeta( + val contributors: List = listOf(), + val translator: String, + val aliases: List = listOf(), + val code: String, + val flag: String, + val name: String +) + +private val KEY_REGEX = Pattern.compile("[\$]\\{([\\w\\.]+)\\}").toRegex() + +@Serializable +data class Locale( + val meta: LocalizationMeta, + val strings: Map +) { + companion object { + fun fromFile(file: File, json: Json): Locale { + return json.decodeFromString(serializer(), file.readText()) + } + } + + fun translate(key: String, args: Map = mapOf()): String { + val format = strings[key] ?: error("Key \"$key\" was not found.") + val stringsToTranslate = format.asListOrNull?.joinToString("\n") ?: format.asString + + return KEY_REGEX.replace(stringsToTranslate, transform = { + args[it.groups[1]!!.value].toString() + }) + } +} diff --git a/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/LocalisationModule.kt b/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/LocalisationModule.kt new file mode 100644 index 00000000..e3cc6a25 --- /dev/null +++ b/modules/localisation/src/main/kotlin/sh/nino/modules/localisation/LocalisationModule.kt @@ -0,0 +1,86 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.localisation + +import gay.floof.utils.slf4j.logging +import kotlinx.serialization.json.Json +import sh.nino.modules.annotations.Action +import sh.nino.modules.annotations.ModuleMeta +import java.io.File + +@ModuleMeta("localisation", "Implements localisation to Nino", version = "2.0.0") +class LocalisationModule(private val configDefaultLocale: String, private val json: Json) { + private val localeDirectory = File("./locales") + private lateinit var defaultLocale: Locale + private val log by logging() + lateinit var locales: Map + + @Action + @Suppress("UNUSED") + fun onInit() { + log.info("Finding locales in ${localeDirectory.path}...") + if (!localeDirectory.exists()) + throw IllegalStateException("Localisation path doesn't exist in '${localeDirectory.path}'!") + + val files = localeDirectory.listFiles { _, s -> s.endsWith(".json") } ?: arrayOf() + val found = mutableMapOf() + + for (file in files) { + val locale = Locale.fromFile(file, json) + log.info("Found language ${locale.meta.code} by ${locale.meta.translator}!") + found[locale.meta.code] = locale + + if (locale.meta.code == configDefaultLocale) { + log.info("Default language was set to $configDefaultLocale and it was found.") + defaultLocale = locale + } + } + + if (!::defaultLocale.isInitialized) { + log.warn("Couldn't find the language $configDefaultLocale, setting to English (US)!") + defaultLocale = found["en_US"]!! + } + + locales = found.toMap() + } + + fun getLocale(guild: String, user: String): Locale { + // This should never happen, but it could happen. + if (!locales.containsKey(guild) || !locales.containsKey(user)) return defaultLocale + + // If both parties use the default locale, return it. + if (guild == defaultLocale.meta.code && user == defaultLocale.meta.code) return defaultLocale + + // Users have more priority than guilds, so let's check if the guild locale + // is the default and the user's locale is completely different + if (user != defaultLocale.meta.code && guild == defaultLocale.meta.code) return locales[user]!! + + // If the user's locale is not the guild's locale, return it, + // so it can be translated properly. + if (guild !== defaultLocale.meta.code && user == defaultLocale.meta.code) return locales[guild]!! + + // We should never be here, but here we are. + error("Illegal unknown value (locale: guild->$guild;user->$user)") + } +} diff --git a/modules/metrics/build.gradle.kts b/modules/metrics/build.gradle.kts new file mode 100644 index 00000000..023c71ca --- /dev/null +++ b/modules/metrics/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules")) + api("io.prometheus:simpleclient_hotspot:0.15.0") + api("io.prometheus:simpleclient:0.15.0") +} diff --git a/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricType.kt b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricType.kt new file mode 100644 index 00000000..bd4aa00a --- /dev/null +++ b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricType.kt @@ -0,0 +1,34 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.metrics + +enum class MetricType { + API_REQUEST_LATENCY, + COMMANDS_EXECUTED, + API_REQUEST_COUNT, + COMMAND_LATENCY, + MESSAGES_SEEN, + GATEWAY_LATENCY, + GUILD_COUNT; +} diff --git a/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricsModule.kt b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricsModule.kt new file mode 100644 index 00000000..8d13ef70 --- /dev/null +++ b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/MetricsModule.kt @@ -0,0 +1,115 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.metrics + +import io.prometheus.client.CollectorRegistry +import io.prometheus.client.Counter +import io.prometheus.client.Gauge +import io.prometheus.client.Histogram +import io.prometheus.client.hotspot.DefaultExports +import sh.nino.modules.annotations.Action +import sh.nino.modules.annotations.ModuleMeta + +@ModuleMeta("metrics", "Enables the use of Prometheus to scrape metrics out of the bot.", version = "2.0.0") +class MetricsModule(val enabled: Boolean, val apiEnabled: Boolean) { + lateinit var apiRequestLatency: Histogram + lateinit var commandsExecuted: Counter + lateinit var apiRequestCount: Gauge + lateinit var websocketEvents: Counter + lateinit var commandLatency: Histogram + lateinit var messagesSeen: Counter + lateinit var gatewayLatency: Histogram + lateinit var gatewayPing: Histogram + lateinit var guildCount: Gauge + lateinit var registry: CollectorRegistry + lateinit var users: Gauge + + @Action + @Suppress("UNUSED") + fun onInit() { + if (enabled) { + // Use a custom registry uwu + registry = CollectorRegistry() + + // Export JVM metrics cuz cool and good + DefaultExports.register(registry) + + commandsExecuted = Counter.build() + .name("nino_commands_executed") + .help("Returns how many commands were executed during its lifetime.") + .register(registry) + + commandLatency = Histogram.build() + .name("nino_command_latency") + .help("Returns the latency in milliseconds of how long a command is executed.") + .labelNames("command") + .register(registry) + + gatewayLatency = Histogram.build() + .name("nino_gateway_latency") + .help("Returns the gateway latency for all shards, use `gatewayPing` per-shard.") + .register(registry) + + gatewayPing = Histogram.build() + .name("nino_gateway_ping") + .help("Returns the gateway latency for all shards.") + .labelNames("shard") + .register(registry) + + messagesSeen = Counter.build() + .name("nino_messages_seen") + .help("Returns how many messages Nino has seen.") + .register(registry) + + guildCount = Gauge.build() + .name("nino_guild_count") + .help("Returns how many guilds Nino is in") + .register(registry) + + users = Gauge.build() + .name("nino_user_count") + .help("Returns how many users Nino can see") + .register(registry) + + websocketEvents = Counter.build() + .name("nino_websocket_events") + .help("Returns how many events that are being emitted.") + .labelNames("shard", "event") + .register(registry) + + if (apiEnabled) { + apiRequestLatency = Histogram.build() + .name("nino_api_request_latency") + .help("Returns the average latency on all API requests.") + .register(registry) + + apiRequestCount = Gauge.build() + .name("nino_api_request_count") + .help("Returns how many requests by endpoint + method have been executed.") + .labelNames("endpoint", "method") + .register(registry) + } + } + } +} diff --git a/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/functions.kt b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/functions.kt new file mode 100644 index 00000000..640432a9 --- /dev/null +++ b/modules/metrics/src/main/kotlin/sh/nino/modules/metrics/functions.kt @@ -0,0 +1,89 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:JvmName("NinoMetricFunctionsKt") + +package sh.nino.modules.metrics + +import io.prometheus.client.Histogram +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +fun MetricsModule.inc(metricType: MetricType) { + // nop if it's not enabled + if (!enabled) return + + when (metricType) { + MetricType.API_REQUEST_COUNT -> { + if (apiEnabled) { + apiRequestCount.inc() + } + } + + MetricType.COMMANDS_EXECUTED -> commandsExecuted.inc() + MetricType.MESSAGES_SEEN -> messagesSeen.inc() + MetricType.GUILD_COUNT -> guildCount.inc() + else -> error("Metric type $metricType is not supported with inc method.") + } +} + +fun MetricsModule.dec(metricType: MetricType) { + if (!enabled) return + + when (metricType) { + MetricType.GUILD_COUNT -> guildCount.dec() + else -> error("Metric type $metricType is not supported with dec method.") + } +} + +@OptIn(ExperimentalContracts::class) +suspend fun MetricsModule.measure(metricType: MetricType, block: suspend () -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + + // If it's not enabled, just call the block + // and not do anything. + if (!enabled) { + block() + return + } + + val histogram: Histogram = when (metricType) { + MetricType.API_REQUEST_LATENCY -> if (apiEnabled) apiRequestLatency else null + MetricType.COMMAND_LATENCY -> commandLatency + else -> null + } ?: return // nop it if not a histogram! + + val timer = histogram.startTimer() + block() + + timer.observeDuration() +} + +fun MetricsModule.incEvent(shard: Int, event: String) { + if (!enabled) return + + websocketEvents.labels("$shard", event).inc() +} diff --git a/modules/punishments/build.gradle.kts b/modules/punishments/build.gradle.kts new file mode 100644 index 00000000..aa89528c --- /dev/null +++ b/modules/punishments/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules")) + implementation(project(":database")) + api(project(":modules:timeouts")) +} diff --git a/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/MemberLikeObject.kt b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/MemberLikeObject.kt new file mode 100644 index 00000000..624ea1e8 --- /dev/null +++ b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/MemberLikeObject.kt @@ -0,0 +1,41 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.punishments + +import dev.kord.common.entity.Snowflake +import dev.kord.core.entity.Guild +import dev.kord.core.entity.Member + +/** + * Returns a [Boolean] if the object is a partial member object. + */ +val MemberLikeObject.isPartial: Boolean + get() = member == null + +fun Member?.toMemberLikeObject(id: Snowflake, guild: Guild): MemberLikeObject = MemberLikeObject(this, guild, id) + +/** + * Represents a "partial" member object. + */ +class MemberLikeObject(val member: Member?, val guild: Guild, val id: Snowflake) diff --git a/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/PunishmentModule.kt b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/PunishmentModule.kt new file mode 100644 index 00000000..3e12a451 --- /dev/null +++ b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/PunishmentModule.kt @@ -0,0 +1,1034 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.punishments + +import dev.kord.common.entity.DiscordMessage +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import dev.kord.common.entity.Snowflake +import dev.kord.core.Kord +import dev.kord.core.behavior.ban +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.channel.editRolePermission +import dev.kord.core.behavior.edit +import dev.kord.core.behavior.getChannelOf +import dev.kord.core.cache.data.AttachmentData +import dev.kord.core.cache.data.MemberData +import dev.kord.core.cache.data.toData +import dev.kord.core.entity.Attachment +import dev.kord.core.entity.Guild +import dev.kord.core.entity.Member +import dev.kord.core.entity.Message +import dev.kord.core.entity.channel.TextChannel +import dev.kord.rest.builder.message.EmbedBuilder +import gay.floof.utils.slf4j.logging +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.toList +import kotlinx.datetime.Clock +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.update +import sh.nino.commons.Constants +import sh.nino.commons.extensions.asSnowflake +import sh.nino.commons.extensions.inject +import sh.nino.commons.extensions.sortWith +import sh.nino.commons.isMemberAbove +import sh.nino.commons.ms +import sh.nino.database.PunishmentType +import sh.nino.database.asyncTransaction +import sh.nino.database.entities.CaseEntity +import sh.nino.database.entities.GuildEntity +import sh.nino.database.entities.PunishmentEntity +import sh.nino.database.entities.WarningEntity +import sh.nino.database.tables.CasesTable +import sh.nino.database.tables.GuildsTable +import sh.nino.database.tables.PunishmentsTable +import sh.nino.database.tables.WarningsTable +import sh.nino.modules.Registry +import sh.nino.modules.annotations.* +import sh.nino.modules.punishments.builders.ApplyPunishmentBuilder +import sh.nino.modules.punishments.builders.PublishModLogBuilder +import sh.nino.modules.punishments.builders.PublishModLogData +import sh.nino.modules.punishments.extensions.asEmoji +import sh.nino.modules.punishments.extensions.permissions +import sh.nino.modules.punishments.extensions.toKey +import sh.nino.modules.timeouts.TimeoutsModule +import sh.nino.modules.timeouts.on +import sh.nino.modules.timeouts.types.ApplyEvent +import sh.nino.modules.timeouts.types.RequestCommand +import sh.nino.modules.timeouts.types.Timeout +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId + +private val WEBHOOK_URI_REGEX = "(?:https?:\\/\\/)?(?:canary\\.|ptb\\.)?discord\\.com\\/api\\/webhooks\\/(\\d{15,21})\\/([^\\/]+)".toRegex() + +@ModuleMeta(name = "punishments", "Punishments module to implement ") +class PunishmentModule { + // The queue is where the timeouts get issued if the server is closed. + private val queue = mutableMapOf() + private val timeouts: TimeoutsModule? by Registry.inject() + private val kord: Kord by inject() + private val log by logging() + + @Suppress("UNUSED") + @Action + fun onInit() { + log.info("Initializing events...") + + timeouts!!.on { + val timeout = this.timeout + val issuedAt = LocalDateTime.ofInstant(Instant.ofEpochMilli(timeout.issuedAt), ZoneId.systemDefault()) + + log.info("Applying ${timeout.type} to user ${timeout.userId} in guild ${timeout.guildId} (issued_at=$issuedAt)") + } + } + + /** + * Resolves the [member] to get the actual [Member] object out of it, this only calls + * REST or cache if the member object isn't a partial one. + */ + private suspend fun resolveMember(member: MemberLikeObject, useRest: Boolean = false): Member { + if (!member.isPartial) { + return member.member!! + } + + // Check if it's cached in Kord + val cached = kord.defaultSupplier.getMemberOrNull(member.guild.id, member.id) + if (cached != null) return cached + + // Let's retrieve it from REST if we can, though + // the parameter is kinda mis-leading... + return if (useRest) { + val raw = kord.rest.guild.getGuildMember(member.guild.id, member.id) + Member(raw.toData(member.guild.id, member.id), raw.user.value!!.toData(), kord) + } else { + // Get the current user because mocked data! + val user = kord.rest.user.getUser(member.id) + Member( + MemberData( + member.id, + member.guild.id, + joinedAt = Clock.System.now().toString(), + roles = listOf() + ), + + user.toData(), + kord + ) + } + } + + /** + * Adds a warning to the [member]. + * @param member The member to add warnings towards. + * @param moderator The moderator who invoked this action. + * @param reason The reason why the [member] needs to be warned. + * @param amount The amount of warnings to add. If [amount] is set to `null`, + * it'll just add the amount of warnings from the [member] in the guild by 1. + */ + suspend fun addWarning( + member: Member, + moderator: Member, + amount: Int = 1, + reason: String? = null, + expiresIn: kotlinx.datetime.LocalDateTime? = null + ) { + log.info("Adding $amount warning${if (amount == 0 || amount > 1) "s" else ""} to ${member.tag} (${member.id}) by moderator ${moderator.tag} (${moderator.id})${if (reason != null) ", for $reason" else ""}") + + val warnings = asyncTransaction { + WarningEntity.find { + WarningsTable.id eq member.id.value.toLong() + } + } + + val combinedAmount = warnings.fold(0) { acc, curr -> acc + curr.amount } + val attached = combinedAmount + amount + + if (attached < 0) + throw IllegalStateException("Warnings was not in bounds (<0; got $attached)") + + // Get the guild's punishments + val punishments = asyncTransaction { + PunishmentEntity.find { + PunishmentsTable.id eq member.guild.id.value.toLong() + } + } + + val guild = member.guild.asGuild() + + // Execute the punishments that are in range of `attached` + for (punishment in punishments) { + apply( + MemberLikeObject(member, guild, member.id), + moderator, + punishment.type + ) { + this.time = punishment.time?.toInt() + } + } + + // Add the warning + val guildId = member.guild.id.value.toLong() + asyncTransaction { + WarningEntity.new(member.id.value.toLong()) { + receivedAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + + if (expiresIn != null) { + this.expiresIn = expiresIn + } + + this.reason = reason + this.amount = amount + this.guild = guildId + } + } + + // Create a new case (if there were no punishments) + if (punishments.toList().isEmpty()) { + val case = asyncTransaction { + CaseEntity.new(member.guild.id.value.toLong()) { + moderatorId = moderator.id.value.toLong() + createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + victimId = member.id.value.toLong() + type = PunishmentType.WARNING_ADDED + + this.reason = "Moderator added **$attached** warnings.${if (reason != null) " ($reason)" else ""}" + } + } + + publishModlog(case) { + this.moderator = moderator + this.guild = guild + + warningsAdded = amount + victim = member + } + } + } + + /** + * Removes any warnings from the [member]. + * + * @param member The member that needs their warnings removed. + * @param moderator The moderator who invoked this action. + * @param reason The reason why the warnings were removed. + * @param amount The amount of warnings to add. If [amount] is set to `null`, + * it'll just clean their database entries for this specific guild, not globally. + * + * @throws IllegalStateException If the member doesn't need any warnings removed. + */ + suspend fun removeWarning( + member: Member, + moderator: Member, + reason: String? = null, + amount: Int? = null + ) { + log.info("Removing ${amount ?: "all"} warnings to ${member.tag} (${member.id}) by ${moderator.tag} (${moderator.id})${if (reason != null) ", for $reason" else ""}") + + val warnings = asyncTransaction { + WarningEntity.find { + (WarningsTable.id eq member.id.value.toLong()) and (WarningsTable.guild eq member.guild.id.value.toLong()) + } + } + + val ifZero = warnings.fold(0) { acc, curr -> acc + curr.amount } + if (warnings.toList().isEmpty() || ifZero < 0) + throw IllegalStateException("Member ${member.tag} doesn't have any warnings to be removed.") + + val guild = member.getGuild() + if (amount == null) { + asyncTransaction { + WarningsTable.deleteWhere { + (WarningsTable.id eq member.id.value.toLong()) and (WarningsTable.guild eq member.guild.id.value.toLong()) + } + } + + val case = asyncTransaction { + CaseEntity.new(member.guild.id.value.toLong()) { + moderatorId = moderator.id.value.toLong() + createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + victimId = member.id.value.toLong() + type = PunishmentType.WARNING_ADDED + + this.reason = "Moderator cleared all warnings.${if (reason != null) " ($reason)" else ""}" + } + } + + publishModlog(case) { + this.moderator = moderator + this.guild = guild + + warningsRemoved = -1 + victim = member + } + } else { + asyncTransaction { + WarningEntity.new(member.id.value.toLong()) { + this.guild = member.guild.id.value.toLong() + this.amount = -amount + this.reason = reason + } + } + + val case = asyncTransaction { + CaseEntity.new(member.guild.id.value.toLong()) { + moderatorId = moderator.id.value.toLong() + createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + victimId = member.id.value.toLong() + type = PunishmentType.WARNING_ADDED + + this.reason = "Moderator removed **$amount** warnings.${if (reason != null) " ($reason)" else ""}" + } + } + + publishModlog(case) { + this.moderator = moderator + this.guild = guild + + warningsRemoved = amount + victim = member + } + } + } + + /** + * Applies a new punishment to a user, if needed. + * @param member The [member][MemberLikeObject] to execute this action. + * @param moderator The moderator who executed this action. + * @param type The punishment type that is being executed. + * @param builder DSL builder for any extra options. + */ + suspend fun apply( + member: MemberLikeObject, + moderator: Member, + type: PunishmentType, + builder: ApplyPunishmentBuilder.() -> Unit = {} + ) { + val options = ApplyPunishmentBuilder().apply(builder).build() + log.info("Applying punishment ${type.toKey()} to member ${member.id}${if (options.reason != null) ", for ${options.reason}" else ""}") + + val settings = asyncTransaction { + GuildEntity.findById(member.guild.id.value.toLong())!! + } + + val self = member.guild.getMember(kord.selfId) + if ( + (!member.isPartial && isMemberAbove(self, member.member!!)) || + (self.getPermissions().code.value.toLong() and type.permissions.code.value.toLong()) == 0L + ) return + + val mem = resolveMember(member, type != PunishmentType.BAN) + when (type) { + PunishmentType.VOICE_UNDEAFEN -> applyVoiceUndeafen(mem, options.reason) + PunishmentType.VOICE_UNMUTE -> applyVoiceUndeafen(mem, options.reason) + PunishmentType.VOICE_DEAFEN -> applyVoiceDeafen(mem, moderator, options.reason, options.time) + PunishmentType.VOICE_MUTE -> applyVoiceMute(mem, moderator, options.reason, options.time) + PunishmentType.UNMUTE -> applyVoiceUnmute(mem, options.reason) + + PunishmentType.KICK -> { + mem.guild.kick(member.id, options.reason) + } + + PunishmentType.UNBAN -> { + mem.guild.unban(member.id, options.reason) + } + + PunishmentType.ROLE_ADD -> { + mem.addRole(options.roleId!!.asSnowflake(), options.reason) + } + + PunishmentType.ROLE_REMOVE -> { + mem.removeRole(options.roleId!!.asSnowflake()) + } + + PunishmentType.THREAD_MESSAGES_ADDED -> applyThreadMessagesBack( + settings, + mem, + options.reason + ) + + PunishmentType.THREAD_MESSAGES_REMOVED -> applyRemoveThreadMessagePerms( + settings, + mem, + moderator, + options.reason, + options.time + ) + + PunishmentType.BAN -> applyBan( + mem, + moderator, + options.reason, + options.days, + options.soft, + options.time + ) + + PunishmentType.MUTE -> applyMute( + settings, + mem, + moderator, + options.reason, + options.time + ) + + else -> { + // do nothing! + } + } + + val case = asyncTransaction { + CaseEntity.new(member.guild.id.value.toLong()) { + attachments = options.attachments.toTypedArray().map { it.url }.toTypedArray() + moderatorId = moderator.id.value.toLong() + victimId = member.id.value.toLong() + soft = options.soft + time = options.time?.toLong() + + this.type = type + this.reason = options.reason + } + } + + if (options.publish) { + publishModlog(case) { + this.moderator = moderator + + voiceChannel = options.voiceChannel + reason = options.reason + victim = mem + guild = member.guild + time = options.time + + if (options.attachments.isNotEmpty()) addAttachments( + options.attachments.map { + Attachment( + // we don't store the id, size, proxyUrl, or filename, + // so it's fine to make it mocked. + AttachmentData( + id = Snowflake(0L), + size = 0, + url = it.url, + proxyUrl = it.proxyUrl, + filename = "unknown.png" + ), + + kord + ) + } + ) + } + } + } + + suspend fun publishModlog(case: CaseEntity, builder: PublishModLogBuilder.() -> Unit = {}) { + val data = PublishModLogBuilder().apply(builder).build() + val settings = asyncTransaction { + GuildEntity[data.guild.id.value.toLong()] + } + + val modlogChannel = try { + data.guild.getChannelOf(Snowflake(settings.modlogChannelId!!)) + } catch (e: Exception) { + null + } ?: return + + val permissions = modlogChannel.getEffectivePermissions(kord.selfId) + if (!permissions.contains(Permission.SendMessages) || !permissions.contains(Permission.EmbedLinks)) + return + + val message: Any? = if (settings.usePlainModlogMessage) { + // Check if we need to execute a webhook + if (settings.modlogWebhookUri != null) { + // check if we can match it + val matcher = WEBHOOK_URI_REGEX.toPattern().matcher(settings.modlogWebhookUri!!) + val id = Snowflake(matcher.group(1)) + val token = matcher.group(2) + + kord.rest.webhook.executeWebhook(id, token, true) { + content = getModlogPlainText(case.id.value.toInt(), data) + } + } else { + modlogChannel.createMessage { + content = getModlogPlainText(case.id.value.toInt(), data) + } + } + } else { + // Check if we need to execute a webhook + if (settings.modlogWebhookUri != null) { + // check if we can match it + val matcher = WEBHOOK_URI_REGEX.toPattern().matcher(settings.modlogWebhookUri!!) + val id = Snowflake(matcher.group(1)) + val token = matcher.group(2) + + kord.rest.webhook.executeWebhook(id, token, true) { + embeds += getModlogMessageAsEmbed(case.id.value.toInt(), data) + } + } else { + modlogChannel.createMessage { + embeds += getModlogMessageAsEmbed(case.id.value.toInt(), data) + } + } + } + + val messageID = (message as? DiscordMessage)?.id ?: (message as Message).id + + asyncTransaction { + CasesTable.update({ + (CasesTable.index eq case.index) and (CasesTable.id eq data.guild.id.value.toLong()) + }) { + it[messageId] = messageID.value.toLong() + } + } + } + + suspend fun editModlogMessage(case: CaseEntity, message: Message) { + val settings = asyncTransaction { + GuildEntity[case.id.value] + } + + val guild = message.getGuild() + val data = PublishModLogBuilder().apply { + this.guild = guild + moderator = guild.members.first { it.id == case.moderatorId.asSnowflake() } + reason = case.reason + victim = guild.members.first { it.id == case.victimId.asSnowflake() } + type = case.type + + if (case.attachments.isNotEmpty()) { + addAttachments( + case.attachments.map { + Attachment( + // we don't store the id, size, proxyUrl, or filename, + // so it's fine to make it mocked. + AttachmentData( + id = Snowflake(0L), + size = 0, + url = it, + proxyUrl = it, + filename = "unknown.png" + ), + + kord + ) + } + ) + } + + if (settings.usePlainModlogMessage) { + // this looks fucking horrendous but it works LOL + val warningsRegex = "Warnings (Added|Removed): \\*\\*([A-Za-z]*|\\d+)\\*\\*".toRegex() + val matcher = warningsRegex.toPattern().matcher(message.content) + + // if we find any matches, let's grab em all + if (matcher.matches()) { + val addOrRemove = matcher.group(1) + val allOrInt = matcher.group(2) + + when (addOrRemove) { + "Added" -> { + // remove instances of `**` + val intValue = try { + Integer.parseInt(allOrInt.replace("**", "")) + } catch (e: Exception) { + null + } ?: throw IllegalStateException("Unable to cast \"$allOrInt\" into a number.") + + warningsAdded = intValue + } + + "Removed" -> { + if (allOrInt == "**All**") { + warningsRemoved = -1 + } else { + val intValue = try { + Integer.parseInt(allOrInt.replace("**", "")) + } catch (e: Exception) { + null + } ?: throw IllegalStateException("Unable to cast \"$allOrInt\" into a number.") + + warningsRemoved = intValue + } + } + } + } + } else { + val embed = message.embeds.first() + val warningsRemovedField = embed.fields.firstOrNull { + it.name.lowercase().contains("warnings removed") + } + + val warningsAddedField = embed.fields.firstOrNull { + it.name.lowercase().contains("warnings added") + } + + if (warningsRemovedField != null) + warningsRemoved = Integer.parseInt(warningsRemovedField.value) + + if (warningsAddedField != null) + warningsAdded = Integer.parseInt(warningsAddedField.value) + } + } + + if (settings.usePlainModlogMessage) { + if (settings.modlogWebhookUri != null) { + // check if we can match it + val matcher = WEBHOOK_URI_REGEX.toPattern().matcher(settings.modlogWebhookUri!!) + if (!matcher.matches()) return + + val id = Snowflake(matcher.group(1)) + val token = matcher.group(2) + + kord.rest.webhook.editWebhookMessage(id, token, message.id) { + content = getModlogPlainText(case.id.value.toInt(), data.build()) + } + } else { + message.edit { + content = getModlogPlainText(case.id.value.toInt(), data.build()) + } + } + } else { + if (settings.modlogWebhookUri != null) { + // check if we can match it + val matcher = WEBHOOK_URI_REGEX.toPattern().matcher(settings.modlogWebhookUri!!) + if (!matcher.matches()) return + + val id = Snowflake(matcher.group(1)) + val token = matcher.group(2) + + kord.rest.webhook.editWebhookMessage(id, token, message.id) { + embeds?.plusAssign(getModlogMessageAsEmbed(case.id.value.toInt(), data.build())) + } + } else { + message.edit { + embeds?.plusAssign(getModlogMessageAsEmbed(case.id.value.toInt(), data.build())) + } + } + } + } + + private suspend fun getOrCreateMutedRole(settings: GuildEntity, guild: Guild): Snowflake { + if (settings.mutedRoleId != null) return Snowflake(settings.mutedRoleId!!) + + val muteRole: Long + val role = guild.roles.firstOrNull { + it.name.lowercase() == "muted" + } + + if (role == null) { + val newRole = kord.rest.guild.createGuildRole(guild.id) { + hoist = false + reason = "Missing muted role in database and in guild" + name = "Muted" + mentionable = false + permissions = Permissions() + } + + muteRole = newRole.id.value.toLong() + val topRole = guild.members.first { it.id == kord.selfId } + .roles + .sortWith { a, b -> b.rawPosition - a.rawPosition } + .firstOrNull() + + if (topRole != null) { + kord.rest.guild.modifyGuildRolePosition(guild.id) { + move(topRole.id to topRole.rawPosition - 1) + } + + for (channel in guild.channels.toList()) { + val perms = channel.getEffectivePermissions(kord.selfId) + if (perms.contains(Permission.ManageChannels)) { + channel.editRolePermission(newRole.id) { + allowed = Permissions() + denied = Permissions { + -Permission.SendMessages + } + + reason = "Overrided permissions for role ${newRole.name} (${newRole.id})" + } + } + } + } + } else { + muteRole = role.id.value.toLong() + } + + if (muteRole == 0L) throw IllegalStateException("Unable to create or find a mute role, manually add it.") + asyncTransaction { + GuildsTable.update({ GuildsTable.id eq guild.id.value.toLong() }) { + it[mutedRoleId] = muteRole + } + } + + return Snowflake(muteRole) + } + + private suspend fun getOrCreateNoThreadsRole(settings: GuildEntity, guild: Guild): Snowflake { + if (settings.noThreadsRoleId != null) return Snowflake(settings.noThreadsRoleId!!) + + val muteRole: Long + val role = guild.roles.firstOrNull { + it.name.lowercase() == "no threads" + } + + if (role == null) { + val newRole = kord.rest.guild.createGuildRole(guild.id) { + hoist = false + reason = "Missing \"No Threads\" role in database and in guild" + name = "Muted" + mentionable = false + permissions = Permissions() + } + + muteRole = newRole.id.value.toLong() + val topRole = guild.members.first { it.id == kord.selfId } + .roles + .sortWith { a, b -> b.rawPosition - a.rawPosition } + .firstOrNull() + + if (topRole != null) { + kord.rest.guild.modifyGuildRolePosition(guild.id) { + move(topRole.id to topRole.rawPosition - 1) + } + + for (channel in guild.channels.toList()) { + val perms = channel.getEffectivePermissions(kord.selfId) + if (perms.contains(Permission.ManageChannels)) { + channel.editRolePermission(newRole.id) { + allowed = Permissions() + denied = Permissions { + -Permission.SendMessages + } + + reason = "Overrided permissions for role ${newRole.name} (${newRole.id})" + } + } + } + } + } else { + muteRole = role.id.value.toLong() + } + + if (muteRole == 0L) throw IllegalStateException("Unable to create or find a No Threads role, manually add it.") + asyncTransaction { + GuildsTable.update({ GuildsTable.id eq guild.id.value.toLong() }) { + it[noThreadsRoleId] = muteRole + } + } + + return Snowflake(muteRole) + } + + private suspend fun applyBan( + member: Member, + moderator: Member, + reason: String? = null, + days: Int = 7, + soft: Boolean = false, + time: Int? = null + ) { + val guild = member.getGuild() + log.info("Banning member '${member.tag} (${member.id})' by ${reason ?: "(no reason)"} by moderator '${moderator.tag} (${moderator.id})' in guild ${guild.name} (${guild.id})") + + guild.ban(member.id) { + this.deleteMessagesDays = days + this.reason = reason + } + + if (soft) { + guild.unban(member.id, reason) + return + } + + if (time != null) { + val timeout = Timeout( + "${guild.id}", + "${member.id}", + System.currentTimeMillis(), + time.toLong(), + "${moderator.id}", + reason, + PunishmentType.UNBAN.toKey() + ) + + if (timeouts!!.closed) { + log.warn("Server is currently closed, adding it to queue...") + queue[member.id] = timeout + } else { + timeouts!!.send(RequestCommand(timeout)) + } + } + } + + private suspend fun applyUnmute(settings: GuildEntity, member: Member, reason: String? = null) { + val muteRoleId = getOrCreateMutedRole(settings, member.guild.asGuild()) + member.removeRole(muteRoleId, reason) + } + + private suspend fun applyThreadMessagesBack(settings: GuildEntity, member: Member, reason: String? = null) { + val threadRoleId = getOrCreateNoThreadsRole(settings, member.guild.asGuild()) + member.removeRole(threadRoleId, reason) + } + + private suspend fun applyMute( + settings: GuildEntity, + member: Member, + moderator: Member, + reason: String? = null, + time: Int? + ) { + val guild = member.getGuild() + val roleId = getOrCreateMutedRole(settings, guild) + member.addRole(roleId, reason) + + if (time != null) { + val timeout = Timeout( + "${guild.id}", + "${member.id}", + System.currentTimeMillis(), + time.toLong(), + "${moderator.id}", + reason, + PunishmentType.UNMUTE.toKey() + ) + + if (timeouts!!.closed) { + log.warn("Server is currently closed, adding it to queue...") + queue[member.id] = timeout + } else { + timeouts!!.send(RequestCommand(timeout)) + } + } + } + + private suspend fun applyRemoveThreadMessagePerms( + settings: GuildEntity, + member: Member, + moderator: Member, + reason: String? = null, + time: Int? + ) { + val guild = member.getGuild() + val roleId = getOrCreateNoThreadsRole(settings, guild) + member.addRole(roleId, reason) + + if (time != null) { + val timeout = Timeout( + "${guild.id}", + "${member.id}", + System.currentTimeMillis(), + time.toLong(), + "${moderator.id}", + reason, + PunishmentType.THREAD_MESSAGES_ADDED.toKey() + ) + + if (timeouts!!.closed) { + log.warn("Server is currently closed, adding it to queue...") + queue[member.id] = timeout + } else { + timeouts!!.send(RequestCommand(timeout)) + } + } + } + + private suspend fun applyVoiceMute( + member: Member, + moderator: Member, + reason: String? = null, + time: Int? + ) { + val guild = member.getGuild() + val voiceState = member.getVoiceState() + if (voiceState.channelId != null && !voiceState.isMuted) { + member.edit { + this.reason = reason + muted = true + } + } + + if (time != null) { + val timeout = Timeout( + "${guild.id}", + "${member.id}", + System.currentTimeMillis(), + time.toLong(), + "${moderator.id}", + reason, + PunishmentType.VOICE_UNMUTE.toKey() + ) + + if (timeouts!!.closed) { + log.warn("Server is currently closed, adding it to queue...") + queue[member.id] = timeout + } else { + timeouts!!.send(RequestCommand(timeout)) + } + } + } + + private suspend fun applyVoiceDeafen( + member: Member, + moderator: Member, + reason: String? = null, + time: Int? + ) { + val guild = member.getGuild() + val voiceState = member.getVoiceState() + if (voiceState.channelId != null && !voiceState.isDeafened) { + member.edit { + this.reason = reason + muted = true + } + } + + if (time != null) { + val timeout = Timeout( + "${guild.id}", + "${member.id}", + System.currentTimeMillis(), + time.toLong(), + "${moderator.id}", + reason, + PunishmentType.VOICE_UNDEAFEN.toKey() + ) + + if (timeouts!!.closed) { + log.warn("Server is currently closed, adding it to queue...") + queue[member.id] = timeout + } else { + timeouts!!.send(RequestCommand(timeout)) + } + } + } + + private suspend fun applyVoiceUnmute(member: Member, reason: String? = null) { + val voiceState = member.getVoiceState() + if (voiceState.channelId != null && !voiceState.isDeafened) { + member.edit { + muted = false + this.reason = reason + } + } + } + + private suspend fun applyVoiceUndeafen(member: Member, reason: String? = null) { + val voiceState = member.getVoiceState() + if (voiceState.channelId != null && !voiceState.isDeafened) { + member.edit { + deafened = false + this.reason = reason + } + } + } + + private fun getModlogMessageAsEmbed(caseId: Int, data: PublishModLogData): EmbedBuilder = EmbedBuilder().apply { + color = Constants.COLOR + author { + name = "[ ${data.type.asEmoji} ${data.type.name} | Case #$caseId ]" + icon = data.victim.avatar?.url + } + + description = buildString { + if (data.reason != null) { + appendLine(data.reason) + } + + if (data.attachments.isNotEmpty()) { + if (data.reason != null) appendLine() + + for ((index, attachment) in data.attachments.withIndex()) + appendLine("• [**Attachment #$index**](${attachment.url})") + } + } + + field { + name = "• Victim" + value = "${data.victim.tag} (**${data.victim.id}**)" + } + + field { + name = "• Moderator" + value = "${data.moderator.tag} (**${data.moderator.id}**)" + } + + if (data.time != null) { + val verboseTime = ms.fromLong(data.time.toLong(), true) + field { + name = "• ${data.type.toKey()} Expires In" + value = "$verboseTime " + } + } + + if (data.warningsAdded != null) { + field { + name = "• Warnings Added" + inline = true + value = if (data.warningsAdded == 1) "All" else data.warningsAdded.toString() + } + } + + if (data.warningsRemoved != null) { + field { + name = "• Warnings Removed" + inline = true + value = if (data.warningsRemoved == 1) "All" else data.warningsRemoved.toString() + } + } + } + + private fun getModlogPlainText(caseId: Int, data: PublishModLogData): String = buildString { + appendLine("[ ${data.type.asEmoji} ${data.type.toKey()} | Case #**$caseId** ]") + + if (data.reason != null) { + appendLine(data.reason) + appendLine() + } + + if (data.attachments.isNotEmpty()) { + for ((index, attachment) in data.attachments.withIndex()) + appendLine("• [**Attachment #$index**](${attachment.url})") + } + + appendLine("• Victim: **${data.victim.tag}** (${data.victim.id})") + appendLine("• Moderator: **${data.moderator.tag}** (${data.moderator.id})") + + if (data.time != null) { + val verboseTime = ms.fromLong(data.time.toLong(), true) + appendLine("• ${data.type.toKey()} Expires In: **$verboseTime** ") + } + + if (data.warningsAdded != null) { + appendLine("• **Warnings Added**: ${data.warningsAdded}") + } + + if (data.warningsRemoved != null) { + appendLine("• **Warnings Removed**: ${if (data.warningsRemoved == -1) "All" else data.warningsAdded}") + } + } +} diff --git a/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/ApplyPunishmentBuilder.kt b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/ApplyPunishmentBuilder.kt new file mode 100644 index 00000000..7633c32e --- /dev/null +++ b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/ApplyPunishmentBuilder.kt @@ -0,0 +1,89 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.punishments.builders + +import dev.kord.core.entity.Attachment +import dev.kord.core.entity.channel.VoiceChannel + +/** + * The data when you fun the [ApplyPunishmentBuilder.build] method. + */ +data class ApplyPunishmentData( + /** + * Returns the [voice channel][VoiceChannel] that is applied to this punishment. + * + * This is only tied to the following punishment types: + * - [PunishmentType.VOICE_UNDEAFEN] + * - [PunishmentType.VOICE_DEAFEN] + * - [PunishmentType.VOICE_UNMUTE] + * - [PunishmentType.VOICE_MUTE] + */ + val voiceChannel: VoiceChannel? = null, + + /** + * Returns a list of attachments to use to provide more evidence within a certain case. + */ + val attachments: List = listOf(), + + /** + * If we should publish this case to the mod-log. + */ + val publish: Boolean = true, + + /** + * The reason why this action was taken care of. + */ + val reason: String? = null, + + /** + * How much time in milliseconds this action should revert. + */ + val time: Int? = null, + + val roleId: Long? = null, + val soft: Boolean = false, + val days: Int = 7 +) + +class ApplyPunishmentBuilder { + var voiceChannel: VoiceChannel? = null + var attachments: List = listOf() + var publish: Boolean = true + var reason: String? = null + var roleId: Long? = null + var time: Int? = null + var soft: Boolean = false + var days: Int = 7 + + fun build(): ApplyPunishmentData = ApplyPunishmentData( + voiceChannel, + attachments, + publish, + reason, + time, + roleId, + soft, + days + ) +} diff --git a/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/PublishModLogBuilder.kt b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/PublishModLogBuilder.kt new file mode 100644 index 00000000..56f59fc3 --- /dev/null +++ b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/builders/PublishModLogBuilder.kt @@ -0,0 +1,83 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.punishments.builders + +import dev.kord.core.entity.Attachment +import dev.kord.core.entity.Guild +import dev.kord.core.entity.User +import dev.kord.core.entity.channel.VoiceChannel +import sh.nino.database.PunishmentType + +data class PublishModLogData( + val warningsRemoved: Int? = null, + val warningsAdded: Int? = null, + val attachments: List = listOf(), + val moderator: User, + val voiceChannel: VoiceChannel? = null, + val reason: String? = null, + val victim: User, + val guild: Guild, + val time: Int? = null, + val type: PunishmentType +) + +class PublishModLogBuilder { + private val attachments: MutableList = mutableListOf() + + lateinit var moderator: User + lateinit var victim: User + lateinit var guild: Guild + lateinit var type: PunishmentType + + var warningsRemoved: Int? = null + var warningsAdded: Int? = null + var voiceChannel: VoiceChannel? = null + var reason: String? = null + var time: Int? = null + + fun addAttachments(list: List): PublishModLogBuilder { + attachments.addAll(list) + return this + } + + fun build(): PublishModLogData { + require(this::moderator.isInitialized) { "Moderator is a required property to initialize." } + require(this::victim.isInitialized) { "Victim is a required property to initialize." } + require(this::guild.isInitialized) { "Guild is a required property to be initialized." } + require(this::type.isInitialized) { "Punishment type is a required property to be initialized." } + + return PublishModLogData( + warningsRemoved, + warningsAdded, + attachments, + moderator, + voiceChannel, + reason, + victim, + guild, + time, + type + ) + } +} diff --git a/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/extensions/PunishmentTypeExtensions.kt b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/extensions/PunishmentTypeExtensions.kt new file mode 100644 index 00000000..fe434835 --- /dev/null +++ b/modules/punishments/src/main/kotlin/sh/nino/modules/punishments/extensions/PunishmentTypeExtensions.kt @@ -0,0 +1,111 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.punishments.extensions + +import dev.kord.common.entity.Permission +import dev.kord.common.entity.Permissions +import sh.nino.database.PunishmentType + +/** + * Returns this [PunishmentType] as an emoji. + */ +val PunishmentType.asEmoji: String + get() = when (this) { + PunishmentType.BAN -> "\uD83D\uDD28" + PunishmentType.KICK -> "\uD83D\uDC62" + PunishmentType.MUTE -> "\uD83D\uDD07" + PunishmentType.UNBAN -> "\uD83D\uDC64" + PunishmentType.UNMUTE -> "\uD83D\uDCE2" + PunishmentType.VOICE_MUTE -> "\uD83D\uDD07" + PunishmentType.VOICE_UNMUTE -> "\uD83D\uDCE2" + PunishmentType.VOICE_DEAFEN -> "\uD83D\uDD07" + PunishmentType.VOICE_UNDEAFEN -> "\uD83D\uDCE2" + PunishmentType.THREAD_MESSAGES_ADDED -> "\uD83E\uDDF5" + PunishmentType.THREAD_MESSAGES_REMOVED -> "\uD83E\uDDF5" + PunishmentType.ROLE_ADD -> "" + PunishmentType.ROLE_REMOVE -> "" + else -> error("Unknown punishment type: $this") + } + +/** + * Returns the required [Permissions] for this [PunishmentType]. + */ +val PunishmentType.permissions: Permissions + get() = when (this) { + PunishmentType.MUTE, PunishmentType.UNMUTE, PunishmentType.ROLE_ADD, PunishmentType.ROLE_REMOVE -> Permissions { + +Permission.ManageRoles + } + + PunishmentType.VOICE_UNDEAFEN, PunishmentType.VOICE_DEAFEN -> Permissions { + +Permission.DeafenMembers + } + + PunishmentType.VOICE_MUTE, PunishmentType.VOICE_UNMUTE -> Permissions { + +Permission.MuteMembers + } + + PunishmentType.UNBAN, PunishmentType.BAN -> Permissions { + +Permission.BanMembers + } + + PunishmentType.KICK -> Permissions { + +Permission.KickMembers + } + + PunishmentType.THREAD_MESSAGES_ADDED, PunishmentType.THREAD_MESSAGES_REMOVED -> Permissions { + +Permission.ManageThreads + } + + else -> Permissions() + } + +fun PunishmentType.toKey(): String = when (this) { + PunishmentType.BAN -> "ban" + PunishmentType.KICK -> "kick" + PunishmentType.MUTE -> "mute" + PunishmentType.UNBAN -> "unban" + PunishmentType.UNMUTE -> "unmute" + PunishmentType.VOICE_MUTE -> "voice mute" + PunishmentType.VOICE_UNMUTE -> "voice unmute" + PunishmentType.VOICE_DEAFEN -> "voice deafen" + PunishmentType.VOICE_UNDEAFEN -> "voice undeafen" + PunishmentType.THREAD_MESSAGES_ADDED -> "thread messages added" + PunishmentType.THREAD_MESSAGES_REMOVED -> "thread messages removed" + else -> error("Unknown punishment type: $this") +} + +fun String.toPunishmentType(): PunishmentType = when (this) { + "ban" -> PunishmentType.BAN + "kick" -> PunishmentType.KICK + "mute" -> PunishmentType.MUTE + "unban" -> PunishmentType.UNBAN + "unmute" -> PunishmentType.UNMUTE + "voice mute" -> PunishmentType.VOICE_MUTE + "voice unmute" -> PunishmentType.VOICE_UNMUTE + "voice deafen" -> PunishmentType.VOICE_DEAFEN + "voice undeafen" -> PunishmentType.VOICE_UNDEAFEN + "thread messages added" -> PunishmentType.THREAD_MESSAGES_ADDED + "thread messages removed" -> PunishmentType.THREAD_MESSAGES_REMOVED + else -> error("Unknown punishment type: $this") +} diff --git a/modules/ravy/README.md b/modules/ravy/README.md new file mode 100644 index 00000000..fdd1a1db --- /dev/null +++ b/modules/ravy/README.md @@ -0,0 +1,2 @@ +# Module sh.nino.modules.ravy +> Module for Nino to interact with the [ravy.org](https://ravy.org) API. diff --git a/modules/ravy/build.gradle.kts b/modules/ravy/build.gradle.kts new file mode 100644 index 00000000..0af9812e --- /dev/null +++ b/modules/ravy/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules")) +} diff --git a/src/structures/Automod.ts b/modules/ravy/src/main/kotlin/sh/nino/modules/ravy/RavyModule.kt similarity index 58% rename from src/structures/Automod.ts rename to modules/ravy/src/main/kotlin/sh/nino/modules/ravy/RavyModule.kt index 2445a937..32e87259 100644 --- a/src/structures/Automod.ts +++ b/modules/ravy/src/main/kotlin/sh/nino/modules/ravy/RavyModule.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,37 +21,21 @@ * SOFTWARE. */ -import type { Member, Message, TextChannel, User } from 'eris'; +package sh.nino.modules.ravy + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import sh.nino.modules.annotations.ModuleMeta +import sh.nino.modules.ravy.data.GetUserResult /** - * Interface to implement as a [Automod] action. + * Represents the module for interacting with the [ravy.org](https://ravy.org) API. */ -export interface Automod { - /** - * Handles any member's nickname updates - * @param member The member - */ - onMemberNickUpdate?(member: Member): Promise; - - /** - * Handles any user updates - */ - onUserUpdate?(user: User): Promise; - - /** - * Handles any members joining the guild - * @param member The member - */ - onMemberJoin?(member: Member): Promise; - - /** - * Handles any message updates or creation - * @param message The message - */ - onMessage?(message: Message): Promise; - - /** - * The name for this [Automod] class. - */ - name: string; +@ModuleMeta("ravy", "Module to interact with the ravy.org API, used for automod") +class RavyModule(private val token: String, private val httpClient: HttpClient) { + suspend fun getUserData(id: String): GetUserResult = httpClient.get("https://ravy.org/api/v1/users/$id") { + header("Authorization", token) + header("Accept", "application/json; charset=utf-8") + }.body() } diff --git a/modules/ravy/src/main/kotlin/sh/nino/modules/ravy/data/GetUserResult.kt b/modules/ravy/src/main/kotlin/sh/nino/modules/ravy/data/GetUserResult.kt new file mode 100644 index 00000000..d7b491db --- /dev/null +++ b/modules/ravy/src/main/kotlin/sh/nino/modules/ravy/data/GetUserResult.kt @@ -0,0 +1,99 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.ravy.data + +@kotlinx.serialization.Serializable +data class GetUserResult( + val pronouns: String, + val bans: List, + val trust: Trust, + val whitelists: List, + val rep: List, + val sentinel: AeroSentinelEntry +) + +/** + * https://docs.ravy.org/share/5bc92059-64ef-4d6d-816e-144b78e97d89/doc/users-TU3mjAioyS#h-trust + */ +@kotlinx.serialization.Serializable +data class Trust( + /** + * From 0-6, higher is better, default is 3 + */ + val level: Int, + + /** + * What the [level] means. + */ + val label: String +) + +/** + * https://docs.ravy.org/share/5bc92059-64ef-4d6d-816e-144b78e97d89/doc/users-TU3mjAioyS#h-banentry + */ +@kotlinx.serialization.Serializable +data class BanEntry( + /** + * Source for where the user was banned + */ + val provider: String, + + /** + * Why the user was banned + */ + val reason: String, + + /** + * Machine-readable version of the reason - only present for providers + * + * - ravy + * - dservices + */ + val reasonKey: String? = null, + + /** + * user ID of the responsible moderator, usually a Discord snowflake. + */ + val moderator: String +) + +@kotlinx.serialization.Serializable +data class WhitelistEntry( + val provider: String, + val reason: String +) + +@kotlinx.serialization.Serializable +data class ReputationEntry( + val provider: String, + val score: Double, + val upvotes: Int, + val downvotes: Int +) + +@kotlinx.serialization.Serializable +data class AeroSentinelEntry( + val verified: Boolean, + val id: String? = null +) diff --git a/modules/scripting/build.gradle.kts b/modules/scripting/build.gradle.kts new file mode 100644 index 00000000..04a46c32 --- /dev/null +++ b/modules/scripting/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + runtimeOnly(kotlin("scripting-jsr223")) + implementation(project(":modules")) + implementation("org.jruby:jruby:9.3.4.0") +} diff --git a/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ScriptingModule.kt b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ScriptingModule.kt new file mode 100644 index 00000000..cc754c0f --- /dev/null +++ b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ScriptingModule.kt @@ -0,0 +1,43 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.scripting + +import gay.floof.utils.slf4j.logging +import sh.nino.modules.annotations.ModuleMeta +import sh.nino.modules.scripting.ruby.RubyScripter + +@ModuleMeta("scripting", "Scripting module for Guild Policies and Tags.") +class ScriptingModule { + val ruby = RubyScripter() + + private val log by logging() + + init { + log.debug("Enabling Ruby and Kotlin scripting...") + + ruby.init() + } + + fun executeRuby(script: String) = ruby.execute(script) +} diff --git a/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyExecutionContext.kt b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyExecutionContext.kt new file mode 100644 index 00000000..f6f3a030 --- /dev/null +++ b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyExecutionContext.kt @@ -0,0 +1,30 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.scripting.ruby + +/** + * Represents the execution context of the Ruby scripting module. This can be + * extended to provide a way to do stuff. + */ +interface RubyExecutionContext diff --git a/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyScripter.kt b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyScripter.kt new file mode 100644 index 00000000..d7387807 --- /dev/null +++ b/modules/scripting/src/main/kotlin/sh/nino/modules/scripting/ruby/RubyScripter.kt @@ -0,0 +1,65 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.scripting.ruby + +import gay.floof.utils.slf4j.logging +import kotlinx.datetime.Clock +import org.jruby.Ruby +import org.jruby.RubyModule +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets +import java.util.stream.Collectors + +class RubyScripter { + private val log by logging() + + private lateinit var ninoModule: RubyModule + private lateinit var jruby: Ruby + + val name: String = "ruby" + + fun init() { + log.debug("Building JRuby instance...") + + jruby = Ruby.getGlobalRuntime() + ninoModule = jruby.getOrCreateModule("Nino") + } + + fun execute(script: String): Any? { + val preludeScript = this::class.java.getResourceAsStream("/prelude.rb") ?: error("Unable to get prelude script") + val preludeText = preludeScript.use { + BufferedReader(InputStreamReader(it, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")) + } + + val fileName = "nino_rubyscript_${Clock.System.now().toEpochMilliseconds()}.rb" + return jruby.executeScript( + """ + $preludeText + $script + """.trimIndent(), + fileName + ) + } +} diff --git a/modules/scripting/src/main/resources/prelude.rb b/modules/scripting/src/main/resources/prelude.rb new file mode 100644 index 00000000..ca3806e9 --- /dev/null +++ b/modules/scripting/src/main/resources/prelude.rb @@ -0,0 +1,22 @@ +# 🔨 Nino: Cute, advanced discord moderation bot made in Kord. +# Copyright (c) 2019-2022 Nino Team +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This is the prelude script for Nino which can execute guild policies or tags. diff --git a/src/util/Stopwatch.ts b/modules/src/main/kotlin/sh/nino/modules/Module.kt similarity index 56% rename from src/util/Stopwatch.ts rename to modules/src/main/kotlin/sh/nino/modules/Module.kt index fefd9a21..abef73d5 100644 --- a/src/util/Stopwatch.ts +++ b/modules/src/main/kotlin/sh/nino/modules/Module.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,36 +21,32 @@ * SOFTWARE. */ -import { performance } from 'perf_hooks'; +package sh.nino.modules + +import kotlin.properties.ReadOnlyProperty /** - * Utility stopwatch for calculating duration on asynchronous execution + * Represents a module that is registered from the [registry][Registry]. */ -export default class Stopwatch { - private startTime?: number; - private endTime?: number; - - private symbolOf(type: number) { - if (type > 1000) return `${type.toFixed(1)}s`; - if (type > 1) return `${type.toFixed(1)}ms`; - return `${type.toFixed(1)}µs`; - } +interface Module: AutoCloseable, ReadOnlyProperty { + /** The name of the module. */ + val name: String - restart() { - this.startTime = performance.now(); - this.endTime = undefined; - } + /** The current version of this module */ + val version: String - start() { - if (this.startTime !== undefined) throw new SyntaxError('Stopwatch has already started'); + /** Short description on what this module is. */ + val description: String - this.startTime = performance.now(); - } + /** The author who made this module. */ + val author: String - end() { - if (!this.startTime) throw new TypeError('Stopwatch has not started'); + /** The subclass that this module was registered by. */ + val current: T - this.endTime = performance.now(); - return this.symbolOf(this.endTime - this.startTime); - } + /** + * The method to initialize this module, which can be represented + * as the [Action][sh.nino.modules.annotations.Action] annotation. + */ + fun init() } diff --git a/modules/src/main/kotlin/sh/nino/modules/Registry.kt b/modules/src/main/kotlin/sh/nino/modules/Registry.kt new file mode 100644 index 00000000..9e0bdcc2 --- /dev/null +++ b/modules/src/main/kotlin/sh/nino/modules/Registry.kt @@ -0,0 +1,245 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules + +import gay.floof.utils.slf4j.logging +import kotlinx.coroutines.runBlocking +import sh.nino.modules.annotations.Action +import sh.nino.modules.annotations.Closeable +import sh.nino.modules.annotations.ModuleMeta +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.full.callSuspend +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.hasAnnotation + +/** + * Represents the module registry that is implemented to register core + * modules like the Scripting, Punishments, Timeouts, and Localisation modules. + */ +class Registry { + // Returns a list of modules that were registered, + // mapped by their KClass -> Module. + val modules = mutableMapOf, Module<*>>() + val log by logging() + + init { + CURRENT = this + } + + /** + * Retrieves the module by the reified [T] generic, or `null` if the module + * hasn't been registered. + * + * ## Example + * ```kotlin + * val registry = Registry() + * registry.register(MyModule()) + * + * val mod = registry.getOrNull() // => Module + * mod?.current // => MyModule? + * ``` + */ + @Suppress("UNCHECKED_CAST") + inline fun getOrNull(): Module? = modules[T::class] as Module? + + /** + * Operator to retrieve a [Module] from the correspondent KClass provided. + * ## Example + * ```kotlin + * val registry = Registry() + * registry.register(MyModule()) + * + * registry[MyModule::class] // => MyModule (not null) + * ``` + * + * @param kClass The class to get the module from. + * @return The [Module] registered. + * @throws IllegalStateException If the module was not registered by [Registry.register]. + */ + operator fun get(kClass: KClass<*>): Module<*> = modules[kClass] ?: error("Module with KClass '$kClass' was not registered.") + + /** + * Returns a [ReadOnlyProperty] of the module registered as [T], as the correspondent class + * that was registered with it. If you want to module itself, it implements a [ReadOnlyProperty] + * by default. + * + * ## Example + * ```kotlin + * val registry = Registry() + * registry.register(MyModule()) + * + * val mod by registry.inject() + * // => MyModule? + * + * val mod2: MyModule by registry.inject() + * // => MyModule? + * + * val mod3: Module by registry[MyModule::class] + * // => Module + * ``` + */ + inline fun inject(): ReadOnlyProperty = ReadOnlyProperty { _, _ -> + if (modules.containsKey(T::class)) + return@ReadOnlyProperty this[T::class].current as T + + null + } + + /** + * Registers a module to the registry. + * @param module The module to register + * @throws IllegalStateException If the module was already registered. + */ + inline fun register(module: T) { + log.debug("Registering module $module...") + + if (modules.containsKey(module::class)) + throw IllegalStateException("Module with KClass ${module::class} was already registered.") + + // Check if we have a `ModuleMeta` annotation register + val meta = module::class.findAnnotation() ?: error("Cannot find @ModuleMeta annotation.") + val action = module::class.members.firstOrNull { it.hasAnnotation() } + val closable = module::class.members.firstOrNull { it.hasAnnotation() } + + // Create a module object + val mod = object: Module { + /** + * Returns the value of the property for the given object. + * @param thisRef the object for which the value is requested. + * @param property the metadata for the property. + * @return the property value. + */ + override fun getValue(thisRef: Any?, property: KProperty<*>): T = this.current + + /** The name of the module. */ + override val name: String = meta.name + + /** The current version of this module */ + override val version: String = meta.version + + /** Short description on what this module is. */ + override val description: String = meta.description + + /** The author who made this module. */ + override val author: String = meta.author + + /** The subclass that this module was registered by. */ + override val current: T = module + + /** + * The method to initialize this module, which can be represented + * as the [Action][sh.nino.modules.annotations.Action] annotation. + */ + override fun init() { + if (action != null && action.isSuspend) { + runBlocking { + action.callSuspend(module) + } + } else { + action?.call(module) + } + } + + /** + * Closes this resource, relinquishing any underlying resources. + * This method is invoked automatically on objects managed by the + * `try`-with-resources statement. + * + * @apiNote + * While this interface method is declared to throw `Exception`, implementers are *strongly* encouraged to + * declare concrete implementations of the `close` method to + * throw more specific exceptions, or to throw no exception at all + * if the close operation cannot fail. + * + * + * Cases where the close operation may fail require careful + * attention by implementers. It is strongly advised to relinquish + * the underlying resources and to internally *mark* the + * resource as closed, prior to throwing the exception. The `close` method is unlikely to be invoked more than once and so + * this ensures that the resources are released in a timely manner. + * Furthermore it reduces problems that could arise when the resource + * wraps, or is wrapped, by another resource. + * + * + * *Implementers of this interface are also strongly advised + * to not have the `close` method throw [ ].* + * + * This exception interacts with a thread's interrupted status, + * and runtime misbehavior is likely to occur if an `InterruptedException` is [ suppressed][Throwable.addSuppressed]. + * + * More generally, if it would cause problems for an + * exception to be suppressed, the `AutoCloseable.close` + * method should not throw it. + * + * + * Note that unlike the [close][java.io.Closeable.close] + * method of [java.io.Closeable], this `close` method + * is *not* required to be idempotent. In other words, + * calling this `close` method more than once may have some + * visible side effect, unlike `Closeable.close` which is + * required to have no effect if called more than once. + * + * However, implementers of this interface are strongly encouraged + * to make their `close` methods idempotent. + * + * @throws Exception if this resource cannot be closed + */ + override fun close() { + if (closable != null && closable.isSuspend) + throw IllegalStateException("@Closeable method cannot be suspended.") + + closable?.call(module) + } + } + + modules[module::class] = mod + mod.init() + + log.debug("Registered module ${mod.name} by ${mod.author}") + } + + fun > unregister(module: T) { + if (!modules.containsKey(module)) + throw IllegalStateException("Module $module was not registered.") + + val mod = modules[module]!! + mod.close() + + modules.remove(module) + } + + fun unregisterAll() { + for (module in modules.values) { + module.close() + } + } + + companion object { + var CURRENT: Registry? = null + + inline fun inject() = CURRENT?.inject() ?: error("Registry wasn't initialized properly.") + } +} diff --git a/modules/src/main/kotlin/sh/nino/modules/annotations/Action.kt b/modules/src/main/kotlin/sh/nino/modules/annotations/Action.kt new file mode 100644 index 00000000..f9f98853 --- /dev/null +++ b/modules/src/main/kotlin/sh/nino/modules/annotations/Action.kt @@ -0,0 +1,31 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.annotations + +/** + * Represents the first action that is executed when the module is being registered + * in the registry. + */ +@Target(AnnotationTarget.FUNCTION) +annotation class Action diff --git a/modules/src/main/kotlin/sh/nino/modules/annotations/Closeable.kt b/modules/src/main/kotlin/sh/nino/modules/annotations/Closeable.kt new file mode 100644 index 00000000..4aa9a216 --- /dev/null +++ b/modules/src/main/kotlin/sh/nino/modules/annotations/Closeable.kt @@ -0,0 +1,30 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.annotations + +/** + * Annotation to represent the close/0 method. + */ +@Target(AnnotationTarget.FUNCTION) +annotation class Closeable diff --git a/modules/src/main/kotlin/sh/nino/modules/annotations/ModuleMeta.kt b/modules/src/main/kotlin/sh/nino/modules/annotations/ModuleMeta.kt new file mode 100644 index 00000000..7f30d06a --- /dev/null +++ b/modules/src/main/kotlin/sh/nino/modules/annotations/ModuleMeta.kt @@ -0,0 +1,39 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.annotations + +/** + * Represents the metadata of this specific module. + * @param name The name of the module. + * @param description Short description of what this module is for. + * @param version The current version of the module. + * @param author The author of the module. + */ +@Target(AnnotationTarget.CLASS) +annotation class ModuleMeta( + val name: String, + val description: String = "This module has no description.", + val version: String = "1.0.0", + val author: String = "Nino Team" +) diff --git a/modules/src/test/kotlin/sh/nino/tests/modules/DummyErrorModule.kt b/modules/src/test/kotlin/sh/nino/tests/modules/DummyErrorModule.kt new file mode 100644 index 00000000..b7eacb8d --- /dev/null +++ b/modules/src/test/kotlin/sh/nino/tests/modules/DummyErrorModule.kt @@ -0,0 +1,26 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.tests.modules + +class DummyErrorModule diff --git a/modules/src/test/kotlin/sh/nino/tests/modules/ModuleTest.kt b/modules/src/test/kotlin/sh/nino/tests/modules/ModuleTest.kt new file mode 100644 index 00000000..d7cb6831 --- /dev/null +++ b/modules/src/test/kotlin/sh/nino/tests/modules/ModuleTest.kt @@ -0,0 +1,98 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@file:Suppress("UNUSED") + +package sh.nino.tests.modules + +import io.kotest.assertions.throwables.shouldNotThrow +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.AnnotationSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import sh.nino.modules.Registry + +class ModuleTest: AnnotationSpec() { + private val registry: Registry = Registry() + private val testModule = TestModule() + + @Test + fun `dummy module should error`() { + val dummy = DummyErrorModule() + val exception = shouldThrow { + registry.register(dummy) + } + + exception.message shouldBe "Cannot find @ModuleMeta annotation." + } + + @Test + fun `test module should not error when registered`() { + val exception = shouldNotThrow { + registry.register(testModule) + } + + val exception2 = shouldThrow { + registry.register(testModule) + } + + exception shouldNotBe null + exception2.message shouldContain "already registered" + } + + @Test + fun `getOrNull should not be null`() { + registry.register(testModule) + + val test0 = registry[TestModule::class] + test0 shouldNotBe null + + val test = registry.getOrNull() + test shouldNotBe null + test!!.current.test shouldBe true + + registry.unregister(TestModule::class) + } + + @Test + fun `readonlyproperty should work`() { + registry.register(testModule) + + val test: TestModule by registry.inject() + test.test shouldBe true + + registry.unregister(TestModule::class) + } + + @Test + fun `test module should not error when unregistering`() { + shouldNotThrow { + registry.unregister(TestModule::class) + } + + shouldThrow { + registry.unregister(DummyErrorModule::class) + } + } +} diff --git a/modules/src/test/kotlin/sh/nino/tests/modules/TestModule.kt b/modules/src/test/kotlin/sh/nino/tests/modules/TestModule.kt new file mode 100644 index 00000000..cee10b44 --- /dev/null +++ b/modules/src/test/kotlin/sh/nino/tests/modules/TestModule.kt @@ -0,0 +1,43 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.tests.modules + +import sh.nino.modules.annotations.Action +import sh.nino.modules.annotations.Closeable +import sh.nino.modules.annotations.ModuleMeta + +@ModuleMeta("owo", "uwu", "1.0.0", "Owo Team") +class TestModule { + val test = true + + @Action + fun uwu() { + println("owo") + } + + @Closeable + fun close() { + println("closed!") + } +} diff --git a/modules/timeouts/build.gradle.kts b/modules/timeouts/build.gradle.kts new file mode 100644 index 00000000..0af9812e --- /dev/null +++ b/modules/timeouts/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +plugins { + `nino-module` +} + +dependencies { + implementation(project(":modules")) +} diff --git a/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/Client.kt b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/Client.kt new file mode 100644 index 00000000..3daee0af --- /dev/null +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/Client.kt @@ -0,0 +1,198 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.timeouts + +import gay.floof.utils.slf4j.logging +import io.ktor.client.* +import io.ktor.client.plugins.websocket.* +import io.ktor.client.request.* +import io.ktor.websocket.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.json.* +import sh.nino.modules.timeouts.types.ApplyEvent +import sh.nino.modules.timeouts.types.Command +import sh.nino.modules.timeouts.types.Event +import sh.nino.modules.timeouts.types.OperationType +import sh.nino.modules.timeouts.types.ReadyEvent +import sh.nino.modules.timeouts.types.Response +import sh.nino.modules.timeouts.types.StatsResponse +import sh.nino.modules.timeouts.types.Timeout +import sh.nino.modules.timeouts.types.TimeoutsResponse +import sh.nino.modules.timeouts.types.fromJsonObject +import kotlin.time.Duration.Companion.seconds + +/** + * Represents the main gateway connection towards the Timeouts microservice. + */ +class Client( + private val uri: String, + private val auth: String = "", + private val httpClient: HttpClient, + private val coroutineScope: CoroutineScope, + private val eventFlow: MutableSharedFlow, + private val json: Json +): CoroutineScope by coroutineScope, AutoCloseable { + private val closeDeferred = CompletableDeferred() + private var messageFlowJob: Job? = null + private val log by logging() + private var session: DefaultWebSocketSession? = null + + private val coroutineExceptionHandler = CoroutineExceptionHandler { ctx, t -> + log.error("Exception in coroutine $ctx:", t) + } + + var closed = false + + private suspend fun createMessageFlow() { + log.debug("Creating event loop...") + + session!!.incoming.receiveAsFlow().collect { + if ((it as? Frame.Close) != null) { + onClose(it) + return@collect + } + + val raw = (it as? Frame.Text)?.readText() ?: error("Frame was not `Frame.Text`") + val decoded = json.decodeFromString(JsonObject.serializer(), raw) + + onMessage(decoded, raw) + } + } + + private suspend fun onClose(frame: Frame.Close) { + val reason = frame.readReason() ?: return + log.warn("Server closed connection (${reason.code} ${reason.message}), re-connecting in 5 minutes...") + + closed = true + session = null + withTimeout(5.seconds) { + connect() + } + } + + private suspend fun onMessage(data: JsonObject, raw: String) { + val type = data["op"]?.jsonPrimitive?.intOrNull + log.trace("data: $raw") + + if (type == null) { + log.error("Received message but missing `op`: int value.") + return + } + + when (OperationType[type]) { + is OperationType.Apply -> { + val timeout = Timeout.fromJsonObject(data) + eventFlow.emit(ApplyEvent(this, timeout)) + } + } + } + + private suspend fun connectionHandler(sess: DefaultWebSocketSession) { + log.info("Connected to WebSocket using URI - 'ws://$uri'!") + session = sess + + // Receive the first message + val message = try { + sess.incoming.receive().readBytes().decodeToString() + } catch (e: Exception) { + null + } + + if (message == null) { + onClose(Frame.Close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Server still down."))) + return + } + + // If it was closed, let's not make it closed. >:( + if (closed) + closed = false + + val obj = json.decodeFromString(JsonObject.serializer(), message) + if (obj["op"]?.jsonPrimitive?.int == 0) { + log.debug("Received READY event from server, hello world!") + + eventFlow.emit(ReadyEvent(this)) + messageFlowJob = coroutineScope.launch(coroutineExceptionHandler) { + createMessageFlow() + } + + closeDeferred.await() + + log.warn("Shutting connection...") + messageFlowJob?.cancelAndJoin() + sess.close(CloseReason(CloseReason.Codes.GOING_AWAY, "Connection was closed by user.")) + } + } + + suspend fun send(command: Command) { + val data = json.encodeToString(Command.Companion, command) + log.trace("Sending data >> $data") + + session!!.send(Frame.Text(data)) + } + + suspend fun sendAndReceive(command: Command): T { + val data = json.encodeToString(Command.Companion, command) + log.trace("Sending data >> $data") + + session!!.send(Frame.Text(data)) + + val resp = session!!.incoming.receive().readBytes().decodeToString() + val obj = json.decodeFromString(JsonObject.serializer(), resp) + + when (val op = obj["op"]?.jsonPrimitive?.int) { + OperationType.Stats.code -> { + @Suppress("UNCHECKED_CAST") + return json.decodeFromJsonElement(StatsResponse.serializer(), obj["d"]!!) as T + } + + OperationType.RequestAll.code -> { + val timeouts = json.decodeFromJsonElement(ListSerializer(Timeout.serializer()), obj["d"]!!) + + @Suppress("UNCHECKED_CAST") + return TimeoutsResponse(timeouts) as T + } + + else -> error("Unexpected operation type when sending command $command: $op") + } + } + + suspend fun connect() { + log.debug("Connecting to timeouts microservice using URI - 'ws://$uri'...") + httpClient.ws("ws://$uri", { + if (auth.isNotEmpty()) header("Authorization", auth) + }) { + connectionHandler(this) + } + } + + override fun close() { + if (closed) return + + closeDeferred.complete(Unit) + } +} diff --git a/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/TimeoutsModule.kt b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/TimeoutsModule.kt new file mode 100644 index 00000000..e071ef6a --- /dev/null +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/TimeoutsModule.kt @@ -0,0 +1,144 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.timeouts + +import gay.floof.utils.slf4j.logging +import io.ktor.client.* +import io.sentry.Sentry +import io.sentry.kotlin.SentryContext +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.* +import kotlinx.serialization.json.Json +import org.slf4j.LoggerFactory +import sh.nino.modules.annotations.Action +import sh.nino.modules.annotations.Closeable +import sh.nino.modules.annotations.ModuleMeta +import sh.nino.modules.timeouts.types.Command +import sh.nino.modules.timeouts.types.Event +import sh.nino.modules.timeouts.types.Response +import kotlin.coroutines.CoroutineContext + +/** + * The main module that can call the Timeouts microservice. + */ +@ModuleMeta( + "timeouts", + "The client implementation of Nino's timeouts microservice.", + version = "2.0.0" +) +class TimeoutsModule( + private val uri: String, + private val auth: String?, + private val httpClient: HttpClient, + private val json: Json +) { + private val log by logging() + lateinit var client: Client + + /** + * Returns if the connection was already closed. + */ + val closed: Boolean = if (::client.isInitialized) client.closed else true + + /** + * Returns the event flow that can be called with [TimeoutsModule.on]! + */ + val events = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) + + @OptIn(DelicateCoroutinesApi::class) + @Suppress("UNUSED") + @Action + suspend fun init() { + if (::client.isInitialized) { + log.warn("TimeoutsModule.init/0 was already called, skipping.") + return + } + + client = Client( + uri, + auth ?: "", + httpClient, + GlobalScope, + events, + json + ) + + // Create a new coroutine scope for this, so it doesn't + // block the main thread :> + GlobalScope.launch { + client.connect() + } + } + + @Closeable + fun close() { + // If it was already closed before, let's make it nop. + if (closed) return + + // If the client wasn't initialized (probably an exception occurred), + // this will be nop. + if (!::client.isInitialized) { + log.warn("Timeouts microservice connection was not established, skipping.") + return + } + + log.info("Closing off connection from timeouts microservice...") + return client.close() + } + + suspend fun send(command: Command) { + if (closed) return + if (!::client.isInitialized) return + + return client.send(command) + } + + suspend fun sendAndReceive(command: Command): T? { + if (closed) return null + if (!::client.isInitialized) return null + + return client.sendAndReceive(command) + } +} + +@OptIn(DelicateCoroutinesApi::class) +inline fun TimeoutsModule.on(scope: CoroutineScope = client, noinline consume: suspend T.() -> Unit): Job = + events.buffer(Channel.UNLIMITED).filterIsInstance() + .onEach { event -> + val ctx: CoroutineContext = if (Sentry.isEnabled()) { + SentryContext() + GlobalScope.coroutineContext + } else { + GlobalScope.coroutineContext + } + + scope.launch(ctx) { + kotlin.runCatching { + consume(event) + }.onFailure { + val log = LoggerFactory.getLogger(TimeoutsModule::class.java) + log.error("Unable to run event ${event::class}:", it) + } + } + }.launchIn(scope) diff --git a/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Commands.kt b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Commands.kt new file mode 100644 index 00000000..09c5ed13 --- /dev/null +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Commands.kt @@ -0,0 +1,73 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.timeouts.types + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject + +/** + * Represents a command to send to the server. + */ +open class Command { + companion object: SerializationStrategy { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("sh.nino.timeouts.Command") { + element("op", OperationType.serializer().descriptor) + element("d", JsonObject.serializer().descriptor) + } + + override fun serialize(encoder: Encoder, value: Command) { + val composite = encoder.beginStructure(descriptor) + when (value) { + is RequestCommand -> { + composite.encodeSerializableElement(descriptor, 0, OperationType.serializer(), OperationType.Request) + composite.encodeSerializableElement(descriptor, 1, RequestCommand.serializer(), value) + } + + is RequestAllCommand -> { + composite.encodeSerializableElement(descriptor, 0, OperationType.serializer(), OperationType.Request) + composite.encodeSerializableElement(descriptor, 1, JsonObject.serializer(), buildJsonObject {}) + } + + is StatsCommand -> { + composite.encodeSerializableElement(descriptor, 0, OperationType.serializer(), OperationType.Stats) + composite.encodeSerializableElement(descriptor, 1, JsonObject.serializer(), buildJsonObject {}) + } + } + } + } +} + +@Serializable +class RequestCommand(val timeout: Timeout): Command() + +@Serializable +class RequestAllCommand: Command() + +@Serializable +class StatsCommand: Command() diff --git a/src/migrations/1622346188448-addAttachmentColumn.ts b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Events.kt similarity index 67% rename from src/migrations/1622346188448-addAttachmentColumn.ts rename to modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Events.kt index 3c29a727..5767354f 100644 --- a/src/migrations/1622346188448-addAttachmentColumn.ts +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Events.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,16 +21,21 @@ * SOFTWARE. */ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class addAttachmentColumn1622346188448 implements MigrationInterface { - name = 'addAttachmentColumn1622346188448'; +package sh.nino.modules.timeouts.types - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "cases" ADD "attachments" text array NOT NULL DEFAULT \'{}\''); - } +import sh.nino.modules.timeouts.Client - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "attachments"'); - } +interface Event { + val client: Client } + +/** + * This indicates that the connection was successful. + */ +class ReadyEvent(override val client: Client): Event + +/** + * This indicates that a timeout packet has fulfilled its lifetime, and we need to do a + * reverse operation. + */ +class ApplyEvent(override val client: Client, val timeout: Timeout): Event diff --git a/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/OperationType.kt b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/OperationType.kt new file mode 100644 index 00000000..d5b2a573 --- /dev/null +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/OperationType.kt @@ -0,0 +1,89 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.timeouts.types + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +/** + * Represents the operation type of command or payload. + */ +@Serializable(with = OperationType.Companion.Serializer::class) +open class OperationType(val code: Int) { + /** + * This is a **server -> client** operation code. + * + * This indicates that the connection was successful. You will be emitted a [ReadyEvent] + * event. + */ + object Ready: OperationType(0) + + /** + * This is a **server -> client** operation code. + * + * This indicates that a timeout packet has fulfilled its lifetime, and we need to do a + * reverse operation. You will be emitted a [ApplyEvent] event. + */ + object Apply: OperationType(1) + + /** + * This is a **client -> server** operation code. + * + * This creates a single timeout to the server itself. + */ + object Request: OperationType(2) + + /** + * This is a **client -> server** operation code. + * + * Requests all the timeouts that are being handled by the server. + */ + object RequestAll: OperationType(3) + + /** + * This is a **client -> server** operation code. + * + * This returns statistics about the microservice including the runtime, the ping from client -> server (for Instatus), + * and more. + */ + object Stats: OperationType(4) + + companion object { + object Serializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("sh.nino.timeouts.OperationType", PrimitiveKind.INT) + override fun deserialize(decoder: Decoder): OperationType = get(decoder.decodeInt()) + override fun serialize(encoder: Encoder, value: OperationType) { + encoder.encodeInt(value.code) + } + } + + private val operationTypes = setOf(Ready, Apply, RequestAll, Stats) + operator fun get(code: Int): OperationType = operationTypes.find { it.code == code } ?: error("Unknown operation type: $code") + } +} diff --git a/src/entities/GuildEntity.ts b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Responses.kt similarity index 65% rename from src/entities/GuildEntity.ts rename to modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Responses.kt index 7140effe..d4692c52 100644 --- a/src/entities/GuildEntity.ts +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Responses.kt @@ -1,5 +1,6 @@ -/** - * Copyright (c) 2019-2021 Nino +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,22 +21,24 @@ * SOFTWARE. */ -import { Entity, Column, PrimaryColumn } from 'typeorm'; +package sh.nino.modules.timeouts.types -@Entity({ name: 'guilds' }) -export default class GuildEntity { - @Column({ default: null, nullable: true, name: 'modlog_channel_id' }) - public modlogChannelID?: string; +import kotlinx.serialization.SerialName - @Column({ default: null, nullable: true, name: 'muted_role_id' }) - public mutedRoleID?: string; +interface Response - @Column({ array: true, type: 'text' }) - public prefixes!: string[]; +@kotlinx.serialization.Serializable +data class StatsResponse( + @SerialName("go_version") + val goVersion: String, - @Column({ default: 'en_US' }) - public language!: string; + @SerialName("commit_sha") + val commitSha: String, - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; -} + @SerialName("build_date") + val buildDate: String, + val version: String +): Response + +@kotlinx.serialization.Serializable +data class TimeoutsResponse(val timeouts: List): Response diff --git a/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Timeout.kt b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Timeout.kt new file mode 100644 index 00000000..6208367c --- /dev/null +++ b/modules/timeouts/src/main/kotlin/sh/nino/modules/timeouts/types/Timeout.kt @@ -0,0 +1,63 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sh.nino.modules.timeouts.types + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long + +/** + * Represents the timeout as a serializable object. ([source](https://github.com/NinoDiscord/timeouts/blob/master/pkg/types.go#L38-L46)) + */ +@Serializable +data class Timeout( + @SerialName("guild_id") + val guildId: String, + + @SerialName("user_id") + val userId: String, + + @SerialName("issued_at") + val issuedAt: Long, + + @SerialName("expires_at") + val expiresIn: Long, + + @SerialName("moderator_id") + val moderatorId: String, + val reason: String? = null, + val type: String +) + +fun Timeout.Companion.fromJsonObject(data: JsonObject): Timeout = Timeout( + guildId = data["guild_id"]!!.jsonPrimitive.content, + userId = data["user_id"]!!.jsonPrimitive.content, + issuedAt = data["issued_at"]!!.jsonPrimitive.long, + expiresIn = data["expires_in"]!!.jsonPrimitive.long, + moderatorId = data["moderator_id"]!!.jsonPrimitive.content, + reason = data["reason"]?.jsonPrimitive?.content, + type = data["type"]!!.jsonPrimitive.content +) diff --git a/ormconfig.js b/ormconfig.js deleted file mode 100644 index 88023984..00000000 --- a/ormconfig.js +++ /dev/null @@ -1,35 +0,0 @@ -const { parse } = require('@augu/dotenv'); -const { join } = require('path'); - -const config = parse({ - populate: false, - file: join(__dirname, '.env'), - - schema: { - DATABASE_USERNAME: 'string', - DATABASE_PASSWORD: 'string', - DATABASE_NAME: 'string', - DATABASE_HOST: 'string', - DATABASE_PORT: 'int', - NODE_ENV: { - type: 'string', - default: ['development', 'production'], - }, - }, -}); - -module.exports = { - migrations: ['./build/migrations/*.js'], - username: config.DATABASE_USERNAME, - password: config.DATABASE_PASSWORD, - entities: ['./build/entities/*.js'], - database: config.DATABASE_NAME, - logging: false, // enable this when the deprecated message is gone - type: 'postgres', - host: config.DATABASE_HOST, - port: config.DATABASE_PORT, - - cli: { - migrationsDir: 'src/migrations', - }, -}; diff --git a/package.json b/package.json deleted file mode 100644 index 55797b33..00000000 --- a/package.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "name": "nino", - "description": "🔨 Advanced and cute moderation discord bot as an entry of Discord's Hack Week", - "version": "1.4.3", - "private": true, - "homepage": "https://nino.floofy.dev", - "license": "MIT", - "repository": "https://github.com/NinoDiscord/Nino", - "main": "build/main.js", - "author": "August ", - "prettier": "@augu/prettier-config", - "maintainers": [ - { - "email": "cutie@floofy.dev", - "name": "August", - "url": "https://floofy.dev" - } - ], - "engines": { - "node": ">=14" - }, - "scripts": { - "clean:node_modules": "rm -rf node_modules/**/node_modules && rm -rf node_modules/@types/**/node_modules && rm -rf node_modules/@augu/**/node_modules", - "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.*", - "husky:install": "husky install && rm .husky/.gitignore", - "build:no-lint": "eslint src --ext .ts && rm -rf build && tsc", - "migrations": "yarn build && typeorm migration:run", - "shortlinks": "node scripts/shortlinks.js", - "licenses": "node scripts/add-license.js", - "prepare": "yarn clean:node_modules", - "build": "yarn lint && yarn format && rm -rf build && tsc", - "format": "prettier --write --parser typescript --config ./prettierrc.js src/**/*.ts", - "start": "cd build && node main.js", - "lint": "eslint src --ext .ts --fix", - "dev": "cd src && nodemon --exec \"ts-node --project ../tsconfig.json --files\" main.ts" - }, - "dependencies": { - "@augu/collections": "1.0.12", - "@augu/dotenv": "1.3.0", - "@augu/lilith": "5.0.4", - "@augu/orchid": "3.1.1", - "@augu/utils": "1.5.3", - "@sentry/node": "6.10.0", - "eris": "github:abalabahaha/eris#dev", - "fastify": "3.20.1", - "fastify-no-icon": "4.0.0", - "ioredis": "4.27.6", - "js-yaml": "4.1.0", - "luxon": "2.0.1", - "ms": "2.1.3", - "pg": "8.7.1", - "prom-client": "13.1.0", - "reflect-metadata": "0.1.13", - "slash-commands": "1.5.0", - "source-map-support": "0.5.19", - "tslog": "3.2.0", - "typeorm": "0.2.31", - "ws": "8.0.0" - }, - "devDependencies": { - "@augu/eslint-config": "2.2.0", - "@augu/prettier-config": "1.0.2", - "@augu/tsconfig": "1.1.1", - "@types/ioredis": "4.26.6", - "@types/js-yaml": "4.0.2", - "@types/luxon": "1.27.1", - "@types/ms": "0.7.31", - "@types/node": "15.3.1", - "@types/ws": "7.4.7", - "@typescript-eslint/eslint-plugin": "4.29.0", - "@typescript-eslint/parser": "4.29.0", - "discord-api-types": "0.22.0", - "eslint": "7.32.0", - "eslint-config-prettier": "8.3.0", - "eslint-plugin-prettier": "3.4.0", - "husky": "7.0.1", - "nodemon": "2.0.12", - "prettier": "2.3.2", - "ts-node": "10.1.0", - "typescript": "4.3.5" - } -} diff --git a/renovate.json b/renovate.json index d66fcce8..9777658a 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,5 @@ "extends": ["config:base"], "automerge": true, "baseBranches": ["edge"], - "ignoreDeps": ["typeorm"] + "ignoreDeps": ["is-docker"] } diff --git a/scripts/add-license.js b/scripts/add-license.js deleted file mode 100644 index 17e182d8..00000000 --- a/scripts/add-license.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -const { readdir } = require('@augu/utils'); -const path = require('path'); -const fs = require('fs'); - -const LICENSE = `/** - * Copyright (c) 2019-${new Date().getFullYear()} Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -`; - -const main = async () => { - console.log('adding licenses at the top of files...'); - const files = await Promise.all([ - readdir(path.join(__dirname, '..', 'src')), - readdir(path.join(__dirname, '..', 'scripts')), - ]).then((arr) => arr.flat()); - - for (const file of files) { - console.log(`Adding license to ${file}...`); - const content = fs.readFileSync(file, 'utf8'); - const raw = content.includes('* Copyright (c)') - ? content.split('\n').slice(22).join('\n') - : content; - - await fs.promises.writeFile(file, LICENSE + raw); - - console.log(`Added license to ${file} :D`); - } -}; - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 00000000..7562e99c --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "[::clean] Cleaning \`build\` directories..." + +rm -rf build +rm -rf {bot,api,automod,commands,database,commons,core,database,modules}/build +rm -rf modules/{localisation,metrics,punishments,ravy,scripting,timeouts}/build +rm -rf commands/{legacy,slash}/build + +echo "[::clean] Done!" diff --git a/scripts/migrate.js b/scripts/migrate.js deleted file mode 100644 index 216bb99b..00000000 --- a/scripts/migrate.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -const { LoggerWithoutCallSite } = require('tslog'); -const { calculateHRTime, readdir } = require('@augu/utils'); -const determineCaseType = require('./util/getCaseType'); -const getRepositories = require('./util/getRepositories'); -const { existsSync } = require('fs'); -const { readFile } = require('fs/promises'); -const { createConnection } = require('typeorm'); - -const { - default: PunishmentsEntity, -} = require('../build/entities/PunishmentsEntity'); -const { default: AutomodEntity } = require('../build/entities/AutomodEntity'); -const { default: CaseEntity } = require('../build/entities/CaseEntity'); -const { default: GuildEntity } = require('../build/entities/GuildEntity'); -const { default: WarningsEntity } = require('../build/entities/WarningsEntity'); -const { - default: LoggingEntity, - LoggingEvents, -} = require('../build/entities/LoggingEntity'); -const { default: UserEntity } = require('../build/entities/UserEntity'); - -const argv = process.argv.slice(2); -const logger = new LoggerWithoutCallSite({ - displayFunctionName: true, - exposeErrorCodeFrame: true, - displayInstanceName: true, - displayFilePath: false, - dateTimePattern: '[ day-month-year / hour:minute:second ]', - instanceName: 'script: v0 -> v1', - name: 'scripts', -}); - -async function main() { - logger.info('Welcome to the database conversion script!'); - logger.info( - 'This script takes care of converting the Mongo database to the PostgreSQL one!' - ); - - if (argv[0] === undefined) { - logger.fatal( - 'You are required to output a directory after `node scripts/migrate.js`.' - ); - process.exit(1); - } - - const directory = argv[0]; - if (!existsSync(directory)) { - logger.fatal(`Directory ${argv[0]} doesn't exist.`); - process.exit(1); - } - - const files = await readdir(directory); - if (!files.every((file) => file.endsWith('.json'))) { - logger.fatal('Every file should end with ".json"'); - process.exit(1); - } - - logger.info('Creating PostgreSQL instance...'); - const connection = await createConnection(); - - const startTime = process.hrtime(); - const guilds = files.find((file) => file.endsWith('guilds.json')); - const guildData = await readFile(guilds, 'utf-8'); - const guildDocs = JSON.parse(guildData); - await convertGuilds(connection, guildDocs); - - const users = files.find((file) => file.endsWith('users.json')); - const userData = await readFile(users, 'utf-8'); - const userDocs = JSON.parse(userData); - await convertUsers(connection, userDocs); - - const warnings = files.find((file) => file.endsWith('warnings.json')); - const warningData = await readFile(warnings, 'utf-8'); - const warningDocs = JSON.parse(warningData); - await convertWarnings(connection, warningDocs); - - const cases = files.find((file) => file.endsWith('cases.json')); - const caseData = await readFile(cases, 'utf-8'); - const caseDocs = JSON.parse(caseData); - await convertCases(connection, caseDocs); - - logger.info( - `Converted ${userDocs.length} users, ${guildDocs.length} guilds, ${ - warningDocs.length - } warnings, and ${caseDocs.length} cases in ~${calculateHRTime( - startTime - )}ms.` - ); - process.exit(0); -} - -async function convertGuilds(connection, documents) { - logger.info(`Found ${documents.length} documents to convert...`); - const { guilds, punishments, automod, logging } = getRepositories(connection); - - const start = process.hrtime(); - for (let i = 0; i < documents.length; i++) { - const document = documents[i]; - - logger.info( - `Guild Entry: ${document.guildID} ${i + 1}/${documents.length}` - ); - const entry = new GuildEntity(); - entry.language = document.locale; - entry.prefixes = [document.prefix]; - entry.guildID = document.guildID; - - if (document.modlog !== undefined) entry.modlogChannelID = document.modlog; - - if (document.mutedRole !== undefined) - entry.mutedRoleID = document.mutedRole; - - await guilds.save(entry); - - logger.info(`Converting ${document.punishments.length} punishments...`); - for (const punishment of document.punishments) { - if (punishment.type === 'unrole' || punishment.type === 'role') { - logger.warn('Removing legacy punishment...', punishment); - continue; - } - - const entry = new PunishmentsEntity(); - entry.warnings = punishment.warnings; - entry.guildID = document.guildID; - entry.soft = punishment.soft === true; - entry.type = determineCaseType(punishment.type); - - if (punishment.temp !== null) entry.time = punishment.temp; - - await punishments.save(entry); - } - - logger.info('Converting automod actions...'); - const automodEntry = new AutomodEntity(); - automodEntry.blacklistWords = document.automod.badwords?.wordlist ?? []; - automodEntry.blacklist = document.automod.badwords?.enabled ?? false; - automodEntry.shortLinks = false; // wasn't introduced in v0 - automodEntry.mentions = document.automod.mention ?? false; - automodEntry.invites = document.automod.invites ?? false; - automodEntry.dehoist = document.automod.dehoist ?? false; - automodEntry.guildID = document.guildID; - automodEntry.spam = document.automod.spam ?? false; - automodEntry.raid = document.automod.raid ?? false; - - await automod.save(automodEntry); - - logger.info('Converting logging actions...'); - const loggingEntry = new LoggingEntity(); - loggingEntry.guildID = document.guildID; - - const _logging = document.logging ?? { enabled: false }; - if (!_logging.enabled) { - loggingEntry.enabled = false; - await logging.save(loggingEntry); - } else { - const events = []; - - loggingEntry.enabled = true; - loggingEntry.channelID = _logging.channelID ?? null; - loggingEntry.ignoreUsers = _logging.ignore ?? []; - loggingEntry.ignoreChannels = _logging.ignoreChannels ?? []; - - if (_logging.events.messageDelete === true) - events.push(LoggingEvents.MessageDeleted); - - if (_logging.events.messageUpdate === true) - events.push(LoggingEvents.MessageUpdated); - - await logging.save(loggingEntry); - } - } - - logger.info( - `Hopefully migrated ${documents.length} guild documents (~${calculateHRTime( - start - ).toFixed(2)}ms)` - ); -} - -async function convertUsers(connection, documents) { - logger.info(`Found ${documents.length} users to convert.`); - const { users } = getRepositories(connection); - - const startTime = process.hrtime(); - for (let i = 0; i < documents.length; i++) { - const document = documents[i]; - logger.info( - `User Entry: ${document.userID} (${i + 1}/${documents.length})` - ); - - const entry = new UserEntity(); - entry.language = document.locale; - entry.prefixes = []; - entry.id = document.userID; - - const user = await users.find({ id: document.userID }); - await users.save(entry); - } - - logger.info( - `Hopefully migrated ${documents.length} user documents (~${calculateHRTime( - startTime - ).toFixed(2)}ms)` - ); -} - -async function convertWarnings(connection, documents) { - logger.info(`Found ${documents.length} warnings to convert.`); - const { warnings } = getRepositories(connection); - - const startTime = process.hrtime(); - for (let i = 0; i < documents.length; i++) { - logger.info( - `Warning Entry: ${documents[i].guild} | ${documents[i].user} (${i + 1}/${ - documents.length - })` - ); - - const document = documents[i]; - const entry = new WarningsEntity(); - - entry.amount = document.amount; - entry.guildID = document.guild; - entry.userID = document.user; - if (typeof document.reason === 'string') entry.reason = document.reason; - - try { - await warnings.save(entry); - } catch (ex) { - logger.error('Unable to serialize input, setting to `1`:', ex); - - entry.amount = 1; - await warnings.save(entry); - } - } - - logger.info( - `Hopefully migrated ${ - documents.length - } warning documents (~${calculateHRTime(startTime).toFixed(2)}ms)` - ); -} - -async function convertCases(connection, documents) { - logger.info(`Found ${documents.length} cases to convert.`); - const { cases } = getRepositories(connection); - - const startTime = process.hrtime(); - for (let i = 0; i < documents.length; i++) { - const document = documents[i]; - logger.info( - `Case Entry: ${document.guild}, ${document.victim}, #${document.id}` - ); - - const entry = new CaseEntity(); - entry.moderatorID = document.moderator; - entry.messageID = document.message; - entry.victimID = document.victim; - entry.guildID = document.guild; - entry.index = document.id; - entry.type = determineCaseType(document.type) ?? document.type; - entry.soft = document.soft === true; - - if (document.reason !== null) entry.reason = document.reason; - - if (document.time !== undefined) entry.time = document.time; - - try { - await cases.save(entry); - } catch (ex) { - logger.info(`Skipping on entity #${document.id}: `, ex); - continue; - } - } - - logger.info( - `Hopefully migrated ${documents.length} case documents (~${calculateHRTime( - startTime - ).toFixed(2)}ms)` - ); -} - -main() - .then(process.exit) - .catch((ex) => { - logger.fatal(ex); - process.exit(1); - }); diff --git a/scripts/run-docker.sh b/scripts/run-docker.sh deleted file mode 100644 index d0e97578..00000000 --- a/scripts/run-docker.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -echo 'Running migrations' -typeorm migration:run - -echo 'Migrations and schemas should be synced.' -npm start diff --git a/scripts/shortlinks.js b/scripts/shortlinks.js deleted file mode 100644 index 47f6c3fb..00000000 --- a/scripts/shortlinks.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -const { LoggerWithoutCallSite } = require('tslog'); -const { calculateHRTime } = require('@augu/utils'); -const { existsSync } = require('fs'); -const { HttpClient } = require('@augu/orchid'); -const { version } = require('../package.json'); -const { join } = require('path'); -const fs = require('fs/promises'); - -const http = new HttpClient({ - userAgent: `Nino/DiscordBot (https://github.com/NinoDiscord/Nino, v${version})`, -}); - -const logger = new LoggerWithoutCallSite({ - displayFunctionName: true, - exposeErrorCodeFrame: true, - displayInstanceName: true, - displayFilePath: false, - dateTimePattern: '[ day-month-year / hour:minute:second ]', - instanceName: 'script: shortlinks', - name: 'scripts', -}); - -const otherUrls = [ - 'shorte.st', - 'adf.ly', - 'bc.vc', - 'soo.gd', - 'ouo.io', - 'zzb.bz', - 'adfoc.us', - 'goo.gl', - 'grabify.link', - 'shorturl.at', - 'tinyurl.com', - 'tinyurl.one', - 'rotf.lol', - 'bit.do', - 'is.gd', - 'owl.ly', - 'buff.ly', - 'mcaf.ee', - 'su.pr', - 'bfy.tw', - 'owo.vc', - 'tiny.cc', - 'rb.gy', - 'bl.ink', - 'v.gd', - 'vurl.com', - 'turl.ca', - 'shrunken.com', - 'p.asia', - 'g.asia', - '3.ly', - '0.gp', - '2.ly', - '4.gp', - '4.ly', - '6.ly', - '7.ly', - '8.ly', - '9.ly', - '2.gp', - '6.gp', - '5.gp', - 'ur3.us', - 'kek.gg', - 'waa.ai', - 'steamcommunity.ru', - 'steanconmunity.ru', -]; - -const startTime = process.hrtime(); -(async () => { - logger.info( - 'Now reading list from https://raw.githubusercontent.com/sambokai/ShortURL-Services-List/master/shorturl-services-list.csv...' - ); - - const requestTime = process.hrtime(); - const res = await http.get( - 'https://raw.githubusercontent.com/sambokai/ShortURL-Services-List/master/shorturl-services-list.csv' - ); - - const requestEnd = calculateHRTime(requestTime); - logger.debug( - `It took ~${requestEnd}ms to get a "${res.statusCode}" response.` - ); - - const data = res.body().split(/\n\r?/); - data.shift(); - - const shortlinks = [ - ...new Set( - [].concat( - data.map((s) => s.slice(0, s.length - 1)), - otherUrls - ) - ), - ].filter((s) => s !== ''); - if (!existsSync(join(__dirname, '..', 'assets'))) - await fs.mkdir(join(__dirname, '..', 'assets')); - - await fs.writeFile( - join(__dirname, '..', 'assets', 'shortlinks.json'), - `${JSON.stringify(shortlinks, null, '\t')}\n` - ); - logger.info( - `It took about ~${calculateHRTime(startTime)}ms to retrieve ${ - shortlinks.length - } short-links.` - ); - process.exit(0); -})(); diff --git a/scripts/update-punishments.js b/scripts/update-punishments.js deleted file mode 100644 index 97473493..00000000 --- a/scripts/update-punishments.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -const { createConnection } = require('typeorm'); -const { - default: PunishmentsEntity, -} = require('../build/entities/PunishmentsEntity'); - -async function main() { - console.log( - `[scripts:update-punishments | ${new Date().toLocaleTimeString()}] Updating punishments...` - ); - const connection = await createConnection(); - - // Retrieve all punishments - const repository = connection.getRepository(PunishmentsEntity); - const punishments = await repository.find({}); - const traversedGuilds = []; - let i = 0; - - await repository.delete({}); - - // Update punishments - console.log( - `[scripts:update-punishments | ${new Date().toLocaleTimeString()}] Found ${ - punishments.length - } punishments to update` - ); - for (const punishment of punishments) { - console.log( - `idx: ${i} | guild entry: ${ - traversedGuilds.find((c) => c.guild === punishment.guildID) !== - undefined - }` - ); - - if ( - traversedGuilds.find((c) => c.guild === punishment.guildID) !== undefined - ) { - const f = traversedGuilds.find((c) => c.guild === punishment.guildID); - const id = traversedGuilds.findIndex( - (c) => c.guild === punishment.guildID - ); - const entity = new PunishmentsEntity(); - - entity.warnings = punishment.warnings; - entity.guildID = punishment.guildID; - entity.index = f.index += 1; - entity.soft = punishment.soft; - entity.time = punishment.time; - entity.type = punishment.type; - entity.days = punishment.days; - - traversedGuilds[id] = { - guild: punishment.guildID, - index: (f.index += 1), - }; - await repository.save(entity); - } else { - const entity = new PunishmentsEntity(); - entity.warnings = punishment.warnings; - entity.guildID = punishment.guildID; - entity.index = 1; - entity.soft = punishment.soft; - entity.time = punishment.time; - entity.type = punishment.type; - entity.days = punishment.days; - - traversedGuilds.push({ guild: punishment.guildID, index: 1 }); - await repository.save(entity); - } - } -} - -main(); diff --git a/scripts/util/getRepositories.js b/scripts/util/getRepositories.js deleted file mode 100644 index 1c7b66dd..00000000 --- a/scripts/util/getRepositories.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -const { - default: PunishmentsEntity, -} = require('../../build/entities/PunishmentsEntity'); -const { - default: AutomodEntity, -} = require('../../build/entities/AutomodEntity'); -const { default: CaseEntity } = require('../../build/entities/CaseEntity'); -const { default: GuildEntity } = require('../../build/entities/GuildEntity'); -const { - default: WarningsEntity, -} = require('../../build/entities/WarningsEntity'); -const { - default: LoggingEntity, -} = require('../../build/entities/LoggingEntity'); -const { default: UserEntity } = require('../../build/entities/UserEntity'); - -/** - * Returns the repositories from the responding `connection`. - * @param {import('typeorm').Connection} connection The connection established - */ -module.exports = (connection) => ({ - punishments: connection.getRepository(PunishmentsEntity), - warnings: connection.getRepository(WarningsEntity), - logging: connection.getRepository(LoggingEntity), - automod: connection.getRepository(AutomodEntity), - guilds: connection.getRepository(GuildEntity), - cases: connection.getRepository(CaseEntity), - users: connection.getRepository(UserEntity), -}); diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..f9b830eb --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,42 @@ +/* + * 🔨 Nino: Cute, advanced discord moderation bot made in Kord. + * Copyright (c) 2019-2022 Nino Team + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +rootProject.name = "Nino" + +include( + ":api", + ":bot", + ":automod", + ":commands:legacy", + ":commands:slash", + ":commons", + ":core", + ":database", + ":modules", + ":modules:localisation", + ":modules:metrics", + ":modules:punishments", + ":modules:ravy", + ":modules:scripting", + ":modules:timeouts" +) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts deleted file mode 100644 index 42969cb1..00000000 --- a/src/@types/global.d.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import { Container } from '@augu/lilith'; -import { APIUser } from 'discord-api-types'; -import { crypto } from '../api/encryption'; - -declare global { - /** The current container running */ - var app: Container; - - namespace NodeJS { - interface Global { - /** The current container running */ - app: Container; - } - } - - interface APITokenResult extends Omit { - expiryDate: number; - data: APIUser; - id: string; - } - - interface RedisInfo { - total_connections_received: number; - total_commands_processed: number; - instantaneous_ops_per_sec: number; - total_net_input_bytes: number; - total_net_output_bytes: number; - instantaneous_input_kbps: number; - instantaneous_output_kbps: number; - rejected_connections: number; - sync_full: number; - sync_partial_ok: number; - sync_partial_err: number; - expired_keys: number; - expired_stale_perc: number; - expired_time_cap_reached_count: number; - evicted_keys: number; - keyspace_hits: number; - keyspace_misses: number; - pubsub_channels: number; - pubsub_patterns: number; - latest_fork_usec: number; - migrate_cached_sockets: number; - slave_expires_tracked_keys: number; - active_defrag_hits: number; - active_defrag_misses: number; - active_defrag_key_hits: number; - active_defrag_key_misses: number; - } - - interface RedisServerInfo { - redis_version: string; - redis_git_sha1: string; - redis_git_dirty: string; - redis_build_id: string; - redis_mode: string; - os: string; - arch_bits: string; - multiplexing_api: string; - atomicvar_api: string; - gcc_version: string; - process_id: string; - process_supervised: string; - run_id: string; - tcp_port: string; - server_time_usec: string; - uptime_in_seconds: string; - uptime_in_days: string; - hz: string; - configured_hz: string; - lru_clock: string; - executable: string; - config_file: string; - io_threads_active: string; - } -} diff --git a/src/@types/ioredis-lock.d.ts b/src/@types/ioredis-lock.d.ts deleted file mode 100644 index 67b32ab1..00000000 --- a/src/@types/ioredis-lock.d.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** */ -declare module 'ioredis-lock' { - import * as ioredis from 'ioredis'; - - interface RedisLockOptions { - timeout?: number; - retries?: number; - delay?: number; - } - - /** - * Creates and returns a new Lock instance, configured for use with the supplied redis client, as well as options, if provided. - * The options object may contain following three keys, as outlined at the start of the documentation: timeout, retries and delay. - */ - export function createLock(client: ioredis.Redis | ioredis.Cluster, options?: RedisLockOptions): Lock; - - /** - * Returns an array of currently active/acquired locks. - */ - export function getAcquiredLocks(): Promise; - - /** - * The constructor for a LockAcquisitionError. Thrown or returned when a lock - * could not be acquired. - */ - export class LockAcquisitionError extends Error { - constructor(message: string); - } - - /** - * The constructor for a LockReleaseError. Thrown or returned when a lock - * could not be released. - */ - export class LockReleaseError extends Error { - constructor(message: string); - } - - /** - * The constructor for a LockExtendError. Thrown or returned when a lock - * could not be extended. - */ - export class LockExtendError extends Error { - constructor(message: string); - } - - /** - * The constructor for a Lock object. Accepts both a redis client, as well as - * an options object with the following properties: timeout, retries and delay. - * Any options not supplied are subject to the current defaults. - */ - export class Lock { - public readonly config: RedisLockOptions; - - /** - * Attempts to acquire a lock, given a key, and an optional callback function. - * If the initial lock fails, additional attempts will be made for the - * configured number of retries, and padded by the delay. The callback is - * invoked with an error on failure, and returns a promise if no callback is - * supplied. If invoked in the context of a promise, it may throw a - * LockAcquisitionError. - * - * @param key The redis key to use for the lock - */ - public acquire(key: string): Promise; - - /** - * Attempts to extend the lock - * @param expire in `timeout` seconds - */ - public extend(expire: number): Promise; - - /** - * Attempts to release the lock, and accepts an optional callback function. - * The callback is invoked with an error on failure, and returns a promise - * if no callback is supplied. If invoked in the context of a promise, it may - * throw a LockReleaseError. - */ - public release(): Promise; - } -} diff --git a/src/@types/locale.d.ts b/src/@types/locale.d.ts deleted file mode 100644 index 343bbfd7..00000000 --- a/src/@types/locale.d.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -export {}; -/** */ -declare global { - /** - * Metadata for a locale, this is used in the "meta" object - */ - interface LocalizationMeta { - /** List of contributors (by user ID) who helped translate or fix minor issues with this Locale */ - contributors: string[]; - - /** The translator's ID */ - translator: string; - - /** Any additional aliases to use when setting or resetting a locale */ - aliases: string[]; - - /** The flag's emoji (example: `:flag_us:`) */ - flag: string; - - /** The full name of the Locale (i.e `English (UK)`) */ - full: string; - - /** The locale's code (i.e `en_US`) */ - code: string; - } - - interface LocalizationStrings { - descriptions: LocalizationStrings.Descriptions; - commands: LocalizationStrings.Commands; - automod: LocalizationStrings.Automod; - generic: LocalizationStrings.Generic; - errors: LocalizationStrings.Errors; - } - - namespace LocalizationStrings { - export interface Descriptions { - // Unknown - unknown: string; - - // Core - help: string; - invite: string; - locale: string; - ping: string; - shardinfo: string; - source: string; - statistics: string; - uptime: string; - - // Moderation - ban: string; - case: string; - kick: string; - mute: string; - pardon: string; - purge: string; - reason: string; - softban: string; - timeouts: string; - unban: string; - unmute: string; - warn: string; - warnings: string; - voice_mute: string; - voice_deafen: string; - voice_undeafen: string; - voice_unmute: string; - - // Settings - automod: string; - logging: string; - modlog: string; - muted_role: string; - prefix: string; - punishments: string; - } - - export interface Commands { - help: { - embed: { - title: string; - description: string[]; - fields: { - moderation: string; - core: string; - settings: string; - }; - }; - - command: { - not_found: string; - embed: { - title: string; - description: string; - fields: { - syntax: string; - category: string; - aliases: string; - owner_only: string; - cooldown: string; - user_perms: string; - bot_perms: string; - examples: string; - }; - }; - }; - - module: { - embed: { - title: string; - }; - }; - - usage_title: string; - usage: string[]; - }; - - invite: string[]; - } - - export interface Automod { - blacklist: string; - invites: string; - mentions: string; - shortlinks: string; - spam: string; - raid: Record<'locked' | 'unlocked', string>; - } - - // eslint-disable-next-line - export interface Generic {} - - // eslint-disable-next-line - export interface Errors {} - } -} diff --git a/src/@types/reflect-metadata.d.ts b/src/@types/reflect-metadata.d.ts deleted file mode 100644 index 5964d683..00000000 --- a/src/@types/reflect-metadata.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** */ -declare namespace Reflect { - /** - * Gets the metadata value for the provided metadata key on the target object or its prototype chain. - * @param metadataKey A key used to store and retrieve metadata. - * @param target The target object on which the metadata is defined. - * @returns The metadata value for the metadata key if found; otherwise, `undefined`. - * @example - * - * class Example { - * } - * - * // constructor - * result = Reflect.getMetadata("custom:annotation", Example); - * - */ - function getMetadata(metadataKey: any, target: any): T; - - /** - * Gets the metadata value for the provided metadata key on the target object or its prototype chain. - * @param metadataKey A key used to store and retrieve metadata. - * @param target The target object on which the metadata is defined. - * @param propertyKey The property key for the target. - * @returns The metadata value for the metadata key if found; otherwise, `undefined`. - */ - function getMetadata(metadataKey: any, target: any, propertyKey: string | symbol): T; -} diff --git a/src/api/API.ts b/src/api/API.ts deleted file mode 100644 index fe377b8a..00000000 --- a/src/api/API.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import fastify, { FastifyReply, FastifyRequest } from 'fastify'; -import { Component, Inject } from '@augu/lilith'; -import LocalizationService from '../services/LocalizationService'; -import CommandService from '../services/CommandService'; -import { pluralize } from '@augu/utils'; -import { Logger } from 'tslog'; -import Config from '../components/Config'; - -@Component({ - priority: 0, - name: 'api' -}) -export default class API { - @Inject - private readonly localization!: LocalizationService; - - @Inject - private readonly commands!: CommandService; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - private instance!: ReturnType; - - async load() { - const enabled = this.config.getProperty('api'); - if (!enabled) { - this.logger.warn('Internal API is not enabled, this isn\'t recommended on self-hosted instances.'); - return; - } - - const server = fastify(); - server.register(require('fastify-no-icon')); - - this.instance = server; - this.setupRoutes(); - - let resolver!: any; - let rejecter!: any; - - const promise = new Promise((resolve, reject) => { - resolver = resolve; - rejecter = reject; - }); - - server.listen(22345, (error, address) => { - if (error) return rejecter(error); - - this.logger.info(`🚀 Launched internal API on ${address}`); - resolver(); - }); - - return promise; - } - - private setupRoutes() { - this.logger.info('🚀 Setting up routing...'); - - this - .instance - .get('/', (_, res) => void res.redirect('https://nino.sh')) - .get('/commands', (req, res) => { - this._handleAllCommandsRoute.call(this, req, res); - }) - .get('/commands/:name', (req, res) => { - this._handleSingleCommandLookup.call(this, req, res); - }); - } - - private _handleAllCommandsRoute(req: FastifyRequest, reply: FastifyReply) { - const query = (req.query as Record<'locale' | 'l', string>); - let locale = query['l'] !== undefined || query['locale'] !== undefined - ? this.localization.locales.find(l => (l.code === query.l || l.code === query.locale) || (l.aliases.includes(query.l) || l.aliases.includes(query.locale))) - : this.localization.defaultLocale; - - if (locale === null) - locale = this.localization.defaultLocale; - - const prefix = this.config.getProperty('prefixes')?.[0] ?? 'x!'; - const commands = this.commands.filter(s => !s.ownerOnly || !s.hidden).map(command => ({ - name: command.name, - description: locale!.translate(command.description), - examples: command.examples.map(s => `${prefix}${s}`), - usage: command.format, - aliases: command.aliases, - cooldown: pluralize('second', command.cooldown), - category: command.category, - user_permissions: command.userPermissions, - bot_permissions: command.botPermissions - })); - - return reply.status(200).send(commands); - } - - private _handleSingleCommandLookup(req: FastifyRequest, reply: FastifyReply) { - const query = (req.query as Record<'locale' | 'l', string>) ?? {} as Record<'locale' | 'l', string>; - const params = (req.params as Record<'name', string>); - let locale = query['l'] !== undefined || query['locale'] !== undefined - ? this.localization.locales.find(l => (l.code === query.l || l.code === query.locale) || (l.aliases.includes(query.l) || l.aliases.includes(query.locale))) - : this.localization.defaultLocale; - - if (locale === null) - locale = this.localization.defaultLocale; - - const prefix = this.config.getProperty('prefixes')?.[0] ?? 'x!'; - const command = this.commands.filter(s => !s.ownerOnly || !s.hidden).filter(s => s.name === params.name)[0]; - if (!command) - return void reply.status(404).send({ - message: `Command ${params.name} was not found.` - }); - - return void reply.status(200).send({ - name: command.name, - description: command.description, - examples: command.examples.map(s => `${prefix}${s}`), - usage: command.format, - aliases: command.aliases, - cooldown: pluralize('second', command.cooldown), - category: command.category, - user_permissions: command.userPermissions, - bot_permissions: command.botPermissions - }); - } -} diff --git a/src/automod/Blacklist.ts b/src/automod/Blacklist.ts deleted file mode 100644 index 097c4c26..00000000 --- a/src/automod/Blacklist.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Message, TextChannel } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import PunishmentService from '../services/PunishmentService'; -import PermissionUtil from '../util/Permissions'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class BlacklistAutomod implements Automod { - public name: string = 'blacklists'; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - async onMessage(msg: Message) { - const settings = await this.database.automod.get(msg.guildID); - if (settings === undefined || settings.blacklist === false) return false; - - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - const content = msg.content.toLowerCase().split(' '); - for (const word of settings.blacklistWords) { - if (content.filter((c) => c === word.toLowerCase()).length > 0) { - const language = this.locales.get(msg.guildID, msg.author.id); - - await msg.channel.createMessage(language.translate('automod.blacklist')); - await msg.delete(); - await this.punishments.createWarning(msg.member, '[Automod] User said a word that is blacklisted here (☍﹏⁰)。'); - - return true; - } - } - - return false; - } -} diff --git a/src/automod/Dehoisting.ts b/src/automod/Dehoisting.ts deleted file mode 100644 index 47f14368..00000000 --- a/src/automod/Dehoisting.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Member } from 'eris'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class Dehoisting implements Automod { - public name: string = 'dehoisting'; - - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - async onMemberNickUpdate(member: Member) { - const settings = await this.database.automod.get(member.guild.id); - if (settings !== undefined && settings.dehoist === false) return false; - - if (member.nick === null) return false; - - if (!member.guild.members.get(this.discord.client.user.id)?.permissions.has('manageNicknames')) return false; - - if (member.nick[0] < '0') { - const origin = member.nick; - await member.edit( - { nick: 'hoister' }, - `[Automod] Member ${member.username}#${member.discriminator} has updated their nick to "${origin}" and dehoisting is enabled.` - ); - - return true; - } - - return false; - } -} diff --git a/src/automod/Invites.ts b/src/automod/Invites.ts deleted file mode 100644 index 19837b41..00000000 --- a/src/automod/Invites.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Invite, Message, TextChannel } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import PunishmentService from '../services/PunishmentService'; -import * as Constants from '../util/Constants'; -import PermissionUtil from '../util/Permissions'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class Invites implements Automod { - public name: string = 'invites'; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly discord!: Discord; - - async onMessage(msg: Message) { - const settings = await this.database.automod.get(msg.guildID); - if (settings === undefined || settings.invites === false) return false; - - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - if (msg.content.match(Constants.DISCORD_INVITE_REGEX) !== null) { - const invites = await msg.channel.guild - .getInvites() - .then((invites) => invites.map((i) => i.code)) - .catch(() => [] as unknown as string[]); - - // Guild#getInvites doesn't add vanity urls, so we have to do it ourselves - if (msg.channel.guild.features.includes('VANITY_URL') && msg.channel.guild.vanityURL !== null) - invites.push(msg.channel.guild.vanityURL); - - const regex = Constants.DISCORD_INVITE_REGEX.exec(msg.content); - if (regex === null) return false; - - const code = regex[0]?.split('/').pop(); - if (code === undefined) return false; - - let invalid = false; - try { - const invite = await this.discord.client.requestHandler - .request('GET', `/invites/${code}`, true) - .then((data) => new Invite(data as any, this.discord.client)) - .catch(() => null); - - if (invite === null) { - invalid = true; - } else { - const hasInvite = invites.filter((inv) => inv === invite.code).length > 0; - if (!hasInvite && invite.guild !== undefined && invite.guild.id === msg.channel.guild.id) - invites.push(invite.code); - } - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 100006 && ex.message.includes('Unknown Invite')) - invalid = true; - } - - if (invalid) return false; - - if (invites.find((inv) => inv === code)) return false; - - const language = this.locales.get(msg.guildID, msg.author.id); - - await msg.channel.createMessage(language.translate('automod.invites')); - await msg.delete(); - await this.punishments.createWarning(msg.member, `[Automod] Advertising in ${msg.channel.mention}`); - - return true; - } - - return false; - } -} diff --git a/src/automod/Mentions.ts b/src/automod/Mentions.ts deleted file mode 100644 index c6f79299..00000000 --- a/src/automod/Mentions.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Message, TextChannel } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import PunishmentService from '../services/PunishmentService'; -import PermissionUtil from '../util/Permissions'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class Mentions implements Automod { - public name: string = 'mentions'; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - async onMessage(msg: Message) { - const settings = await this.database.automod.get(msg.guildID); - if (settings === undefined || settings.invites === false) return false; - - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - if (msg.mentions.length >= 4) { - const language = this.locales.get(msg.guildID, msg.author.id); - - await msg.channel.createMessage(language.translate('automod.mentions')); - await msg.delete(); - await this.punishments.createWarning( - msg.member, - `[Automod] Mentioned 4 or more people in ${msg.channel.mention}` - ); - - return true; - } - - return false; - } -} diff --git a/src/automod/Raid.ts b/src/automod/Raid.ts deleted file mode 100644 index 7a6ee40f..00000000 --- a/src/automod/Raid.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Constants, Guild, Message, TextChannel, Overwrite } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import { PunishmentType } from '../entities/PunishmentsEntity'; -import PunishmentService from '../services/PunishmentService'; -import PermissionUtil from '../util/Permissions'; -import { isObject } from '@augu/utils'; -import { Automod } from '../structures'; -import * as luxon from 'luxon'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; -import Redis from '../components/Redis'; - -interface RaidChannelLock { - affectedIn: string; - state: { - channelID: string; - position: RaidChannelLockPositionArray[]; - }[]; -} - -interface RaidChannelLockPositionArray { - allow: bigint; - deny: bigint; - type: Overwrite['type']; - id: string; -} - -interface IBigIntSerialized { - value: number; - bigint: boolean; -} - -// Serializer for JSON.stringify for bigints -const bigintSerializer = (_: string, value: unknown) => { - if (typeof value === 'bigint') return { value: Number(value), bigint: true }; - else return value; -}; - -// Deserializer for JSON.parse for bigints -const bigintDeserializer = (_: string, value: unknown) => { - if (isObject(value) && value.bigint === true) return BigInt(value.value); - else return value; -}; - -export default class RaidAutomod implements Automod { - public name = 'raid'; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly redis!: Redis; - - async onMessage(msg: Message) { - const settings = await this.database.automod.get(msg.guildID); - - if (settings === undefined || settings.raid === false) return false; - - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - // A raid can happen with 25-100+ pings - const joinedAt = luxon.DateTime.fromJSDate(new Date(msg.member.joinedAt ?? new Date())); - const now = luxon.DateTime.now(); - const difference = Math.floor(now.diff(joinedAt, ['days']).days); - //const language = this.locales.get(msg.guildID, msg.author.id); - - if (msg.mentions.length > 20 && difference < 3) { - // Lockdown all channels @everyone has access in - // await this._lockChannels(msg.channel.id, msg.channel.guild); - // await msg.channel.createMessage(language.translate('automod.raid.locked')); - - try { - await this.punishments.apply({ - moderator: this.discord.client.user, - publish: true, - reason: `[Automod] Raid in <#${msg.channel.id}> (Pinged ${msg.mentions.length} users)`, - member: { - guild: msg.channel.guild, - id: msg.author.id, - }, - soft: false, - type: PunishmentType.Ban, - }); - } catch { - // skip if we can't ban the user - } - - return true; - - // if (this._raidLocks.hasOwnProperty(msg.channel.guild.id)) { - // await this._raidLocks[msg.channel.guild.id].lock.extend(`raid:lockdown:${msg.guildID}`); - // clearTimeout(this._raidLocks[msg.channel.guild.id].timeout); - - // // Create a new timeout - // this._raidLocks[msg.channel.guild.id].timeout = setTimeout(async() => { - // const _raidLock = this._raidLocks[msg.guildID]; - // if (_raidLock !== undefined) { - // try { - // await _raidLock.lock.release(); - // } catch { - // // ignore if we can't release the lock - // } - - // await this._restore(msg.channel.guild); - // await msg.channel.createMessage(language.translate('automod.raid.unlocked')); - // await this.redis.client.del(`nino:raid:lockdown:indicator:${msg.guildID}`); - - // delete this._raidLocks[msg.guildID]; - // } - // }, 3000); - - // return true; - // } else { - // const timeout = setTimeout(async() => { - // const _raidLock = this._raidLocks[msg.guildID]; - // if (_raidLock !== undefined) { - // try { - // await _raidLock.lock.release(); - // } catch { - // // ignore if we can't release the lock - // } - - // await this._restore(msg.channel.guild); - // await msg.channel.createMessage(language.translate('automod.raid.unlocked')); - // await this.redis.client.del(`nino:raid:lockdown:indicator:${msg.guildID}`); - - // delete this._raidLocks[msg.guildID]; - // } - // }, 3000); - - // const lock = RedisLock.create(); - // await lock.acquire(`raid:lockdown:${msg.guildID}`); - - // this._raidLocks[msg.guildID] = { - // timeout, - // lock - // }; - - // await this.redis.client.set(`nino:raid:lockdown:indicator:${msg.guildID}`, msg.guildID); - // return true; - // } - } - - return false; - } - - // protected async _lockChannels(affectedID: string, guild: Guild) { - // // Retrieve old permissions - // const state = guild.channels.map(channel => ({ - // channelID: channel.id, - // position: channel.permissionOverwrites - // .filter(overwrite => overwrite.type === 'role') - // .map(overwrite => ({ - // allow: overwrite.allow, - // deny: overwrite.deny, - // type: overwrite.type, - // id: overwrite.id - // })) - // })); - - // await this.redis.client.hset('nino:raid:lockdowns:channels', guild.id, JSON.stringify({ affectedIn: affectedID, state }, bigintSerializer)); - - // const automod = await this.database.automod.get(guild.id); - // const filter = (channel: TextChannel) => - // channel.type === 0 && - // !automod!.whitelistChannelsDuringRaid.includes(channel.id); - - // // Change @everyone's permissions in all text channels - // for (const channel of guild.channels.filter(filter)) { - // const allow = channel.permissionOverwrites.has(guild.id) ? channel.permissionOverwrites.get(guild.id)!.allow : 0n; - // const deny = channel.permissionOverwrites.has(guild.id) ? channel.permissionOverwrites.get(guild.id)!.deny : 0n; - - // // this shouldn't happen but whatever - // // Checks if `deny` can be shifted with `sendMessages` - // if (!!(deny & Constants.Permissions.sendMessages) === true) - // continue; - - // await channel.editPermission( - // /* role id */ guild.id, - // /* allowed */ allow & ~Constants.Permissions.sendMessages, - // /* denied */ deny | Constants.Permissions.sendMessages, - // /* type */ 'role', - // /* reason */ '[Lockdown] Raid occured.' - // ); - // } - // } - - // protected async _restore(guild: Guild) { - // const locks = await this.redis.client.hget('nino:raid:lockdowns:channels', guild.id) - // .then(data => data !== null ? JSON.parse(data, bigintDeserializer) : null) - // .catch(() => null) as RaidChannelLock | null; - - // if (locks !== null) { - // // Release the locks of the channels - // const channel = guild.channels.get(locks.affectedIn); - // if (channel !== undefined && channel.type === 0) { - // const overwrite = channel.permissionOverwrites.get(guild.id); - // await channel.editPermission( - // guild.id, - // overwrite?.allow ?? 0n, - // overwrite?.deny ?? 0n, - // 'role', - // '[Lockdown] Raid lock has been released.' - // ); - // } - - // for (const { channelID, position } of locks.state.filter(c => c.channelID !== locks.affectedIn)) { - // const channel = guild.channels.get(channelID); - // if (channel !== undefined && channel.type !== 0) - // continue; - - // for (const pos of position) { - // try { - // await channel!.editPermission( - // pos.id, - // pos.allow, - // pos.deny, - // pos.type, - // '[Lockdown] Raid lock has been released.' - // ); - // } catch(ex) { - // console.error(ex); - // } - // } - // } - // } - - // await this.redis.client.hdel('nino:raid:lockdowns:channels', guild.id); - // } -} diff --git a/src/automod/Shortlinks.ts b/src/automod/Shortlinks.ts deleted file mode 100644 index 141e440a..00000000 --- a/src/automod/Shortlinks.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Message, TextChannel } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import PunishmentService from '../services/PunishmentService'; -import * as Constants from '../util/Constants'; -import PermissionUtil from '../util/Permissions'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -// tfw ice the adorable fluff does all the regex for u :woeme: -const LINK_REGEX = /(https?:\/\/)?(\w*\.\w*)/gi; - -export default class Shortlinks implements Automod { - public name: string = 'shortlinks'; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - async onMessage(msg: Message) { - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - const settings = await this.database.automod.get(msg.author.id); - if (settings !== undefined && settings.shortLinks === false) return false; - - const matches = msg.content.match(LINK_REGEX); - if (matches === null) return false; - - // Why not use .find/.filter? - // Because, it should traverse *all* matches, just not one match. - // - // So for an example: - // "haha scam thing!!! floofy.dev bit.ly/jnksdjklsdsj" - // - // In the string, if `.find`/`.filter` was the solution here, - // it'll only match "owo.com" and not "bit.ly" - for (let i = 0; i < matches.length; i++) { - if (Constants.SHORT_LINKS.includes(matches[i])) { - const language = this.locales.get(msg.guildID, msg.author.id); - - await msg.delete(); - await msg.channel.createMessage(language.translate('automod.shortlinks')); - await this.punishments.createWarning( - msg.member, - `[Automod] Sending a blacklisted URL in ${msg.channel.mention}` - ); - - return true; - } - } - - return false; - } -} diff --git a/src/automod/Spam.ts b/src/automod/Spam.ts deleted file mode 100644 index abe8a090..00000000 --- a/src/automod/Spam.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Message, TextChannel } from 'eris'; -import LocalizationService from '../services/LocalizationService'; -import PunishmentService from '../services/PunishmentService'; -import { Collection } from '@augu/collections'; -import PermissionUtil from '../util/Permissions'; -import { Automod } from '../structures'; -import { Inject } from '@augu/lilith'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class Mentions implements Automod { - private cache: Collection> = new Collection(); - public name: string = 'mentions'; - - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly locales!: LocalizationService; - - @Inject - private readonly discord!: Discord; - - async onMessage(msg: Message) { - const settings = await this.database.automod.get(msg.guildID); - if (settings === undefined || settings.invites === false) return false; - - if (!msg || msg === null) return false; - - const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; - - if ( - (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || - !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || - msg.author.bot || - msg.channel.permissionsOf(msg.author.id).has('banMembers') - ) - return false; - - const queue = this.get(msg.guildID, msg.author.id); - queue.push(msg.timestamp); - - if (queue.length >= 5) { - const old = queue.shift()!; - if (msg.editedTimestamp && msg.editedTimestamp > msg.timestamp) return false; - - if (msg.timestamp - old <= 3000) { - const language = this.locales.get(msg.guildID, msg.author.id); - this.clear(msg.guildID, msg.author.id); - - await msg.channel.createMessage(language.translate('automod.spam')); - await this.punishments.createWarning(msg.member, `[Automod] Spamming in ${msg.channel.mention} o(╥﹏╥)o`); - return true; - } - } - - this.clean(msg.guildID); - return false; - } - - private clean(guildID: string) { - const now = Date.now(); - const buckets = this.cache.get(guildID); - - // Let's just not do anything if there is no spam cache for this guild - if (buckets === undefined) return; - - const ids = buckets.filterKeys((val) => now - val[val.length - 1] >= 5000); - for (const id of ids) this.cache.delete(id); - } - - private get(guildID: string, userID: string) { - if (!this.cache.has(guildID)) this.cache.set(guildID, new Collection()); - - if (!this.cache.get(guildID)!.has(userID)) this.cache.get(guildID)!.set(userID, []); - - return this.cache.get(guildID)!.get(userID)!; - } - - private clear(guildID: string, userID: string) { - this.cache.get(guildID)!.delete(userID); - } -} diff --git a/src/commands/core/HelpCommand.ts b/src/commands/core/HelpCommand.ts deleted file mode 100644 index 86e0da5e..00000000 --- a/src/commands/core/HelpCommand.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { Constants as ErisConstants } from 'eris'; -import { firstUpper } from '@augu/utils'; -import CommandService from '../../services/CommandService'; -import Permissions from '../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../components/Discord'; - -interface CommandCategories { - moderation?: Command[]; - settings?: Command[]; - general?: Command[]; -} - -export default class HelpCommand extends Command { - private categories!: CommandCategories; - private parent!: CommandService; - - @Inject - private discord!: Discord; - - constructor() { - super({ - description: 'descriptions.help', - examples: ['help', 'help help', 'help General'], - cooldown: 2, - aliases: ['halp', 'h', 'cmds', 'commands'], - usage: '[cmdOrMod | "usage"]', - name: 'help', - }); - } - - run(msg: CommandMessage, [command]: [string]) { - return command !== undefined ? this.renderDoc(msg, command) : this.renderHelpCommand(msg); - } - - private async renderHelpCommand(msg: CommandMessage) { - if (this.categories === undefined) { - this.categories = {}; - - const commands = this.parent.filter((cmd) => !cmd.ownerOnly); - for (let i = 0; i < commands.length; i++) { - const command = commands[i]; - (this.categories[command.category] ??= []).push(command); - } - } - - const prefix = msg.settings.prefixes[Math.floor(Math.random() * msg.settings.prefixes.length)]; - const embed = EmbedBuilder.create() - .setTitle( - msg.locale.translate('commands.help.embed.title', [ - `${this.discord.client.user.username}#${this.discord.client.user.discriminator}`, - ]) - ) - .setDescription(msg.locale.translate('commands.help.embed.description', [prefix, this.parent.size])); - - for (const cat in this.categories as Required) { - const commands = this.categories[cat] as Command[]; - embed.addField( - msg.locale.translate(`commands.help.embed.fields.${cat}` as any, [this.categories[cat].length]), - commands.map((cmd) => `**\`${cmd.name}\`**`).join(', '), - false - ); - } - - return msg.reply(embed); - } - - private async renderDoc(msg: CommandMessage, cmdOrMod: string) { - const command = this.parent.filter( - (cmd) => (!cmd.hidden && cmd.name === cmdOrMod) || cmd.aliases.includes(cmdOrMod) - )[0]; - const prefix = msg.settings.prefixes[msg.settings.prefixes.length - 1]; - - if (command !== undefined) { - const description = msg.locale.translate(command.description as any); - const embed = EmbedBuilder.create() - .setTitle(msg.locale.translate('commands.help.command.embed.title', [command.name])) - .setDescription(msg.locale.translate('commands.help.command.embed.description', [description])) - .addFields([ - { - name: msg.locale.translate('commands.help.command.embed.fields.syntax'), - value: `**\`${prefix}${command.format}\`**`, - inline: false, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.category'), - value: firstUpper(command.category), - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.aliases'), - value: command.aliases.join(', ') || 'No aliases available', - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.owner_only'), - value: command.ownerOnly ? 'Yes' : 'No', - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.cooldown'), - value: `${command.cooldown} Seconds`, - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.user_perms'), - value: - Permissions.stringify( - command.userPermissions.reduce((acc, curr) => acc | ErisConstants.Permissions[curr], 0n) - ) || 'None', - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.bot_perms'), - value: - Permissions.stringify( - command.botPermissions.reduce((acc, curr) => acc | ErisConstants.Permissions[curr], 0n) - ) || 'None', - inline: true, - }, - { - name: msg.locale.translate('commands.help.command.embed.fields.examples'), - value: - command.examples.map((example) => `• **${prefix}${example}**`).join('\n') || 'No examples are available.', - inline: false, - }, - ]); - - return msg.reply(embed); - } else { - if (cmdOrMod === 'usage') { - const embed = EmbedBuilder.create() - .setTitle(msg.locale.translate('commands.help.usage_title')) - .setDescription(msg.locale.translate('commands.help.usage', [msg.settings.prefixes[0]])); - - return msg.reply(embed); - } - - const mod = this.parent.filter((cmd) => cmd.category.toLowerCase() === cmdOrMod.toLowerCase()); - if (mod.length > 0) { - const embed = EmbedBuilder.create() - .setAuthor(msg.locale.translate('commands.help.module.embed.title', [firstUpper(cmdOrMod)])) - .setDescription( - mod.map( - (command) => - `**\`${prefix}${command.format}\`** ~ \u200b \u200b**${msg.locale.translate( - command.description as any - )}**` - ) - ); - - return msg.reply(embed); - } else { - return msg.reply(msg.locale.translate('commands.help.command.not_found', [cmdOrMod])); - } - } - } -} diff --git a/src/commands/core/LocaleCommand.ts b/src/commands/core/LocaleCommand.ts deleted file mode 100644 index 7750391a..00000000 --- a/src/commands/core/LocaleCommand.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder, Subcommand } from '../../structures'; -import LocalizationService from '../../services/LocalizationService'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -interface Flags { - user?: string | true; - u?: string | true; -} - -export default class LocaleCommand extends Command { - @Inject - private languages!: LocalizationService; - - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - constructor() { - super({ - description: 'descriptions.locale', - examples: [ - 'locale', - 'locale list', - 'locale set fr_FR --user', - 'locale set fr_FR', - 'locale reset --user', - 'locale reset', - ], - aliases: ['language', 'lang'], - name: 'locale', - }); - } - - run(msg: CommandMessage) { - const embed = EmbedBuilder.create() - .setTitle('[ Localization ]') - .setDescription([ - '> **Current Settings**', - '```apache', - `Guild: ${msg.settings.language}`, - `User: ${msg.userSettings.language}`, - '```', - '', - `• Use \`${msg.settings.prefixes[0]}locale set fr_FR\` to set the guild's language (Requires \`Manage Guild\`)`, - '• Use the `--user` or `-u` flag to set or reset your language.', - `• Use \`${msg.settings.prefixes[0]}locale list\` to view a list of the languages available`, - ]); - - return msg.reply(embed); - } - - @Subcommand() - list(msg: CommandMessage) { - const languages = this.languages.locales.map((locale) => { - const user = this.discord.client.users.get(locale.translator); - return `❯ ${locale.flag} **${locale.full} (${locale.code})** by **${user?.username ?? 'Unknown User'}**#**${ - user?.discriminator ?? '0000' - }** (${locale.contributors.length} contributers, \`${msg.settings.prefixes[0]}locale set ${locale.code} -u\`)`; - }); - - return msg.reply(EmbedBuilder.create().setTitle('[ Languages ]').setDescription(languages)); - } - - @Subcommand('', { permissions: 'manageGuild' }) - async set(msg: CommandMessage, [locale]: [string]) { - if (!locale) - return msg.reply( - `No locale has been specified, use \`${msg.settings.prefixes[0]}locale list\` to list all of them` - ); - - if (!this.languages.locales.has(locale)) - return msg.reply( - `Locale \`${locale}\` doesn't exist, use \`${msg.settings.prefixes[0]}locale list\` to list all of them` - ); - - const flags = msg.flags(); - const isUser = flags.user === true || flags.u === true; - const controller = isUser ? this.database.users : this.database.guilds; - const id = isUser ? msg.author.id : msg.guild.id; - - await controller.update(id, { language: locale }); - return msg.reply(`Language for ${isUser ? 'user' : 'server'} has been set to **${locale}**`); - } - - @Subcommand(undefined, { permissions: 'manageGuild' }) - async reset(msg: CommandMessage) { - const flags = msg.flags(); - const isUser = flags.user === true || flags.u === true; - const controller = isUser ? this.database.users : this.database.guilds; - const id = isUser ? msg.author.id : msg.guild.id; - - await controller.update(id, { - language: this.languages.defaultLocale.code, - }); - return msg.reply( - `Language for ${isUser ? 'user' : 'server'} has been resetted to **${this.languages.defaultLocale.code}**` - ); - } -} diff --git a/src/commands/core/PingCommand.ts b/src/commands/core/PingCommand.ts deleted file mode 100644 index aa6b14dc..00000000 --- a/src/commands/core/PingCommand.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { calculateHRTime } from '@augu/utils'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import Redis from '../../components/Redis'; - -export default class PingCommand extends Command { - @Inject - private discord!: Discord; - - constructor() { - super({ - description: 'descriptions.ping', - aliases: ['pong', 'latency', 'lat'], - cooldown: 1, - name: 'ping', - }); - } - - async run(msg: CommandMessage) { - const startedAt = process.hrtime(); - const message = await msg.reply('What did you want me to respond to?'); - const ping = calculateHRTime(startedAt); - const node = process.env.REGION ?? 'unknown'; - - const deleteMsg = process.hrtime(); - await message.delete(); - - const shard = this.discord.client.shards.get(msg.guild.shard.id)!; - const redis = await app.get('redis').getStatistics(); - const postgres = await app.get('database').getStatistics(); - - return msg.reply( - [ - `:satellite_orbital: Running under node **${node}**`, - '', - `> **Message Delete**: ${calculateHRTime(deleteMsg).toFixed()}ms`, - `> **Message Send**: ${ping.toFixed()}ms`, - `> **PostgreSQL**: ${postgres.ping}`, - `> **Shard #${shard.id}**: ${shard.latency}ms`, - `> **Redis**: ${redis.ping}`, - ].join('\n') - ); - } -} diff --git a/src/commands/core/ShardInfoCommand.ts b/src/commands/core/ShardInfoCommand.ts deleted file mode 100644 index 00b3395f..00000000 --- a/src/commands/core/ShardInfoCommand.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { firstUpper } from '@augu/utils'; -import type { Shard } from 'eris'; -import { Inject } from '@augu/lilith'; -import Discord from '../../components/Discord'; - -type ShardStatus = Shard['status']; - -const hearts: { [P in ShardStatus]: string } = { - disconnected: ':heart:', - handshaking: ':yellow_heart:', - connecting: ':yellow_heart:', - identifying: ':blue_heart:', - resuming: ':blue_heart:', - ready: ':green_heart:', -}; - -interface ShardInfo { - current: boolean; - status: ShardStatus; - guilds: number; - users: number; - heart: string; - id: number; -} - -export default class ShardInfoCommand extends Command { - @Inject - private discord!: Discord; - - constructor() { - super({ - description: 'descriptions.shardinfo', - aliases: ['shard', 'shards'], - cooldown: 6, - name: 'shardinfo', - }); - } - - run(msg: CommandMessage) { - const shards = this.discord.client.shards.map((shard) => ({ - current: msg.guild.shard.id === shard.id, - status: shard.status, - guilds: this.discord.client.guilds.filter((guild) => guild.shard.id === shard.id).length, - users: this.discord.client.guilds - .filter((guild) => guild.shard.id === shard.id) - .reduce((a, b) => a + b.memberCount, 0), - heart: hearts[shard.status], - id: shard.id, - })); - - const embed = EmbedBuilder.create().addFields( - shards.map((shard) => ({ - name: `❯ Shard #${shard.id}`, - value: [ - `${shard.heart} **${firstUpper(shard.status)}**${shard.current ? ' (current)' : ''}`, - '', - `• **Guilds**: ${shard.guilds}`, - `• **Users**: ${shard.users}`, - ].join('\n'), - inline: true, - })) - ); - - return msg.reply(embed); - } -} diff --git a/src/commands/core/StatisticsCommand.ts b/src/commands/core/StatisticsCommand.ts deleted file mode 100644 index b5eda992..00000000 --- a/src/commands/core/StatisticsCommand.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { Color, version, commitHash } from '../../util/Constants'; -import { firstUpper, humanize } from '@augu/utils'; -import CommandService from '../../services/CommandService'; -import { formatSize } from '../../util'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import Config from '../../components/Config'; -import Redis from '../../components/Redis'; -import os from 'os'; - -export default class StatisticsCommand extends Command { - private parent!: CommandService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly config!: Config; - - @Inject - private readonly redis!: Redis; - - constructor() { - super({ - description: 'descriptions.statistics', - aliases: ['stats', 'botinfo', 'info', 'me'], - cooldown: 8, - name: 'statistics', - }); - } - - async run(msg: CommandMessage) { - const database = await this.database.getStatistics(); - const redis = await this.redis.getStatistics(); - const guilds = this.discord.client.guilds.size.toLocaleString(); - const users = this.discord.client.guilds.reduce((a, b) => a + b.memberCount, 0).toLocaleString(); - const channels = Object.keys(this.discord.client.channelGuildMap).length.toLocaleString(); - const memoryUsage = process.memoryUsage(); - const avgPing = this.discord.client.shards.reduce((a, b) => a + b.latency, 0); - const node = process.env.REGION ?? 'unknown'; - const ownerIDs = this.config.getProperty('owners') ?? []; - const owners = await Promise.all( - ownerIDs.map((id) => { - const user = this.discord.client.users.get(id); - if (user === undefined) return this.discord.client.getRESTUser(id); - else return Promise.resolve(user); - }) - ); - - const dashboardUrl = - this.discord.client.user.id === '531613242473054229' - ? 'https://stats.floofy.dev/d/e3KPDLknk/nino-prod?orgId=1' - : this.discord.client.user.id === '613907896622907425' - ? 'https://stats.floofy.dev/d/C5bZHVZ7z/nino-edge?orgId=1' - : ''; - - const embed = new EmbedBuilder() - .setAuthor( - `[ ${this.discord.client.user.username}#${this.discord.client.user.discriminator} ~ v${version} (${ - commitHash ?? '' - }) ]`, - 'https://nino.floofy.dev', - this.discord.client.user.dynamicAvatarURL('png', 1024) - ) - .setColor(Color) - .addFields([ - { - name: '❯ Discord', - value: [ - `• **Commands Executed (session)**\n${this.parent.commandsExecuted.toLocaleString()}`, - `• **Messages Seen (session)**\n${this.parent.messagesSeen.toLocaleString()}`, - `• **Shards [C / T]**\n${msg.guild.shard.id} / ${this.discord.client.shards.size} (${avgPing}ms avg.)`, - `• **Channels**\n${channels}`, - `• **Guilds**\n${guilds}`, - `• **Users**\n${users}`, - ].join('\n'), - inline: true, - }, - { - name: `❯ Process [${process.pid}]`, - value: [ - `• **System Memory [Free / Total]**\n${formatSize(os.freemem())} / ${formatSize(os.totalmem())}`, - `• **Memory Usage [RSS / Heap]**\n${formatSize(memoryUsage.rss)} / ${formatSize(memoryUsage.heapUsed)}`, - `• **Current Node**\n${node ?? 'Unknown'}`, - `• **Uptime**\n${humanize(Math.floor(process.uptime() * 1000), true)}`, - ].join('\n'), - inline: true, - }, - { - name: `❯ Redis v${redis.server.redis_version} [${firstUpper(redis.server.redis_mode)}]`, - value: [ - `• **Network I/O**\n${formatSize(redis.stats.total_net_input_bytes)} / ${formatSize( - redis.stats.total_net_output_bytes - )}`, - `• **Uptime**\n${humanize(Number(redis.server.uptime_in_seconds) * 1000, true)}`, - `• **Ops/s**\n${redis.stats.instantaneous_ops_per_sec.toLocaleString()}`, - `• **Ping**\n${redis.ping}`, - ].join('\n'), - inline: true, - }, - { - name: `❯ PostgreSQL v${database.version}`, - value: [ - `• **Insert / Delete / Update / Fetched**:\n${database.inserted.toLocaleString()} / ${database.deleted.toLocaleString()} / ${database.updated.toLocaleString()} / ${database.fetched.toLocaleString()}`, - `• **Uptime**\n${database.uptime}`, - `• **Ping**\n${database.ping}`, - ].join('\n'), - inline: true, - }, - ]) - .setFooter(`Owners: ${owners.map((user) => `${user.username}#${user.discriminator}`).join(', ')}`); - - if (dashboardUrl !== '') embed.setDescription(`[[**Metrics Dashboard**]](${dashboardUrl})`); - - return msg.reply(embed); - } -} diff --git a/src/commands/moderation/BanCommand.ts b/src/commands/moderation/BanCommand.ts deleted file mode 100644 index 5d84ce63..00000000 --- a/src/commands/moderation/BanCommand.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User } from 'eris'; -import { Command, CommandMessage } from '../../structures'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import Permissions from '../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../components/Discord'; -import ms = require('ms'); - -interface BanFlags { - soft?: string | true; - days?: string | true; - d?: string | true; -} - -export default class BanCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'banMembers', - botPermissions: 'banMembers', - description: 'descriptions.ban', - category: Categories.Moderation, - examples: [ - 'ban @Nino', - 'ban @Nino some reason!', - 'ban @Nino some reason! | 1d', - 'ban @Nino some reason! | 1d -d 7', - ], - aliases: ['banne', 'bent', 'bean'], - usage: ' [reason [| time]]', - name: 'ban', - }); - } - - async run(msg: CommandMessage, args: string[]) { - if (args.length < 1) return msg.reply('No bot or user was specified.'); - - const userID = args[0]; - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - const member = msg.guild.members.get(user.id) ?? { - id: user.id, - guild: msg.guild, - }; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member.id === this.discord.client.user.id) return msg.reply(';w; why would you ban me from here? **(/。\)**'); - - if (member instanceof Member) { - // this won't work for banning members not in this guild - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - const ban = await msg.guild.getBan(user.id).catch(() => null); - if (ban !== null) - return msg.reply( - `${user.bot ? 'Bot' : 'User'} was previously banned for ${ban.reason ?? '*(no reason provided)*'}` - ); - - args.shift(); // remove user ID - - let reason = args.length > 0 ? args.join(' ') : undefined; - let time: string | null = null; - - if (reason !== undefined) { - const [r, t] = reason.split(' | '); - reason = r; - time = t ?? null; - } - - const flags = msg.flags(); - if (typeof flags.days === 'boolean' || typeof flags.d === 'boolean') - return msg.reply('The `--days` flag must have a value appended. Example: `--days=7` or `-d 7`'); - - const days = flags.days ?? flags.d ?? 7; - if (Number(days) > 7) return msg.reply('You can only concat 7 days worth of messages'); - - if (flags.soft !== undefined) - await msg.reply( - 'Flag `--soft` is deprecated and will be removed in a future release, use the `softban` command.' - ); - - try { - await this.punishments.apply({ - attachments: msg.attachments, - moderator: msg.author, - publish: true, - reason, - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - soft: flags.soft === true, - type: PunishmentType.Ban, - days: Number(days), - time: time !== null ? ms(time) : undefined, - }); - - return msg.reply( - `${user.bot ? 'Bot' : 'User'} **${user.username}#${user.discriminator}** has been banned${ - reason ? ` *for ${reason}${time !== null ? ` in ${time}` : ''}` : '.' - }*` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/CaseCommand.ts b/src/commands/moderation/CaseCommand.ts deleted file mode 100644 index 93ffdd8c..00000000 --- a/src/commands/moderation/CaseCommand.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import ms from 'ms'; - -export default class CaseCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'banMembers', - description: 'descriptions.case', - examples: ['case', 'case 3'], - category: Categories.Moderation, - aliases: ['lookup'], - usage: '[caseID]', - name: 'case', - }); - } - - async run(msg: CommandMessage, args: string[]) { - if (args.length < 1) return msg.reply('No bot or user was found.'); - - const caseID = args[0]; - if (isNaN(Number(caseID))) return msg.reply(`Case \`${caseID}\` was not a number.`); - - const caseModel = await this.database.cases.repository.findOne({ - guildID: msg.guild.id, - index: Number(caseID), - }); - - if (caseModel === undefined) return msg.reply(`Case #**${caseID}** was not found.`); - - const moderator = this.discord.client.users.get(caseModel.moderatorID) ?? { - username: 'Unknown User', - discriminator: '0000', - }; - - const victim = this.discord.client.users.get(caseModel.victimID) ?? { - discriminator: '0000', - username: 'Unknown User', - }; - - const embed = EmbedBuilder.create() - .setAuthor( - `[ Case #${caseModel.index} | ${victim.username}#${victim.discriminator} (${caseModel.victimID})]`, - undefined, - (victim as any).dynamicAvatarURL?.('png', 1024) - ) // dynamicAvatarURL might not exist since partials - .setDescription([ - `${ - caseModel.reason - ? `**${caseModel.reason}**` - : `*Unknown, use \`${msg.settings.prefixes[0]}reason ${caseModel.index} \` to set a reason*` - }`, - '', - caseModel.messageID !== null || msg.settings.modlogChannelID !== null - ? `[**\`[Jump Here]\`**](https://discord.com/channels/${msg.guild.id}/${msg.settings.modlogChannelID}/${caseModel.messageID})` - : '', - ]) - .addField( - '• Moderator', - `${moderator.username}#${moderator.discriminator} (${(moderator as any).id ?? '(unknown)'})`, - true - ) - .addField('• Type', caseModel.type, true); - - if (caseModel.time !== null) embed.addField('• Time', ms(Number(caseModel.time!), { long: true }), true); - - return msg.reply(embed); - } -} diff --git a/src/commands/moderation/KickCommand.ts b/src/commands/moderation/KickCommand.ts deleted file mode 100644 index 5d4876cc..00000000 --- a/src/commands/moderation/KickCommand.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { DiscordRESTError, User } from 'eris'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Permissions from '../../util/Permissions'; -import Discord from '../../components/Discord'; - -export default class KickCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'kickMembers', - botPermissions: 'kickMembers', - description: 'descriptions.kick', - category: Categories.Moderation, - examples: ['kick @Nino get yeeted!'], - aliases: ['yeet', 'yeetafluff', 'yeetfluff', 'boot'], - usage: ' [reason]', - name: 'kick', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot kick members outside the server.'); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you kicking the owner, you idiot."); - - if (member.id === this.discord.client.user.id) - return msg.reply(';w; why would you kick me from here? **(/。\)**'); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - - try { - await this.punishments.apply({ - attachments: msg.attachments, - moderator: msg.author, - publish: true, - reason: reason.length ? reason.join(' ') : undefined, - member: msg.guild.members.get(user.id)!, - soft: false, - type: PunishmentType.Kick, - }); - - return msg.reply( - `${user.bot ? 'Bot' : 'User'} **${user.username}#${user.discriminator}** has been kicked${ - reason.length ? ` *for ${reason.join(' ')}*` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/MuteCommand.ts b/src/commands/moderation/MuteCommand.ts deleted file mode 100644 index a2d9d3c7..00000000 --- a/src/commands/moderation/MuteCommand.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, User } from 'eris'; -import { Command, CommandMessage } from '../../structures'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Permissions from '../../util/Permissions'; -import Discord from '../../components/Discord'; -import ms = require('ms'); - -export default class MuteCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'manageMessages', - botPermissions: 'manageRoles', - description: 'descriptions.mute', - category: Categories.Moderation, - examples: ['mute @Nino', 'mute @Nino bap!', 'mute @Nino bap bap | 1d'], - usage: ' [reason [|time]]', - name: 'mute', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: [string, ...string[]]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot mute members outside the server.'); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you kicking the owner, you idiot."); - - if (member.id === this.discord.client.user.id) return msg.reply("I don't have the Muted role."); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - - const areason = reason.join(' '); - let actualReason: string | undefined = undefined; - let time: string | undefined = undefined; - - if (areason !== '') { - const [r, t] = areason.split(' | '); - actualReason = r; - time = t; - } - - if (msg.settings.mutedRoleID !== undefined && member.roles.includes(msg.settings.mutedRoleID)) - return msg.reply('Member is already muted.'); - - try { - await this.punishments.apply({ - attachments: msg.attachments, - moderator: msg.author, - publish: true, - reason: actualReason, - member, - type: PunishmentType.Mute, - time: time !== undefined ? ms(time) : undefined, - }); - - return msg.reply( - `${user.bot ? 'Bot' : 'User'} **${user.username}#${user.discriminator}** has been muted${ - actualReason ? ` *for ${actualReason}${time !== null ? ` in ${time}*` : ''}` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/PardonCommand.ts b/src/commands/moderation/PardonCommand.ts deleted file mode 100644 index 0824cbb7..00000000 --- a/src/commands/moderation/PardonCommand.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, User, Member } from 'eris'; -import { Command, CommandMessage } from '../../structures'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import Permissions from '../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -export default class PardonCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'kickMembers', - description: 'descriptions.pardon', - category: Categories.Moderation, - examples: ['pardon 280158289667555328 1', 'pardon 280158289667555328 1 yes'], - aliases: ['rmwarn', 'rmw'], - usage: ' [amount] [...reason]', - name: 'pardon', - }); - } - - async run(msg: CommandMessage, [userID, amount, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot warn members outside the server.'); - - const member = msg.guild.members.get(user.id)!; - if (!(member instanceof Member)) return msg.reply("Cannot remove warnings from a member that isn't here."); - - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you kicking the owner, you idiot."); - - if (member.id === this.discord.client.user.id) return msg.reply(';w; why would you warn me? **(/。\)**'); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - - const actualAmount = amount !== 'all' ? Number(amount) : 'all'; - if (actualAmount !== 'all' && isNaN(actualAmount)) - return msg.reply(`The amount provided (\`${amount}\`) was not a number.`); - - try { - await this.punishments.removeWarning( - msg.guild.members.get(user.id)!, - reason.join(' ') || 'No reason was provided.', - actualAmount - ); - - const warnings = await this.database.warnings.getAll(msg.guild.id, user.id); - const _amount = warnings.reduce((acc, curr) => acc + curr.amount, 0); - return msg.reply( - `User **${user.username}#${user.discriminator}** now has **${_amount === 0 ? 'no' : _amount}** warnings left.` - ); - } catch (ex) { - if (ex instanceof RangeError || ex instanceof SyntaxError) return msg.error(ex.message); - - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/PurgeCommand.ts b/src/commands/moderation/PurgeCommand.ts deleted file mode 100644 index 3aea964c..00000000 --- a/src/commands/moderation/PurgeCommand.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder, Subcommand } from '../../structures'; -import { Inject } from '@augu/lilith'; -import { Categories, DISCORD_INVITE_REGEX, ID_REGEX } from '../../util/Constants'; -import Discord from '../../components/Discord'; -import { Message, User } from 'eris'; -import { pluralize } from '@augu/utils'; - -// It's a function so it can properly hydrate instead of being in the command scope as a getter. -const getTwoWeeksFromNow = () => Date.now() - 1000 * 60 * 60 * 24 * 14; - -export default class PurgeCommand extends Command { - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: ['manageMessages'], - botPermissions: ['manageMessages'], - description: 'descriptions.purge', - examples: [ - 'prune | Automatically delete 100 messages from this channel', - 'prune 6 | Delete 6 messages from the channel', - 'prune August#5820 5 | Removes messages that **August** has said. (do not actually do this)', - 'prune self 6 | Removes 6 messages that I have said', - 'prune system | Prunes all system-related messages', - 'prune attachments | Prunes all messages with attachments', - ], - aliases: ['prune', 'delmsgs', 'delmsg'], - category: Categories.Moderation, - usage: ' [amount] | | [amount]', - name: 'purge', - }); - } - - private _generateEmbed(msg: CommandMessage, messages: Message[]) { - // removes duplicated ids (so it doesn't look garbage (i.e: https://github.com/NinoDiscord/Nino/blob/56290f3cce32ec8376d704ab510a10bdeea7fa7a/src/commands/moderation/Prune.ts#L64)) - const users = [...new Set(messages.map((s) => s.author.id))]; - return EmbedBuilder.create() - .setAuthor( - `[ ${msg.author.username}#${msg.author.discriminator} ~ Purge Result ]`, - '', - msg.author.dynamicAvatarURL('png', 1024) - ) - .setDescription([ - `${msg.successEmote} I have deleted **${pluralize('message', messages.length)}** from this channel.`, - '> 👤 **Users Affected**', - '```apache', - users - .map((user) => { - const u = this.discord.client.users.get(user) ?? { - username: 'Unknown User', - discriminator: '0000', - id: user, - bot: false, - }; - const messagesByUser = messages.filter((c) => c.author.id === user); - const percentage = ((messagesByUser.length / messages.length) * 100).toFixed(); - - return `• ${u.username}#${u.discriminator}${u.bot ? ' (Bot)' : ''} (${u.id}) - ${percentage}% deleted`; - }) - .join('\n'), - '```', - ]); - } - - async run(msg: CommandMessage, [userIdOrAmount, amount]: [string, string?]) { - if (!userIdOrAmount) { - await msg.reply('Now purging 100 messages from this channel...'); - - const messages = await msg.channel - .getMessages({ limit: 100 }) - .then((f) => f.filter((c) => c.timestamp >= getTwoWeeksFromNow())); - if (!messages.length) return msg.error('No messages were found that is under 2 weeks?'); - - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command.` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - const user = await this.discord.getUser(userIdOrAmount).catch(() => null); - if (user !== null) - return this._deleteByUser(msg, user.id, amount !== undefined && !isNaN(Number(amount)) ? Number(amount) : 50); - else - return this._deleteAmount( - msg, - !isNaN(Number(userIdOrAmount)) - ? Number(userIdOrAmount) - : amount !== undefined && !isNaN(Number(amount)) - ? Number(amount) - : 50 - ); - } - - private async _deleteByUser(msg: CommandMessage, userID: string, amount?: number) { - const amountToDelete = amount ?? 50; - if (amountToDelete > 100) - return msg.reply(`Cannot delete more or equal to 100 messages. (went over **${amountToDelete - 100}**.)`); - - const messages = await msg.channel - .getMessages({ limit: amountToDelete }) - .then((messages) => messages.filter((m) => m.author.id === userID && m.timestamp >= getTwoWeeksFromNow())); - if (!messages.length) return msg.error('No messages were found that is under 2 weeks?'); - - if (amountToDelete === 1) { - const message = messages[0]; - await message.delete(); - - return msg.success('Deleted one message.'); - } - - const user = (await this.discord.getUser(userID)) as User; - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete ${amountToDelete} messages from ${user.username}#${user.discriminator})` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - private async _deleteAmount(msg: CommandMessage, amount: number) { - if (amount > 100) return msg.reply(`Cannot delete more or equal to 100 messages. (went over **${amount - 100}**.)`); - - const messages = await msg.channel - .getMessages({ limit: amount }) - .then((m) => m.filter((c) => c.timestamp > getTwoWeeksFromNow())); - if (!messages.length) return msg.error('No messages were found that is under 2 weeks?'); - - if (amount === 1) { - const message = messages[0]; - await message.delete(); - - return msg.success('Deleted the previous message'); - } - - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete ${amount} messages from all users)` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - @Subcommand('[amount]', ['images', 'imgs']) - async attachments(msg: CommandMessage, [amount]: [string?]) { - const toDelete = amount !== undefined && !isNaN(Number(amount)) ? Number(amount) : 50; - if (toDelete > 100) - return msg.reply(`Cannot delete more or equal to 100 messages. (went over **${toDelete - 100}**.)`); - - const messages = await msg.channel - .getMessages({ limit: toDelete }) - .then((m) => m.filter((c) => c.attachments.length > 0).filter((c) => c.timestamp > getTwoWeeksFromNow())); - if (toDelete === 1) { - const message = messages[0]; - await message.delete(); - - return msg.success('Deleted one message.'); - } - - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete ${toDelete} messages with attachments)` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - @Subcommand() - async system(msg: CommandMessage) { - const messages = await msg.channel - .getMessages({ limit: 10 }) - .then((m) => m.filter((c) => c.author.system).filter((c) => c.timestamp > getTwoWeeksFromNow())); - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete 10 messages that are system-related)` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - @Subcommand() - async invites(msg: CommandMessage) { - const messages = await msg.channel - .getMessages({ limit: 10 }) - .then((m) => - m.filter((c) => DISCORD_INVITE_REGEX.test(c.content)).filter((c) => c.timestamp > getTwoWeeksFromNow()) - ); - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete 10 messages that are system-related)` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } - - @Subcommand('[amount]') - async self(msg: CommandMessage, [amount]: [string?]) { - const toDelete = amount !== undefined && !isNaN(Number(amount)) ? Number(amount) : 50; - if (toDelete > 100) - return msg.reply(`Cannot delete more or equal to 100 messages. (went over **${toDelete - 100}**.)`); - - const messages = await msg.channel - .getMessages({ limit: toDelete }) - .then((m) => - m.filter((c) => c.author.id === this.discord.client.user.id).filter((c) => c.timestamp > getTwoWeeksFromNow()) - ); - if (toDelete === 1) { - const message = messages[0]; - await message.delete(); - - return msg.success('Deleted one message.'); - } - - await this.discord.client.deleteMessages( - /* channelID */ msg.channel.id, - /* messages */ messages.map((i) => i.id), - /* reason */ `[Purge] User ${msg.author.username}#${msg.author.discriminator} ran the \`purge\` command. (used to delete ${toDelete} messages that I said)` - ); - - return msg.reply(this._generateEmbed(msg, messages), false); - } -} diff --git a/src/commands/moderation/ReasonCommand.ts b/src/commands/moderation/ReasonCommand.ts deleted file mode 100644 index 085f6147..00000000 --- a/src/commands/moderation/ReasonCommand.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, Subcommand } from '../../structures'; -import type { TextChannel } from 'eris'; -import PunishmentService from '../../services/PunishmentService'; -import type CaseEntity from '../../entities/CaseEntity'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import ms from 'ms'; - -export default class ReasonCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'kickMembers', - botPermissions: 'manageMessages', - description: 'descriptions.reason', - category: Categories.Moderation, - examples: [ - 'reason 69 some reason!', - 'reason latest another reason', - 'reason l another reason that is the recent case', - ], - usage: '[caseID | "l" | "latest"] [...reason]', - aliases: ['set-reason', 'r'], - name: 'reason', - }); - } - - async run(msg: CommandMessage, [caseID, ...reason]: [string, ...string[]]) { - if (!caseID) return msg.reply('Missing case ID.'); - - const id = Number(caseID); - if (isNaN(id)) return msg.reply('Case ID was not a number.'); - - let caseModel = await this.database.cases.get(msg.guild.id, id); - if (!caseModel) return msg.reply(`Case with ID #**${id}** was not found.`); - - if (reason.includes(' | ') && ms(reason.join(' ').split(' | ')[1]) !== undefined) - await msg.reply( - 'Due to infrastructure issues with some internal stuff, editing times will be deprecated & removed in a future release.' - ); - - await this.database.cases.update(msg.guild.id, caseModel.index, { - reason: reason.join(' ') || 'No reason was provided.', - }); - - caseModel = (await this.database.cases.get(msg.guild.id, id)) as unknown as CaseEntity; - if (caseModel.messageID !== null && msg.settings.modlogChannelID !== null) { - const channel = await this.discord.getChannel(msg.settings.modlogChannelID!); - - if (channel === null) - return msg.reply( - 'unknown error occured, report to devs here under <#824071651486335036>: https://discord.gg/ATmjFH9kMH' - ); - - const message = await this.discord.client.getMessage(channel!.id, caseModel.messageID!); - await this.punishments.editModLog(caseModel, message); - - return msg.reply(`Updated case #**${caseModel.index}** with reason **${reason.join(' ') || '(unknown)'}**`); - } - - return msg.reply( - "Unable to edit case due to no mod-log channel or that case didn't create a message in the mod-log." - ); - } - - @Subcommand('<...reason>', ['l']) - async latest(msg: CommandMessage, reason: string[]) { - const latestCases = await this.database.cases.getAll(msg.guild.id); - if (!latestCases.length) return msg.reply('There are no recent cases to edit, maybe punish someone?'); - - if (reason.includes(' | ') && ms(reason.join(' ').split(' | ')[1]) !== undefined) - await msg.reply( - 'Due to infrastructure issues with some internal stuff, editing times will be deprecated & removed in a future release.' - ); - - let latestCaseModel = latestCases[latestCases.length - 1]; // .last(); when :woeme: - await this.database.cases.update(msg.guild.id, latestCaseModel.index, { - reason: reason.join(' ') || 'No reason was provided.', - }); - - latestCaseModel = await this.database.cases.get(msg.guild.id, latestCaseModel.index).then((r) => r!); - if (latestCaseModel.messageID !== null && msg.settings.modlogChannelID !== null) { - const channel = await this.discord.getChannel(msg.settings.modlogChannelID!); - - if (channel === null) - return msg.reply( - 'unknown error occured, report to devs here under <#824071651486335036>: https://discord.gg/ATmjFH9kMH' - ); - - const message = await this.discord.client.getMessage(channel!.id, latestCaseModel.messageID!); - await this.punishments.editModLog(latestCaseModel, message); - - return msg.reply(`Updated case #**${latestCaseModel.index}** with reason **${reason.join(' ') || '(unknown)'}**`); - } - - return msg.reply( - "Unable to edit case due to no mod-log channel or that case didn't create a message in the mod-log." - ); - } -} diff --git a/src/commands/moderation/SoftbanCommand.ts b/src/commands/moderation/SoftbanCommand.ts deleted file mode 100644 index 3ea56c40..00000000 --- a/src/commands/moderation/SoftbanCommand.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User } from 'eris'; -import { Command, CommandMessage } from '../../structures'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Permissions from '../../util/Permissions'; -import Discord from '../../components/Discord'; - -interface Flags { - days?: string | true; - d?: string | true; -} - -export default class SoftbanCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'banMembers', - botPermissions: 'banMembers', - description: 'descriptions.softban', - category: Categories.Moderation, - examples: [ - 'softban 154254569632587456', - 'softban 154254569632587456 bad!', - 'softban @Nino bad bot!', - 'softban @Nino bad bot! -d 7', - 'softban 154254569632587456 bad bot! --days=7', - ], - usage: ' [reason] [--days | -d]', - name: 'softban', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: [string, ...string[]]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) - return msg.reply( - `Bot or user **${user.username}#${user.discriminator}** must be in the guild to perform this action.` - ); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member.id === this.discord.client.user.id) - return msg.reply(';w; why would you soft-ban me from here? **(/。\)**'); - - if (member instanceof Member) { - // this won't work for banning members not in this guild - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - const flags = msg.flags(); - if (typeof flags.days === 'boolean' || typeof flags.d === 'boolean') - return msg.reply('The `--days` flag must have a value appended. Example: `--days=7` or `-d 7`'); - - const days = flags.days ?? flags.d ?? 7; - if (Number(days) > 7) return msg.reply('You can only concat 7 days worth of messages'); - - try { - await this.punishments.apply({ - attachments: msg.attachments, - moderator: msg.author, - publish: true, - reason: reason.join(' ') || 'No reason was provided.', - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - soft: true, - type: PunishmentType.Ban, - days: Number(days), - }); - - return msg.reply( - `${user.bot ? 'Bot' : 'User'} **${user.username}#${user.discriminator}** has been banned${ - reason ? ` *for ${reason}*` : '' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/TimeoutsCommand.ts b/src/commands/moderation/TimeoutsCommand.ts deleted file mode 100644 index 83ce9d84..00000000 --- a/src/commands/moderation/TimeoutsCommand.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import type { Timeout } from '../../components/timeouts/types'; -import { Categories } from '../../util/Constants'; -import { firstUpper } from '@augu/utils'; -import { Inject } from '@augu/lilith'; -import Discord from '../../components/Discord'; -import Redis from '../../components/Redis'; - -export default class TimeoutsCommand extends Command { - @Inject - private discord!: Discord; - - @Inject - private redis!: Redis; - - constructor() { - super({ - userPermissions: 'manageMessages', - botPermissions: 'manageMessages', - description: 'descriptions.timeouts', - category: Categories.Moderation, - examples: ['timeouts', 'timeouts unban'], - name: 'timeouts', - }); - } - - async run(msg: CommandMessage, [type]: [string]) { - return type !== undefined ? this._sendTimeoutsAsSpecific(msg, type) : this._sendTimeouts(msg); - } - - private async _sendTimeoutsAsSpecific(msg: CommandMessage, type: string) { - const timeouts = await this.redis.client - .hget('nino:timeouts', msg.guild.id) - .then((value) => (value !== null ? JSON.parse(value) : [])) - .catch(() => [] as Timeout[]); - - if (!timeouts.length) return msg.reply(`Guild **${msg.guild.name}** doesn't have any concurrent timeouts.`); - - const all = timeouts.filter((p) => p.type.toLowerCase() === type.toLowerCase()); - if (!all.length) return msg.reply(`Punishment type **${type}** didn't have any timeouts.`); - - const h = await Promise.all( - all.slice(0, 10).map(async (pkt, idx) => { - const user = await this.discord - .getUser(pkt.user) - .then((user) => - user === null - ? { - username: 'Unknown User', - discriminator: '0000', - id: pkt.user, - } - : user! - ) - .catch(() => ({ - username: 'Unknown User', - discriminator: '0000', - id: pkt.user, - })); - - const moderator = this.discord.client.users.get(pkt.moderator) ?? { - username: 'Unknown User', - discriminator: '0000', - }; - const issuedAt = new Date(pkt.issued); - return { - name: `❯ #${idx + 1}: User ${user!.username}#${user!.discriminator}`, - value: [ - `• **Issued At**: ${issuedAt.toUTCString()}`, - `• **Expires At**: ${new Date(pkt.expired).toUTCString()}`, - `• **Moderator**: ${moderator.username}#${moderator.discriminator}`, - `• **Reason**: ${pkt.reason ?? '*No reason was defined.*'}`, - ].join('\n'), - inline: true, - }; - }) - ); - - const embed = EmbedBuilder.create() - .setAuthor( - `[ Timeouts in ${msg.guild.name} (${msg.guild.id}) ]`, - undefined, - msg.guild.dynamicIconURL('png', 1024) - ) - .addFields(h) - .setFooter('Only showing 10 entries.'); - - return msg.reply(embed); - } - - private async _sendTimeouts(msg: CommandMessage) { - const timeouts = await this.redis.client - .hget('nino:timeouts', msg.guild.id) - .then((value) => (value !== null ? JSON.parse(value) : [])) - .catch(() => [] as Timeout[]); - - if (!timeouts.length) return msg.reply(`Guild **${msg.guild.name}** doesn't have any concurrent timeouts.`); - - const h = await Promise.all( - timeouts.slice(0, 10).map(async (pkt, idx) => { - const user = await this.discord - .getUser(pkt.user) - .then((user) => - user === null - ? { - username: 'Unknown User', - discriminator: '0000', - id: pkt.user, - } - : user! - ) - .catch(() => ({ - username: 'Unknown User', - discriminator: '0000', - id: pkt.user, - })); - - const moderator = this.discord.client.users.get(pkt.moderator) ?? { - username: 'Unknown User', - discriminator: '0000', - }; - const issuedAt = new Date(pkt.issued); - return { - name: `❯ #${idx + 1}: User ${user!.username}#${user!.discriminator}`, - value: [ - `• **Issued At**: ${issuedAt.toUTCString()}`, - `• **Expires At**: ${new Date(pkt.expired).toDateString()}`, - `• **Moderator**: ${moderator.username}#${moderator.discriminator}`, - `• **Reason**: ${pkt.reason ?? '*No reason was defined.*'}`, - `• **Punishment**: ${firstUpper(pkt.type)}`, - ].join('\n'), - inline: true, - }; - }) - ); - - const embed = EmbedBuilder.create() - .setAuthor( - `[ Timeouts in ${msg.guild.name} (${msg.guild.id}) ]`, - undefined, - msg.guild.dynamicIconURL('png', 1024) - ) - .addFields(h) - .setFooter('Only showing 10 entries.'); - - return msg.reply(embed); - } -} diff --git a/src/commands/moderation/UnbanCommand.ts b/src/commands/moderation/UnbanCommand.ts deleted file mode 100644 index b5323eeb..00000000 --- a/src/commands/moderation/UnbanCommand.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Redis from '../../components/Redis'; - -export default class UnbanCommand extends Command { - @Inject - private punishments!: PunishmentService; - - @Inject - private redis!: Redis; - - constructor() { - super({ - userPermissions: 'banMembers', - botPermissions: 'banMembers', - description: 'descriptions.ban', - category: Categories.Moderation, - examples: ['unban @Nino', 'unban @Nino some reason!'], - aliases: ['unbent', 'unbean'], - usage: ' [reason]', - name: 'unban', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: [string, ...string[]]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - try { - await this.punishments.apply({ - moderator: msg.author, - publish: true, - reason: reason.join(' ') || 'No description was provided.', - member: { id: userID, guild: msg.guild }, - type: PunishmentType.Unban, - }); - - const timeouts = await this.redis.getTimeouts(msg.guild.id); - const available = timeouts.filter( - (pkt) => pkt.type !== 'unban' && pkt.user !== userID && pkt.guild === msg.guild.id - ); - - await this.redis.client.hmset('nino:timeouts', [msg.guild.id, available]); - return msg.reply('User or bot has been unbanned successfully.'); - } catch (ex) { - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/UnmuteCommand.ts b/src/commands/moderation/UnmuteCommand.ts deleted file mode 100644 index 11a3de55..00000000 --- a/src/commands/moderation/UnmuteCommand.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { DiscordRESTError, User } from 'eris'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Permissions from '../../util/Permissions'; -import Discord from '../../components/Discord'; -import Redis from '../../components/Redis'; - -export default class UnmuteCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly redis!: Redis; - - constructor() { - super({ - userPermissions: 'kickMembers', - botPermissions: 'manageRoles', - description: 'descriptions.unmute', - category: Categories.Moderation, - examples: ['unmute 1245454585452365896', 'unmute 1245454585452365896 some reason'], - name: 'unmute', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot unmute members outside the server.'); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you kicking the owner, you idiot."); - - if (member.id === this.discord.client.user.id) return msg.reply("I don't have the Muted role."); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - - if (msg.settings.mutedRoleID !== undefined && !member.roles.includes(msg.settings.mutedRoleID)) - return msg.reply('Member is already un-muted.'); - - try { - await this.punishments.apply({ - attachments: msg.attachments, - moderator: msg.author, - publish: true, - reason: reason.join(' ') || 'No reason was provided', - member: msg.guild.members.get(user.id)!, - type: PunishmentType.Unmute, - }); - - const timeouts = await this.redis.getTimeouts(msg.guild.id); - const available = timeouts.filter( - (pkt) => pkt.type !== 'unmute' && pkt.user !== userID && pkt.guild === msg.guild.id - ); - - await this.redis.client.hmset('nino:timeouts', [msg.guild.id, available]); - return msg.reply( - `:thumbsup: Successfully unmuted **${user.username}#${user.discriminator}**${ - reason.length > 0 ? `, for **${reason.join(' ')}**` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/WarnCommand.ts b/src/commands/moderation/WarnCommand.ts deleted file mode 100644 index c70ad527..00000000 --- a/src/commands/moderation/WarnCommand.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { DiscordRESTError, User } from 'eris'; -import PunishmentService from '../../services/PunishmentService'; -import { Categories } from '../../util/Constants'; -import Permissions from '../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import { pluralize } from '@augu/utils'; - -export default class WarnCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'kickMembers', - description: 'descriptions.warn', - category: Categories.Moderation, - examples: ['warn 280158289667555328 no'], - aliases: ['addwarn'], - name: 'warn', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot warn members outside the server.'); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you kicking the owner, you idiot."); - - if (member.id === this.discord.client.user.id) return msg.reply(';w; why would you warn me? **(/。\)**'); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - - try { - await this.punishments.createWarning( - msg.guild.members.get(user.id)!, - reason.join(' ') || 'No reason was provided.', - 1 - ); - - const warnings = await this.database.warnings - .getAll(msg.guild.id, user.id) - .then((warnings) => warnings.filter((warns) => warns.amount > 0)); - const count = warnings.reduce((acc, curr) => acc + curr.amount, 0); - - return msg.reply( - `:thumbsup: Warned **${user.username}#${user.discriminator}**${ - reason.length > 0 ? ` for **${reason.join(' ')}**` : ' ' - }, they now have **${pluralize('warning', count)}**.` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/WarningsCommand.ts b/src/commands/moderation/WarningsCommand.ts deleted file mode 100644 index 222b6b07..00000000 --- a/src/commands/moderation/WarningsCommand.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { DiscordRESTError, User } from 'eris'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -export default class WarningsCommand extends Command { - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - description: 'descriptions.warnings', - category: Categories.Moderation, - examples: ['warnings 280158289667555328'], - aliases: ['warns', 'view-warns'], - name: 'warnings', - }); - } - - async run(msg: CommandMessage, [userID]: string[]) { - if (!userID) return msg.reply('No user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User with ID "${userID}" was not found. (assuming it's a deleted user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - if (!msg.guild.members.has(user.id)) return msg.reply('Cannot view warnings outside of this guild.'); - - if (user.bot) return msg.reply('Bots cannot be warned.'); - - const member = msg.guild.members.get(user.id)!; - if (member.id === msg.guild.ownerID) return msg.reply('Why would the server owner have any warnings...?'); - - if (member.id === this.discord.client.user.id) return msg.reply('W-why would I have any warnings?!'); - - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply("Moderators or administrators don't have warnings attached to them."); - - const warnings = await this.database.warnings - .getAll(msg.guild.id, user.id) - .then((warnings) => warnings.filter((warn) => warn.amount > 0)); - if (warnings.length === 0) - return msg.reply(`User **${user.username}#${user.discriminator}** doesn't have any warnings attached to them.`); - - const embed = EmbedBuilder.create() - .setTitle(`[ ${user.username}#${user.discriminator} (${user.id}) <~> Warnings ]`) - .setDescription(`They have a total of **${warnings.length}** warnings attached`) - .addFields( - warnings.map((warn, idx) => ({ - name: `❯ Warning #${idx + 1}`, - value: [`• **Amount**: ${warn.amount}`, `• **Reason**: ${warn.reason ?? '(no reason was provided)'}`].join( - '\n' - ), - inline: true, - })) - ); - - return msg.reply(embed); - } -} diff --git a/src/commands/moderation/voice/VoiceDeafenCommand.ts b/src/commands/moderation/voice/VoiceDeafenCommand.ts deleted file mode 100644 index ebef0122..00000000 --- a/src/commands/moderation/voice/VoiceDeafenCommand.ts +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User, VoiceChannel } from 'eris'; -import { Command, CommandMessage } from '../../../structures'; -import { PunishmentType } from '../../../entities/PunishmentsEntity'; -import PunishmentService from '../../../services/PunishmentService'; -import { Categories } from '../../../util/Constants'; -import Permissions from '../../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../../components/Discord'; -import ms = require('ms'); - -export default class VoiceDeafenCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'voiceMuteMembers', - description: 'descriptions.voice_deafen', - category: Categories.Moderation, - examples: ['vcdeaf <@256548545856545896>', 'vcdeaf 3', 'vcdeaf 3 some reason!', 'vcdeaf 3 some reason! | 3d'], - aliases: ['deafvc', 'vcdeaf'], - name: 'vcdeafen', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - const member = msg.guild.members.get(user.id) ?? { - id: user.id, - guild: msg.guild, - }; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member instanceof Member) { - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - if (msg.member.voiceState.channelID === null) - return msg.reply('You must be in a voice channel to perform this action.'); - - const channel = this.discord.client.getChannel(msg.member.voiceState.channelID) as VoiceChannel; - if (channel.voiceMembers.size === 1) return msg.reply('You must be in an active voice channel.'); - - if (!channel.voiceMembers.has(user.id)) - return msg.reply(`Member **${user.username}#${user.discriminator}** is not in this voice channel.`); - - const voiceState = channel.voiceMembers.get(user.id)!.voiceState; - if (voiceState.deaf === true) - return msg.reply(`Member **${user.username}#${user.discriminator}** is already server deafened.`); - - const areason = reason.join(' '); - let actualReason: string | undefined = undefined; - let time: string | undefined = undefined; - - if (areason !== '') { - const [r, t] = areason.split(' | '); - actualReason = r; - time = t; - } - - // Nino needs to join the voice channel they're in. - await this.discord.client.joinVoiceChannel(msg.member.voiceState.channelID); - try { - await this.punishments.apply({ - moderator: msg.author, - publish: true, - reason: actualReason, - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - type: PunishmentType.VoiceDeafen, - time: time !== undefined ? ms(time!) : undefined, - }); - - this.discord.client.leaveVoiceChannel(msg.member.voiceState.channelID); - return msg.reply( - `:thumbsup: Member **${user.username}#${user.discriminator}** has been server deafened in voice channels.${ - reason.length ? ` *for ${reason.join(' ')}${time !== undefined ? `, for ${time}*` : '*'}` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/voice/VoiceKickCommand.ts b/src/commands/moderation/voice/VoiceKickCommand.ts deleted file mode 100644 index 9b9142f9..00000000 --- a/src/commands/moderation/voice/VoiceKickCommand.ts +++ /dev/null @@ -1,325 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User, VoiceChannel } from 'eris'; -import { Command, CommandMessage, Subcommand } from '../../../structures'; -import { PunishmentType } from '../../../entities/PunishmentsEntity'; -import PunishmentService from '../../../services/PunishmentService'; -import { Categories, CHANNEL_REGEX } from '../../../util/Constants'; -import Permissions from '../../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../../components/Discord'; -import ms = require('ms'); - -const condition = (discord: Discord, member: Member) => - member.user.id !== discord.client.user.id && // If it's not Nino - member.guild.ownerID === member.user.id && // If the owner is in the voice channel - member.permissions.has('voiceMuteMembers'); // If the member is a voice moderator - -const botCondition = (discord: Discord, member: Member) => - member.user.id !== discord.client.user.id && // If it's not Nino - member.bot === true; // If it's a bot - -export default class VoiceKickCommand extends Command { - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'voiceMoveMembers', - description: 'descriptions.voice_kick', - category: Categories.Moderation, - examples: [ - 'vckick | Kick all members in your voice channel', - 'vckick <#521254554543485646> | Kick all members in a specific channel', - 'vckick bots | Kick all bots in your voice channel', - 'vckick bots <#521254554543485646> | Kick all bots in a specific channel', - ], - aliases: ['kickvc'], - name: 'vckick', - }); - } - - async run(msg: CommandMessage, [channelOrAmount]: [string?]) { - if (!channelOrAmount) { - const message = await msg.reply('Kicking all members...'); - if (!msg.member.voiceState.channelID) return msg.reply('You must be in a voice channel.'); - - const id = msg.member.voiceState.channelID; // cache it if they decide to leave - const voiceChan = await this.discord.getChannel(id); - if (voiceChan === null) return msg.error("Unknown voice channel you're in."); - - if ( - !voiceChan.permissionsOf(this.discord.client.user.id).has('voiceConnect') || - !voiceChan.permissionsOf(this.discord.client.user.id).has('voiceMoveMembers') - ) - return msg.reply('I do not have permissions to **Connect** or **Move Members**.'); - - const members = voiceChan.voiceMembers.filter((c) => condition(this.discord, c)); - if (members.length === 0) - return msg.error( - 'No users were in this channel. (excluding myself, the owner, and people with **`Voice Mute Members`** permission)' - ); - - if (members.length === 1) { - await this.discord.client.joinVoiceChannel(id); - try { - await members[0].edit( - { channelID: null }, - encodeURIComponent( - `[Voice Kick] Told to kick ${members[0].username}#${members[0].discriminator} (${members[0].id})` - ) - ); - } catch { - return msg.error(`Unable to kick **${members[0].username}#${members[0].discriminator}**.`); - } - } - - await this.discord.client.joinVoiceChannel(id); - await message.edit(`ℹ️ **Removing ${members.length} members...**`); - - let success = 0; - let errored = 0; - for (const member of members) { - try { - success++; - await member.edit( - { channelID: null }, - encodeURIComponent(`[Voice Kick] Told to kick ${member.username}#${member.discriminator} (${member.id})`) - ); - } catch { - errored++; - } - } - - const errorRate = ((errored / members.length) * 100).toFixed(2); - const successRate = ((success / members.length) * 100).toFixed(2); - this.discord.client.leaveVoiceChannel(id); - - await message.delete(); - return msg.reply( - [ - `Successfully kicked **${success}/${members.length}** members.`, - '', - `> ${msg.successEmote} **Success Rate**: ${successRate}%`, - `> ${msg.errorEmote} **Error Rate**: ${errorRate}%`, - ].join('\n') - ); - } - - const channel = await this.discord.getChannel(channelOrAmount); - - // if I can recall correctly, IDs are around 15-21 but I could be wrong. - // ~ Noel - if (channel === null) return msg.reply(`Channel with ID **${channelOrAmount}** was not found.`); - - if (channel.type !== 2) return msg.reply('Channel was not a voice channel.'); - - if ( - !channel.permissionsOf(this.discord.client.user.id).has('voiceConnect') || - !channel.permissionsOf(this.discord.client.user.id).has('voiceMoveMembers') - ) - return msg.reply('I do not have permissions to **Connect** or **Move Members**.'); - - const members = channel.voiceMembers.filter((c) => condition(this.discord, c)); - if (members.length === 0) - return msg.error( - 'No users were in this channel. (excluding myself, the owner, and people with **`Voice Mute Members`** permission)' - ); - - if (members.length === 1) { - await this.discord.client.joinVoiceChannel(channel.id); - try { - await members[0].edit( - { channelID: null }, - encodeURIComponent( - `[Voice Kick] Told to kick ${members[0].username}#${members[0].discriminator} (${members[0].id})` - ) - ); - } catch { - return msg.error(`Unable to kick **${members[0].username}#${members[0].discriminator}**.`); - } - } - - const message = await msg.reply(`ℹ️ Kicking all members in <#${channel.id}> (${members.length} members)`); - await this.discord.client.joinVoiceChannel(channel.id); - - let success = 0; - let errored = 0; - for (const member of members) { - try { - success++; - await member.edit( - { channelID: null }, - encodeURIComponent(`[Voice Kick] Told to kick ${member.username}#${member.discriminator} (${member.id})`) - ); - } catch { - errored++; - } - } - - const errorRate = ((errored / members.length) * 100).toFixed(2); - const successRate = ((success / members.length) * 100).toFixed(2); - this.discord.client.leaveVoiceChannel(channel.id); - - await message.delete(); - return msg.reply( - [ - `Successfully kicked **${success}/${members.length}** members.`, - '', - `> ${msg.successEmote} **Success Rate**: ${successRate}%`, - `> ${msg.errorEmote} **Error Rate**: ${errorRate}%`, - ].join('\n') - ); - } - - @Subcommand('') - async bots(msg: CommandMessage, [channelOrAmount]: [string?]) { - if (!channelOrAmount) { - const message = await msg.reply('Kicking all bots...'); - if (!msg.member.voiceState.channelID) return msg.reply('You must be in a voice channel.'); - - const id = msg.member.voiceState.channelID; // cache it if they decide to leave - const voiceChan = await this.discord.getChannel(id); - if (voiceChan === null) return msg.error("Unknown voice channel you're in."); - - if ( - !voiceChan.permissionsOf(this.discord.client.user.id).has('voiceConnect') || - !voiceChan.permissionsOf(this.discord.client.user.id).has('voiceMoveMembers') - ) - return msg.reply('I do not have permissions to **Connect** or **Move Members**.'); - - const members = voiceChan.voiceMembers.filter((c) => botCondition(this.discord, c)); - if (members.length === 0) return msg.error('No bots were in this channel. (excluding myself)'); - - if (members.length === 1) { - await this.discord.client.joinVoiceChannel(id); - try { - await members[0].edit( - { channelID: null }, - encodeURIComponent( - `[Voice Kick] Told to kick ${members[0].username}#${members[0].discriminator} (${members[0].id})` - ) - ); - } catch { - return msg.error(`Unable to kick bot **${members[0].username}#${members[0].discriminator}**.`); - } - } - - await this.discord.client.joinVoiceChannel(id); - await message.edit(`ℹ️ **Removing ${members.length} bots...**`); - - let success = 0; - let errored = 0; - for (const member of members) { - try { - success++; - await member.edit( - { channelID: null }, - encodeURIComponent(`[Voice Kick] Told to kick ${member.username}#${member.discriminator} (${member.id})`) - ); - } catch { - errored++; - } - } - - const errorRate = ((errored / members.length) * 100).toFixed(2); - const successRate = ((success / members.length) * 100).toFixed(2); - this.discord.client.leaveVoiceChannel(id); - - await message.delete(); - return msg.reply( - [ - `Successfully kicked **${success}/${members.length}** bots.`, - '', - `> ${msg.successEmote} **Success Rate**: ${successRate}%`, - `> ${msg.errorEmote} **Error Rate**: ${errorRate}%`, - ].join('\n') - ); - } - - const channel = await this.discord.getChannel(channelOrAmount); - - // if I can recall correctly, IDs are around 15-21 but I could be wrong. - // ~ Noel - if (channel === null) return msg.reply(`Channel with ID **${channelOrAmount}** was not found.`); - - if (channel.type !== 2) return msg.reply('Channel was not a voice channel.'); - - if ( - !channel.permissionsOf(this.discord.client.user.id).has('voiceConnect') || - !channel.permissionsOf(this.discord.client.user.id).has('voiceMoveMembers') - ) - return msg.reply('I do not have permissions to **Connect** or **Move Members**.'); - - const members = channel.voiceMembers.filter((c) => botCondition(this.discord, c)); - if (members.length === 0) - return msg.error( - 'No users were in this channel. (excluding myself, the owner, and people with **`Voice Mute Members`** permission)' - ); - - if (members.length === 1) { - await this.discord.client.joinVoiceChannel(channel.id); - try { - await members[0].edit( - { channelID: null }, - encodeURIComponent( - `[Voice Kick] Told to kick ${members[0].username}#${members[0].discriminator} (${members[0].id})` - ) - ); - } catch { - return msg.error(`Unable to kick **${members[0].username}#${members[0].discriminator}**.`); - } - } - - const message = await msg.reply(`ℹ️ Kicking all members in <#${channel.id}> (${members.length} members)`); - await this.discord.client.joinVoiceChannel(channel.id); - - let success = 0; - let errored = 0; - for (const member of members) { - try { - success++; - await member.edit( - { channelID: null }, - encodeURIComponent(`[Voice Kick] Told to kick ${member.username}#${member.discriminator} (${member.id})`) - ); - } catch { - errored++; - } - } - - const errorRate = ((errored / members.length) * 100).toFixed(2); - const successRate = ((success / members.length) * 100).toFixed(2); - this.discord.client.leaveVoiceChannel(channel.id); - - await message.delete(); - return msg.reply( - [ - `Successfully kicked **${success}/${members.length}** members.`, - '', - `> ${msg.successEmote} **Success Rate**: ${successRate}%`, - `> ${msg.errorEmote} **Error Rate**: ${errorRate}%`, - ].join('\n') - ); - } -} diff --git a/src/commands/moderation/voice/VoiceMuteCommand.ts b/src/commands/moderation/voice/VoiceMuteCommand.ts deleted file mode 100644 index c2a22611..00000000 --- a/src/commands/moderation/voice/VoiceMuteCommand.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User, VoiceChannel } from 'eris'; -import { Command, CommandMessage } from '../../../structures'; -import { PunishmentType } from '../../../entities/PunishmentsEntity'; -import PunishmentService from '../../../services/PunishmentService'; -import { Categories } from '../../../util/Constants'; -import Permissions from '../../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../../components/Discord'; -import ms = require('ms'); - -export default class VoiceMuteCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - description: 'descriptions.voice_mute', - category: Categories.Moderation, - examples: ['vcmute <@256548545856545896>', 'vcmute 3', 'vcmute 3 some reason!', 'vcmute 3 some reason! | 3d'], - aliases: ['mutevc'], - name: 'vcmute', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - const member = msg.guild.members.get(user.id) ?? { - id: user.id, - guild: msg.guild, - }; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member instanceof Member) { - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - if (msg.member.voiceState.channelID === null) - return msg.reply('You must be in a voice channel to perform this action.'); - - const channel = this.discord.client.getChannel(msg.member.voiceState.channelID) as VoiceChannel; - if (channel.voiceMembers.size === 1) return msg.reply('You must be in an active voice channel.'); - - if (!channel.voiceMembers.has(user.id)) - return msg.reply(`Member **${user.username}#${user.discriminator}** is not in this voice channel.`); - - const voiceState = channel.voiceMembers.get(user.id)!.voiceState; - if (voiceState.mute === true) - return msg.reply(`Member **${user.username}#${user.discriminator}** is already server muted.`); - - const areason = reason.join(' '); - let actualReason: string | undefined = undefined; - let time: string | undefined = undefined; - - if (areason !== '') { - const [r, t] = areason.split(' | '); - actualReason = r; - time = t; - } - - // Nino needs to join the voice channel they're in. - await this.discord.client.joinVoiceChannel(msg.member.voiceState.channelID); - try { - await this.punishments.apply({ - moderator: msg.author, - publish: true, - reason: actualReason, - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - type: PunishmentType.VoiceMute, - time: time !== undefined ? ms(time!) : undefined, - }); - - this.discord.client.leaveVoiceChannel(msg.member.voiceState.channelID); - return msg.reply( - `:thumbsup: Member **${user.username}#${user.discriminator}** has been server muted in voice channels.${ - reason.length ? ` *for ${reason.join(' ')}${time !== undefined ? `, for ${time}*` : '*'}` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/voice/VoiceUndeafenCommand.ts b/src/commands/moderation/voice/VoiceUndeafenCommand.ts deleted file mode 100644 index 798c1556..00000000 --- a/src/commands/moderation/voice/VoiceUndeafenCommand.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User, VoiceChannel } from 'eris'; -import { Command, CommandMessage } from '../../../structures'; -import { PunishmentType } from '../../../entities/PunishmentsEntity'; -import PunishmentService from '../../../services/PunishmentService'; -import { Categories } from '../../../util/Constants'; -import Permissions from '../../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../../components/Discord'; -import ms = require('ms'); - -export default class VoiceUndeafenCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - description: 'descriptions.voice_undeafen', - category: Categories.Moderation, - examples: ['vcundeaf <@256548545856545896>', 'vcundeaf 3 some reason!'], - aliases: ['undeafvc'], - name: 'vcundeaf', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - const member = msg.guild.members.get(user.id) ?? { - id: user.id, - guild: msg.guild, - }; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member instanceof Member) { - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - if (msg.member.voiceState.channelID === null) - return msg.reply('You must be in a voice channel to perform this action.'); - - const channel = this.discord.client.getChannel(msg.member.voiceState.channelID) as VoiceChannel; - if (channel.voiceMembers.size === 1) return msg.reply('You must be in an active voice channel.'); - - if (!channel.voiceMembers.has(user.id)) - return msg.reply(`Member **${user.username}#${user.discriminator}** is not in this voice channel.`); - - const voiceState = channel.voiceMembers.get(user.id)!.voiceState; - if (!voiceState.deaf) - return msg.reply(`Member **${user.username}#${user.discriminator}** is already not deafened.`); - - const areason = reason.join(' '); - let actualReason: string | undefined = undefined; - let time: string | undefined = undefined; - - if (areason !== '') { - const [r, t] = areason.split(' | '); - actualReason = r; - time = t; - } - - // Nino needs to join the voice channel they're in. - await this.discord.client.joinVoiceChannel(msg.member.voiceState.channelID); - try { - await this.punishments.apply({ - moderator: msg.author, - publish: true, - reason: actualReason, - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - type: PunishmentType.VoiceUndeafen, - time: time !== undefined ? ms(time!) : undefined, - }); - - this.discord.client.leaveVoiceChannel(msg.member.voiceState.channelID); - return msg.reply( - `:thumbsup: Member **${user.username}#${user.discriminator}** has been server undeafen in voice channels.${ - reason.length ? ` *for ${reason.join(' ')}${time !== undefined ? `, for ${time}*` : '*'}` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/moderation/voice/VoiceUnmuteCommand.ts b/src/commands/moderation/voice/VoiceUnmuteCommand.ts deleted file mode 100644 index 5715c735..00000000 --- a/src/commands/moderation/voice/VoiceUnmuteCommand.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { DiscordRESTError, Member, User, VoiceChannel } from 'eris'; -import { Command, CommandMessage } from '../../../structures'; -import { PunishmentType } from '../../../entities/PunishmentsEntity'; -import PunishmentService from '../../../services/PunishmentService'; -import { Categories } from '../../../util/Constants'; -import Permissions from '../../../util/Permissions'; -import { Inject } from '@augu/lilith'; -import Discord from '../../../components/Discord'; -import ms = require('ms'); - -export default class VoiceUnmuteCommand extends Command { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - description: 'descriptions.voice_mute', - category: Categories.Moderation, - examples: ['vcunmute <@256548545856545896>', 'vcunmute 3 some reason!'], - aliases: ['unmutevc'], - name: 'vcunmute', - }); - } - - async run(msg: CommandMessage, [userID, ...reason]: string[]) { - if (!userID) return msg.reply('No bot or user was specified.'); - - let user!: User | null; - try { - user = await this.discord.getUser(userID); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10013) - return msg.reply(`User or bot with ID "${userID}" was not found. (assuming it's a deleted bot or user)`); - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - - if (user === null) return msg.reply('Bot or user was not found.'); - - const member = msg.guild.members.get(user.id) ?? { - id: user.id, - guild: msg.guild, - }; - if (member.id === msg.guild.ownerID) - return msg.reply("I don't think I can perform this action due to you banning the owner, you idiot."); - - if (member instanceof Member) { - if (member.permissions.has('administrator') || member.permissions.has('banMembers')) - return msg.reply( - `I can't perform this action due to **${user.username}#${user.discriminator}** being a server moderator.` - ); - - if (!Permissions.isMemberAbove(msg.member, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above as you.`); - - if (!Permissions.isMemberAbove(msg.self!, member)) - return msg.reply(`User **${user.username}#${user.discriminator}** is the same or above me.`); - } - - if (msg.member.voiceState.channelID === null) - return msg.reply('You must be in a voice channel to perform this action.'); - - const channel = this.discord.client.getChannel(msg.member.voiceState.channelID) as VoiceChannel; - if (channel.voiceMembers.size === 1) return msg.reply('You must be in an active voice channel.'); - - if (!channel.voiceMembers.has(user.id)) - return msg.reply(`Member **${user.username}#${user.discriminator}** is not in this voice channel.`); - - const voiceState = channel.voiceMembers.get(user.id)!.voiceState; - if (!voiceState.mute) return msg.reply(`Member **${user.username}#${user.discriminator}** is already unmuted.`); - - const areason = reason.join(' '); - let actualReason: string | undefined = undefined; - let time: string | undefined = undefined; - - if (areason !== '') { - const [r, t] = areason.split(' | '); - actualReason = r; - time = t; - } - - // Nino needs to join the voice channel they're in. - await this.discord.client.joinVoiceChannel(msg.member.voiceState.channelID); - try { - await this.punishments.apply({ - moderator: msg.author, - publish: true, - reason: actualReason, - member: msg.guild.members.get(user.id) || { - id: user.id, - guild: msg.guild, - }, - type: PunishmentType.VoiceMute, - time: time !== undefined ? ms(time!) : undefined, - }); - - this.discord.client.leaveVoiceChannel(msg.member.voiceState.channelID); - return msg.reply( - `:thumbsup: Member **${user.username}#${user.discriminator}** has been unmuted in voice channels.${ - reason.length ? ` *for ${reason.join(' ')}${time !== undefined ? `, for ${time}*` : '*'}` : '.' - }` - ); - } catch (ex) { - if (ex instanceof DiscordRESTError && ex.code === 10007) { - return msg.reply( - `Member **${user.username}#${user.discriminator}** has left but been detected. Kinda weird if you ask me, to be honest.` - ); - } - - return msg.reply( - [ - 'Uh-oh! An internal error has occured while running this.', - 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', - '', - '```js', - ex.stack ?? '<... no stacktrace? ...>', - '```', - ].join('\n') - ); - } - } -} diff --git a/src/commands/owner/BlacklistCommand.ts b/src/commands/owner/BlacklistCommand.ts deleted file mode 100644 index 95c884c2..00000000 --- a/src/commands/owner/BlacklistCommand.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { Categories, Color } from '../../util/Constants'; -import { BlacklistType } from '../../entities/BlacklistEntity'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import Config from '../../components/Config'; - -export default class BlacklistCommand extends Command { - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - @Inject - private config!: Config; - - constructor() { - super({ - description: 'Blacklists a user or guild from Nino', - category: Categories.Owner, - hidden: true, - ownerOnly: true, - aliases: ['bl'], - usage: '["guild" | "user"] [id] [...reason]', - name: 'blacklist', - }); - } - - async run(msg: CommandMessage, [type, id, ...reason]: ['guild' | 'user', string, ...string[]]) { - if (!type) { - const guilds = await this.database.blacklists.getByType(BlacklistType.Guild); - const users = await this.database.blacklists.getByType(BlacklistType.User); - - return msg.reply( - new EmbedBuilder() - .setColor(Color) - .setDescription([ - `❯ **Guilds Blacklisted**: ${guilds.length.toLocaleString()}`, - `❯ **Users Blacklisted**: ${users.length.toLocaleString()}`, - ]) - ); - } - - if (!['guild', 'user'].includes(type)) - return msg.reply('Missing the type to blacklist. Available options: `user` and `guild`.'); - - if (type === 'guild') { - const guild = this.discord.client.guilds.get(id); - if (!guild) return msg.reply(`Guild **${id}** doesn't exist`); - - const entry = await this.database.blacklists.get(id); - if (entry !== undefined) return msg.reply(`Guild **${guild.name}** is already on the blacklist.`); - - await this.database.blacklists.create({ - issuer: msg.author.id, - reason: reason ? reason.join(' ') : undefined, - type: BlacklistType.Guild, - id, - }); - - return msg.reply(`:thumbsup: Blacklisted guild **${guild.name}** for *${reason ?? 'no reason provided'}*`); - } - - if (type === 'user') { - const owners = this.config.getProperty('owners') ?? []; - const user = await this.discord.getUser(id); - if (user === null) return msg.reply(`User ${id} doesn't exist.`); - - if (owners.includes(id)) return msg.reply('Cannot blacklist a owner'); - - const entry = await this.database.blacklists.get(user.id); - if (entry !== undefined) - return msg.reply(`User **${user.username}#${user.discriminator}** is already on the blacklist.`); - - await this.database.blacklists.create({ - issuer: msg.author.id, - reason: reason ? reason.join(' ') : undefined, - type: BlacklistType.User, - id: user.id, - }); - - return msg.reply( - `:thumbsup: Blacklisted user ${user.username}#${user.discriminator} for *${ - reason?.join(' ') ?? 'no reason, just felt like it.' - }*` - ); - } - } -} diff --git a/src/commands/owner/EvalCommand.ts b/src/commands/owner/EvalCommand.ts deleted file mode 100644 index fbaf9d64..00000000 --- a/src/commands/owner/EvalCommand.ts +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { Categories } from '../../util/Constants'; -import { inspect } from 'util'; -import { Inject } from '@augu/lilith'; -import Stopwatch from '../../util/Stopwatch'; -import Config from '../../components/Config'; - -export default class EvalCommand extends Command { - @Inject - private config!: Config; - - constructor() { - super({ - description: 'Evaluates JavaScript code and return a clean output', - ownerOnly: true, - hidden: true, - category: Categories.Owner, - aliases: ['evl', 'ev', 'js'], - name: 'eval', - }); - } - - async run(msg: CommandMessage, args: string[]) { - if (!args.length) return msg.reply('What do you want me to evaluate?'); - - let script = args.join(' '); - let result: any; - let depth = 1; - - const flags = msg.flags<{ - depth?: string | true; - slient?: string | true; - s?: string | true; - }>(); - - if (flags.depth === true) return msg.reply('`--depth` flag requires a input. Example: `--depth 1`'); - - if (flags.depth !== undefined) { - depth = Number(flags.depth); - if (isNaN(depth)) return msg.reply('Depth value was not a number'); - - script = script.replace(`--depth=${depth}`, ''); - } - - if (script.startsWith('```js') && script.endsWith('```')) { - script = script.replace('```js', ''); - script = script.replace('```', ''); - } - - const stopwatch = new Stopwatch(); - const isAsync = script.includes('return') || script.includes('await'); - const slient = flags.slient === true || flags.s === true; - - stopwatch.start(); - try { - result = eval(isAsync ? `(async()=>{${script}})()` : script); - - const time = stopwatch.end(); - let asyncTimer: string | undefined = undefined; - if (result instanceof Promise) { - stopwatch.restart(); - result = await result; - - asyncTimer = stopwatch.end(); - } - - if (typeof result !== 'string') - result = inspect(result, { - depth, - showHidden: false, - }); - - if (slient) return; - - const res = this.redact(result); - return msg.reply( - [`:timer: **${asyncTimer !== undefined ? `${time}<${asyncTimer}>` : time}**`, '', '```js', res, '```'].join( - '\n' - ) - ); - } catch (ex) { - const time = stopwatch.end(); - return msg.reply([`:timer: **${time}**`, '', '```js', ex.stack ?? '<... no stacktrace ...>', '```'].join('\n')); - } - } - - private redact(script: string) { - const rawConfig = this.config['config']; // yes we need the raw config cuz i dont feel like using .getProperty :woeme: - let tokens = [ - ...(rawConfig.redis.sentinels?.map((r) => r.host) ?? []), - rawConfig.database.username, - rawConfig.database.password, - rawConfig.redis.password, - rawConfig.database.host, - rawConfig.database.url, - rawConfig.redis.host, - rawConfig.sentryDsn, - rawConfig.ksoft, - rawConfig.token, - ]; - - if (rawConfig.botlists !== undefined) - tokens.push( - rawConfig.botlists.dservices, - rawConfig.botlists.dboats, - rawConfig.botlists.topgg, - rawConfig.botlists.delly, - rawConfig.botlists.dbots, - rawConfig.botlists.bfd - ); - - tokens = tokens.filter(Boolean); - return script.replace(new RegExp(tokens.join('|'), 'gi'), 'owo? nu!!!'); - } -} diff --git a/src/commands/owner/WhitelistCommand.ts b/src/commands/owner/WhitelistCommand.ts deleted file mode 100644 index 80d4775a..00000000 --- a/src/commands/owner/WhitelistCommand.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage } from '../../structures'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Config from '../../components/Config'; - -export default class WhitelistCommand extends Command { - @Inject - private database!: Database; - - @Inject - private config!: Config; - - constructor() { - super({ - description: 'Whitelists a user or guild from Nino', - category: Categories.Owner, - ownerOnly: true, - hidden: true, - aliases: ['wl'], - usage: '["guild" | "user"] [id] [...reason]', - name: 'whitelist', - }); - } - - async run(msg: CommandMessage, [type, id]: ['guild' | 'user', string]) { - if (!['guild', 'user'].includes(type)) - return msg.reply('Missing the type to blacklist. Available options: `user` and `guild`.'); - - if (type === 'guild') { - const entry = await this.database.blacklists.get(id); - if (entry === undefined) return msg.reply(`Guild **${id}** is already whitelisted`); - - await this.database.blacklists.delete(id); - return msg.reply(`:thumbsup: Whitelisted guild **${id}**.`); - } - - if (type === 'user') { - const owners = this.config.getProperty('owners') ?? []; - if (owners.includes(id)) return msg.reply('Cannot whitelist a owner'); - - const entry = await this.database.blacklists.get(id); - if (entry === undefined) return msg.reply(`User **${id}** is already whitelisted`); - - await this.database.blacklists.delete(id); - return msg.reply(`:thumbsup: Whitelisted user ${id}.`); - } - } -} diff --git a/src/commands/settings/Automod.ts b/src/commands/settings/Automod.ts deleted file mode 100644 index 61e2c6a6..00000000 --- a/src/commands/settings/Automod.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, Subcommand, EmbedBuilder } from '../../structures'; -import { Categories, Color } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; -import { TextChannel } from 'eris'; - -export default class AutomodCommand extends Command { - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: 'manageGuild', - description: 'descriptions.automod', - category: Categories.Settings, - examples: ['automod', 'automod spam', 'automod blacklist uwu owo'], - name: 'automod', - }); - } - - async run(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const embed = new EmbedBuilder() - .setColor(Color) - .setDescription([ - `• ${settings!.shortLinks ? msg.successEmote : msg.errorEmote} **Short Links** (\`${ - msg.settings.prefixes[0] - }automod shortlinks\`)`, - `• ${settings!.blacklist ? msg.successEmote : msg.errorEmote} **Blacklist Words** (\`${ - msg.settings.prefixes[0] - }automod blacklist\`)`, - `• ${settings!.mentions ? msg.successEmote : msg.errorEmote} **Mentions** (\`${ - msg.settings.prefixes[0] - }automod mentions\`)`, - `• ${settings!.dehoist ? msg.successEmote : msg.errorEmote} **Dehoisting** (\`${ - msg.settings.prefixes[0] - }automod dehoist\`)`, - `• ${settings!.invites ? msg.successEmote : msg.errorEmote} **Invites** (\`${ - msg.settings.prefixes[0] - }automod invites\`)`, - `• ${settings!.raid ? msg.successEmote : msg.errorEmote} **Raids** (\`${ - msg.settings.prefixes[0] - }automod raid\`)`, - `• ${settings!.spam ? msg.successEmote : msg.errorEmote} **Spam** (\`${ - msg.settings.prefixes[0] - }automod spam\`)`, - ]); - - return msg.reply(embed); - } - - @Subcommand() - async shortlinks(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const enabled = !settings!.shortLinks; - - await this.database.automod.update(msg.guild.id, { - shortLinks: enabled, - }); - - return msg.reply(`${enabled ? msg.successEmote : msg.errorEmote} the Shortlinks automod feature`); - } - - @Subcommand('[...words]') - async blacklist(msg: CommandMessage, [...words]: [...string[]]) { - const settings = await this.database.automod.get(msg.guild.id); - - if (!words.length) { - const type = !settings!.blacklist; - const res = await this.database.automod.update(msg.guild.id, { - blacklist: type, - }); - - const suffix = res ? 'd' : ''; - return msg.reply( - `${res ? `${msg.successEmote} Successfully` : `${msg.errorEmote} Unable to`} **${ - type ? `enable${suffix}` : `disable${suffix}` - }** the Blacklist automod feature.` - ); - } - - if (words[0] === 'remove') { - // Remove argument "remove" from the words list - words.shift(); - - const all: string[] = settings!.blacklistWords; - for (const word of words) { - const index = all.indexOf(word); - if (index !== -1) all.splice(index, 1); - } - - await this.database.automod.update(msg.guild.id, { - blacklistWords: all, - }); - - return msg.success('Successfully removed the blacklisted words. :D'); - } - - const curr = settings!.blacklistWords.concat(words); - await this.database.automod.update(msg.guild.id, { - blacklistWords: curr, - }); - - return msg.success('Successfully added the words to the blacklist. :3'); - } - - @Subcommand() - async mentions(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const type = !settings!.mentions; - - await this.database.automod.update(msg.guild.id, { - mentions: type, - }); - - return msg.reply( - `${type ? `${msg.successEmote} **Enabled**` : `${msg.errorEmote} **Disabled**`} Mentions automod feature.` - ); - } - - @Subcommand() - async invites(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const t = !settings!.invites; - - await this.database.automod.update(msg.guild.id, { - invites: !settings!.invites, - }); - - return msg.reply( - `${t ? `${msg.successEmote} **Enabled**` : `${msg.errorEmote} **Disabled**`} Invites automod feature.` - ); - } - - @Subcommand() - async dehoist(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const t = !settings!.dehoist; - - await this.database.automod.update(msg.guild.id, { - dehoist: !settings!.dehoist, - }); - - return msg.reply( - `${t ? `${msg.successEmote} **Enabled**` : `${msg.errorEmote} **Disabled**`} Dehoisting automod feature.` - ); - } - - @Subcommand() - async spam(msg: CommandMessage) { - const settings = await this.database.automod.get(msg.guild.id); - const t = !settings!.spam; - - await this.database.automod.update(msg.guild.id, { - spam: t, - }); - - return msg.reply( - `${t ? `${msg.successEmote} **Enabled**` : `${msg.errorEmote} **Disabled**`} Spam automod feature.` - ); - } - - @Subcommand('[...channels]') - async raid(msg: CommandMessage, [...channels]: string[]) { - if (!channels.length) { - const settings = await this.database.automod.get(msg.guild.id); - const t = !settings!.raid; - const result = await this.database.automod.update(msg.guild.id, { - raid: !settings!.raid, - }); - - return msg.reply( - `${t ? `${msg.successEmote} **Enabled**` : `${msg.errorEmote} **Disabled**`} Raid automod feature.` - ); - } - - const automod = await this.database.automod.get(msg.guild.id); - const found = await Promise.all(channels.map((chanID) => this.discord.getChannel(chanID))); - const chans = found.filter((c) => c !== null && c.type !== 0).map((s) => s!.id); - const result = await this.database.automod.update(msg.guild.id, { - whitelistChannelsDuringRaid: automod!.whitelistChannelsDuringRaid.concat(chans), - }); - - const message = - result.affected === 1 - ? `${msg.successEmote} Added **${chans.length}** channels to the whitelist.` - : `${msg.errorEmote} Unable to add **${chans.length}** channels to the whitelist.`; - - return msg.reply(message); - } -} diff --git a/src/commands/settings/Logging.ts b/src/commands/settings/Logging.ts deleted file mode 100644 index 2ab77747..00000000 --- a/src/commands/settings/Logging.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder, Subcommand } from '../../structures'; -import type { AnyGuildChannel, TextChannel, User } from 'eris'; -import { LoggingEvents } from '../../entities/LoggingEntity'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -const LOGGING_EVENTS = Object.values(LoggingEvents).map((event) => - event.replace('channel_', '').replace('left', 'leave').replace(/_/g, '.') -); -const humanizedEvents = { - [LoggingEvents.VoiceChannelSwitch]: 'Voice Channel Switch', - [LoggingEvents.VoiceChannelLeft]: 'Voice Channel Leave', - [LoggingEvents.VoiceChannelJoin]: 'Voice Channel Join', - [LoggingEvents.MessageUpdated]: 'Message Updated', - [LoggingEvents.MessageDeleted]: 'Message Deleted', -}; - -export default class ModLogCommand extends Command { - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - constructor() { - super({ - userPermissions: ['manageGuild'], - description: 'descriptions.logging', - category: Categories.Settings, - examples: [ - 'logging | Enable / Disable the feature', - 'logging list | List the current configuration', - 'logging reset | Reset the mod log channel', - 'logging events | Enable all logging events', - 'logging event message.update | Enable / Disable a specific event', - 'logging 236987412589632587 | Specify a logs channel', - 'logging ignore 5246968653214587563 | Ignores a channel from displaying logs', - 'logging ignore 1532589645236985346 | Ignores a specific user from being displayed in logs', - ], - aliases: ['logs'], - usage: '[channelID]', - name: 'logging', - }); - } - - async run(msg: CommandMessage, [channel]: [string]) { - if (!channel) { - const settings = await this.database.logging.get(msg.guild.id); - const type = !settings.enabled; - - await this.database.logging.update(msg.guild.id, { - enabled: type, - }); - - return msg.reply( - `${type ? msg.successEmote : msg.errorEmote} Successfully **${ - type ? 'enabled' : 'disabled' - }** the Logging feature.` - ); - } - - const chan = await this.discord.getChannel(channel, msg.guild); - const settings = await this.database.logging.get(msg.guild.id); - - if (chan === null) return msg.reply(`Channel "${channel}" doesn't exist.`); - - if (chan.type !== 0) return msg.reply(`Channel #${chan.name} was not a text channel`); - - const perms = chan.permissionsOf(this.discord.client.user.id); - if (!perms.has('sendMessages') || !perms.has('readMessages') || !perms.has('embedLinks')) - return msg.reply( - `I am missing the following permissions: **Send Messages**, **Read Messages**, and **Embed Links** in #${chan.name}.` - ); - - let updateEnabled = false; - // Enable on channel input (so it runs properly) - if (!settings.enabled) { - await this.database.logging.update(msg.guild.id, { enabled: true }); - updateEnabled = true; - } - - await this.database.logging.update(msg.guild.id, { - channelID: chan.id, - }); - - return msg.reply( - `Logs will be shown in #${chan.name}!${ - updateEnabled ? "\n:eyes: I saw it wasn't enabled. So, I enabled it myself." : '' - }` - ); - } - - @Subcommand() - async list(msg: CommandMessage) { - const settings = await this.database.logging.get(msg.guild.id); - - const embed = EmbedBuilder.create().setDescription([ - `• **Channels Ignored**: ${settings.ignoreChannels.length}`, - `• **Users Ignored**: ${settings.ignoreUsers.length}`, - `• **Channel**: ${settings.channelID !== null ? `<#${settings!.channelID}>` : 'None'}`, - `• **Enabled**: ${settings.enabled ? msg.successEmote : msg.errorEmote}`, - `• **Events**: ${settings.events.map((ev) => humanizedEvents[ev]).join(', ') || 'None'}`, - ]); - - return msg.reply(embed); - } - - @Subcommand() - async reset(msg: CommandMessage) { - const settings = await this.database.logging.get(msg.guild.id); - if (!settings.channelID) return msg.reply('No mod logs channel has been set.'); - - await this.database.logging.update(msg.guild.id, { - channelID: undefined, - }); - - return msg.reply('Resetted the mod log successfully.'); - } - - @Subcommand('', ['events']) - async event(msg: CommandMessage, [event]: string) { - const settings = await this.database.logging.get(msg.guild.id); - - if (!event) - return msg.reply( - `No event was listed, here is the list:\n\n\`\`\`apache\n${LOGGING_EVENTS.map( - (event) => `${event} | ${msg.settings.prefixes[0]}logging event ${event}` - ).join('\n')}\`\`\`` - ); - - if (event === '*') { - const events = Object.values(LoggingEvents); - settings.events = events; - await this.database.logging['repository'].save(settings); - - return msg.reply( - `:thumbsup: **Enabled** all logging events, to disable all of them, do \`${msg.settings.prefixes[0]}logging events -*\`.` - ); - } - - if (event === '-*') { - settings.events = []; - await this.database.logging['repository'].save(settings); - - return msg.reply(':thumbsup: **Disabled** all logging events.'); - } - - if (!LOGGING_EVENTS.includes(event)) - return msg.reply( - `Invalid event **${event}**, here is the list:\n\n\`\`\`apache\n${LOGGING_EVENTS.map( - (event) => `${event} | ${msg.settings.prefixes[0]}logging event ${event}` - ).join('\n')}\`\`\`` - ); - - const keyedEvent = Object.values(LoggingEvents).find( - (val) => val === event.replace('voice.', 'voice_channel_').replace('leave', 'left').replace('.', '_') - )!; - const disabled = settings.events.includes(keyedEvent); - - settings.events = !disabled ? [...settings.events, keyedEvent] : settings.events.filter((r) => r !== keyedEvent); - await this.database.logging['repository'].save(settings); - - return msg.reply(`:thumbsup: **${!disabled ? 'Enabled' : 'Disabled'}** logging event \`${keyedEvent}\`.`); - } - - @Subcommand('') - async ignore(msg: CommandMessage, [chanOrUserId]: [string]) { - if (!chanOrUserId) return msg.reply('Missing a channel/user ID or mention'); - - const settings = await this.database.logging.get(msg.guild.id); - const channel = await this.discord.getChannel(chanOrUserId); - let user: User | null = null; - - try { - user = await this.discord.getUser(chanOrUserId); - } catch { - // ignore - } - - if (channel !== null) { - if (![0, 5].includes(channel.type)) - return msg.reply(`Channel with ID ${channel.id} was not a Text or News channel`); - - const enabled = !settings.ignoreChannels.includes(channel.id); - settings.ignoreChannels = !settings.ignoreChannels.includes(channel.id) - ? [...settings.ignoreChannels, channel.id] - : settings.ignoreChannels.filter((chanID) => chanID !== channel.id); - await this.database.logging['repository'].save(settings); - - return msg.reply( - `:thumbsup: ${enabled ? 'Added' : 'Deleted'} entry for channel **#${channel.name}** to be excluded in logging.` - ); - } - - if (user !== null) { - const enabled = !settings.ignoreUsers.includes(user.id); - settings.ignoreUsers = !settings.ignoreUsers.includes(user.id) - ? [...settings.ignoreUsers, user.id] - : settings.ignoreUsers.filter((userID) => userID !== user!.id); - await this.database.logging['repository'].save(settings); - - return msg.reply( - `:thumbsup: ${enabled ? 'Added' : 'Deleted'} entry for user **${user.username}#${ - user.discriminator - }** to be excluded in logging.` - ); - } - - return msg.reply(`Channel or user \`${chanOrUserId}\` was not found.`); - } -} diff --git a/src/commands/settings/ModLog.ts b/src/commands/settings/ModLog.ts deleted file mode 100644 index dad4dcc9..00000000 --- a/src/commands/settings/ModLog.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, Subcommand } from '../../structures'; -import type { TextChannel } from 'eris'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -export default class ModLogCommand extends Command { - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: ['manageGuild'], - description: 'descriptions.modlog', - category: Categories.Settings, - examples: ['modlog', 'modlog reset', 'modlog <#236987412589632587>'], - aliases: ['set-modlog'], - usage: '[channel]', - name: 'modlog', - }); - } - - async run(msg: CommandMessage, [channel]: [string]) { - if (!channel) { - const chan = - msg.settings.modlogChannelID !== null - ? await this.discord.getChannel(msg.settings.modlogChannelID!, msg.guild) - : null; - return msg.reply(chan === null ? 'No mod-log has been set.' : `Mod Logs are set in **#${chan.name}**`); - } - - const chan = await this.discord.getChannel(channel, msg.guild); - if (chan === null) return msg.reply(`Channel "${channel}" doesn't exist.`); - - if (chan.type !== 0) return msg.reply(`Channel #${chan.name} was not a text channel`); - - const perms = chan.permissionsOf(this.discord.client.user.id); - if (!perms.has('sendMessages') || !perms.has('readMessages') || !perms.has('embedLinks')) - return msg.reply( - `I am missing the following permissions: **Send Messages**, **Read Messages**, and **Embed Links** in #${chan.name}.` - ); - - await this.database.guilds.update(msg.guild.id, { - modlogChannelID: chan.id, - }); - - return msg.reply(`Mod Logs are now set in #${chan.name}!`); - } - - @Subcommand() - async reset(msg: CommandMessage) { - if (!msg.settings.modlogChannelID) return msg.reply('No mod logs channel has been set.'); - - await this.database.guilds.update(msg.guild.id, { - modlogChannelID: undefined, - }); - - return msg.reply('Resetted the mod log successfully.'); - } -} diff --git a/src/commands/settings/MutedRole.ts b/src/commands/settings/MutedRole.ts deleted file mode 100644 index f892ce23..00000000 --- a/src/commands/settings/MutedRole.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, Subcommand, CommandMessage } from '../../structures'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Discord from '../../components/Discord'; - -export default class MutedRoleCommand extends Command { - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - constructor() { - super({ - userPermissions: 'manageGuild', - description: 'descriptions.muted_role', - category: Categories.Settings, - examples: [ - 'muterole | View the current Muted role in this server', - 'muterole reset | Resets the Muted role in this server', - 'muterole 3621587485965325 | Sets the current mute role', - ], - aliases: ['mutedrole', 'mute-role'], - name: 'muterole', - }); - } - - async run(msg: CommandMessage, [roleID]: [string]) { - if (!roleID) - return msg.settings.mutedRoleID !== null - ? msg.reply(`The muted role in this guild is <@&${msg.settings.mutedRoleID}>`) - : msg.reply('No muted role is set in this guild.'); - - const role = await this.discord.getRole(roleID, msg.guild); - if (role === null) return msg.reply(`\`${roleID}\` was not a role.`); - - await this.database.guilds.update(msg.guild.id, { mutedRoleID: role.id }); - return msg.reply(`The Muted role is now set to **${role.name}**`); - } - - @Subcommand() - async reset(msg: CommandMessage) { - await this.database.guilds.update(msg.guild.id, { mutedRoleID: undefined }); - return msg.reply(':thumbsup: Muted role has been reset.'); - } -} diff --git a/src/commands/settings/Prefix.ts b/src/commands/settings/Prefix.ts deleted file mode 100644 index ebb8d753..00000000 --- a/src/commands/settings/Prefix.ts +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, Subcommand, CommandMessage, EmbedBuilder } from '../../structures'; -import { Categories } from '../../util/Constants'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import Config from '../../components/Config'; - -interface Flags { - user?: string | true; - u?: string | true; -} - -export default class PrefixCommand extends Command { - @Inject - private database!: Database; - - @Inject - private config!: Config; - - constructor() { - super({ - description: 'descriptions.prefix', - category: Categories.Settings, - examples: [ - 'prefix', - 'prefix set !', - 'prefix set ! --user', - 'prefix remove !', - 'prefix remove ! --user', - 'prefix remove 1', - 'prefix remove 1 --user', - ], - aliases: ['prefixes'], - usage: '[prefix] [--user]', - name: 'prefix', - }); - } - - async run(msg: CommandMessage) { - const flags = msg.flags(); - const entity = flags.user === true || flags.u === true ? msg.userSettings : msg.settings; - const defaultPrefixes = this.config.getProperty('prefixes') ?? []; - - const embed = EmbedBuilder.create().setDescription([ - `> **List of ${flags.user === true || flags.u === true ? 'user' : 'guild'} prefixes available**:`, - '', - '```apache', - entity.prefixes.map((prefix, index) => `- ${index}. : ${prefix}`).join('\n') || - `None (use ${msg.settings.prefixes[0]}prefix set -u to set one!)`, - '```', - ]); - - if (defaultPrefixes.length > 0) - embed.setFooter(`Prefixes ${defaultPrefixes.join(', ')} will always work no matter what.`); - - return msg.reply(embed); - } - - @Subcommand(' [--user | -u]') - async set(msg: CommandMessage, [...prefix]: [...string[]]) { - if (!prefix) - return msg.reply('Missing a prefix to set! You can use `"` to make spaced ones, example: `"nino "` -> `nino `.'); - - const pre = prefix.join(' ').replaceAll(/['"]/g, ''); - if (pre.length > 25) - return msg.reply( - `Prefix \`${pre}\` is over the limit of 25 characters, you went over ${ - pre.length - 25 - } characters (excluding \`"\`).` - ); - - const flags = msg.flags(); - const isUser = flags.user === true || flags.u === true; - const controller = isUser ? this.database.users : this.database.guilds; - const data = await controller.get(isUser ? msg.author.id : msg.guild.id); - const owners = this.config.getProperty('owners') ?? []; - - if (!isUser && (!msg.member.permissions.has('manageGuild') || !owners.includes(msg.author.id))) - return msg.reply('Missing the **Manage Guild** permission.'); - - if (data.prefixes.length > 5) - return msg.reply(`${isUser ? 'You' : 'The guild'} has exceeded the amount of prefixes.`); - - const index = data.prefixes.findIndex((prefix) => prefix.toLowerCase() === pre.toLowerCase()); - if (index !== -1) - return msg.reply(`Prefix \`${pre}\` already exists as a ${isUser ? 'your' : "the guild's"} prefix.`); - - data.prefixes.push(pre); - - // @ts-ignore Wow, our first ts-ignore in this project! (ts2349) - await controller.repository.save(data); - return msg.reply(`Prefix \`${pre}\` is now available`); - } - - @Subcommand(' [--user | -u]') - async reset(msg: CommandMessage, [...prefix]: [...string[]]) { - if (!prefix) - return msg.reply('Missing a prefix to set! You can use `"` to make spaced ones, example: `"nino "` -> `nino `.'); - - const pre = prefix.join(' ').replaceAll(/['"]/g, ''); - if (pre.length > 25) - return msg.reply( - `Prefix \`${pre}\` is over the limit of 25 characters, you went over ${ - pre.length - 25 - } characters (excluding \`"\`).` - ); - - const flags = msg.flags(); - const isUser = flags.user === true || flags.u === true; - const controller = isUser ? this.database.users : this.database.guilds; - const data = await controller.get(isUser ? msg.author.id : msg.guild.id); - const owners = this.config.getProperty('owners') ?? []; - - if (!isUser && (!msg.member.permissions.has('manageGuild') || !owners.includes(msg.author.id))) - return msg.reply('Missing the **Manage Guild** permission.'); - - const index = data.prefixes.findIndex((prefix) => prefix.toLowerCase() === pre.toLowerCase()); - if (index === -1) return msg.reply('Prefix was not found'); - - data.prefixes.splice(index, 1); - - // @ts-ignore Check out the issue ID -> (ts2349) - await controller.repository.save(data); - return msg.reply( - `Prefix with index **${index}** (\`${prefix}\`) has been removed, ${isUser ? 'you' : 'the guild'} have ${ - data.prefixes.length - } prefix${data.prefixes.length === 1 ? 'es' : ''} left.` - ); - } -} diff --git a/src/commands/settings/Punishments.ts b/src/commands/settings/Punishments.ts deleted file mode 100644 index c07a99d5..00000000 --- a/src/commands/settings/Punishments.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder, Subcommand } from '../../structures'; -import { PunishmentType } from '../../entities/PunishmentsEntity'; -import { Categories } from '../../util/Constants'; -import { firstUpper } from '@augu/utils'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import ms = require('ms'); - -// Punishments shouldn't be chained with warnings and voice-related shit -const TYPES = Object.values(PunishmentType).filter((w) => !w.startsWith('warning.') && !w.startsWith('voice.')); - -interface Flags { - soft?: string | true; - days?: string | true; - d?: string | true; - s?: string | true; -} - -export default class PunishmentsCommand extends Command { - @Inject - private database!: Database; - - constructor() { - super({ - userPermissions: 'manageGuild', - description: 'descriptions.punishments', - category: Categories.Settings, - examples: [ - 'punishments | List all the punishments in this guild', - 'punishments 3 mute | Add a punishment of 3 warnings to be a mute', - 'punishments 5 ban / 1d | Adds a punishment for a ban for a day', - 'punishments remove 3 | Remove a punishment by the index', - ], - aliases: ['punish'], - name: 'punishments', - }); - } - - async run(msg: CommandMessage, [index, type, time]: [string, string, string?]) { - const punishments = await this.database.punishments.getAll(msg.guild.id); - - if (!index) { - if (!punishments.length) return msg.reply('There are no punishments setup in this guild.'); - - const embed = EmbedBuilder.create() - .setTitle(`:pencil2: ~ Punishments for ${msg.guild.name}`) - .addFields( - punishments.map((punishment) => ({ - name: `❯ Punishment #${punishment.index}`, - value: [ - `• **Warnings**: ${punishment.warnings}`, - `• **Soft**: ${punishment.soft ? 'Yes' : 'No'}`, - `• **Time**: ${punishment.time !== null ? ms(punishment.time!) : 'No time duration'}`, - `• **Type**: ${firstUpper(punishment.type)}`, - ].join('\n'), - inline: true, - })) - ); - - return msg.reply(embed); - } - - if (punishments.length > 10) return msg.reply("Yea, I think you're fine with 10 punishments..."); - - if (isNaN(Number(index))) return msg.reply('The amount of warnings you specified was not a number'); - - if (Number(index) === 0) - return msg.reply("You need to specify an amount of warnings, `0` isn't gonna cut it you know."); - - if (Number(index) > 10) return msg.reply('Uh-oh! The guild has reached the maximum amount of 10 warnings, sorry.'); - - if (type === undefined || !TYPES.includes(type as any)) - return msg.reply( - `You haven't specified a punishment type or the one you provided is not a valid one.\n\n\`\`\`apache\n${TYPES.map( - (type) => `- ${type}` - ).join('\n')}\`\`\`` - ); - - const flags = msg.flags(); - const soft = flags.soft === true || flags.s === true; - const days = flags.days !== undefined ? flags.days : flags.d !== undefined ? flags.d : undefined; - let timeStamp: number | undefined = undefined; - - try { - if (time !== undefined) timeStamp = ms(time); - } catch { - // uwu - } - - if (type !== PunishmentType.Ban && soft === true) - return msg.reply(`The \`--soft\` argument only works on bans only, you specified \`${type}\`.`); - - const entry = await this.database.punishments.create({ - warnings: Number(index), - guildID: msg.guild.id, - time: timeStamp, - soft, - days: days ? Number(days) : undefined, - type: type as PunishmentType, - }); - - return msg.reply(`Punishment #**${entry.index}** has been created`); - } - - @Subcommand('') - async remove(msg: CommandMessage, [index]: [string]) { - if (!index) - return msg.reply( - `Missing an amount of warnings to be removed. Run \`${msg.settings.prefixes[0]}punishments\` to see which one you want removed.` - ); - - if (isNaN(Number(index))) return msg.reply(`\`${index}\` was not a number`); - - const punishment = await this.database.punishments.get(msg.guild.id, Number(index)); - if (punishment === undefined) - return msg.reply( - `Punishment #**${index}** warnings was not found. Run \`${msg.settings.prefixes[0]}punishments\` to see which one you want removed.` - ); - - await this.database.punishments['repository'].delete({ - guildID: msg.guild.id, - index: Number(index), - }); - return msg.reply(`:thumbsup: Punishment #**${punishment.index}** has been removed.`); - } -} diff --git a/src/commands/settings/Settings.ts b/src/commands/settings/Settings.ts deleted file mode 100644 index ae7b5108..00000000 --- a/src/commands/settings/Settings.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Command, CommandMessage, EmbedBuilder } from '../../structures'; -import { LoggingEvents } from '../../entities/LoggingEntity'; -import { Inject } from '@augu/lilith'; -import Database from '../../components/Database'; -import { Categories } from '../../util/Constants'; - -const humanizedEvents = { - [LoggingEvents.VoiceChannelSwitch]: 'Voice Channel Switch', - [LoggingEvents.VoiceChannelLeft]: 'Voice Channel Leave', - [LoggingEvents.VoiceChannelJoin]: 'Voice Channel Join', - [LoggingEvents.MessageUpdated]: 'Message Updated', - [LoggingEvents.MessageDeleted]: 'Message Deleted', -}; - -export default class SettingsCommand extends Command { - @Inject - private readonly database!: Database; - - constructor() { - super({ - userPermissions: ['manageGuild'], - description: 'descriptions.settings', - category: Categories.Settings, - aliases: ['config', 'conf'], - name: 'settings', - }); - } - - async run(msg: CommandMessage) { - // Bulk get all guild settings - const [settings, automod, logging] = await Promise.all([ - this.database.guilds.get(msg.guild.id), - this.database.automod.get(msg.guild.id), - this.database.logging.get(msg.guild.id), - ]); - - const embed = EmbedBuilder.create() - .setTitle(`[ :pencil2: Settings for ${msg.guild.name} (${msg.guild.id}) ]`) - .addFields([ - { - name: '❯ Settings', - value: [ - `• **Muted Role**: ${ - settings.mutedRoleID !== null ? `<@&${settings.mutedRoleID}> (**${settings.mutedRoleID}**)` : 'None' - }`, - `• **Mod Log**: ${ - settings.modlogChannelID !== null - ? `<#${settings.modlogChannelID}> (**${settings.modlogChannelID}**)` - : 'None' - }`, - ].join('\n'), - inline: true, - }, - { - name: '❯ Automod', - value: [ - `• ${automod!.shortLinks ? msg.successEmote : msg.errorEmote} **Short Links**`, - `• ${automod!.blacklist ? msg.successEmote : msg.errorEmote} **Blacklist Words**`, - `• ${automod!.mentions ? msg.successEmote : msg.errorEmote} **Mentions**`, - `• ${automod!.dehoist ? msg.successEmote : msg.errorEmote} **Dehoisting**`, - `• ${automod!.invites ? msg.successEmote : msg.errorEmote} **Invites**`, - `• ${automod!.raid ? msg.successEmote : msg.errorEmote} **Raid**`, - `• ${automod!.spam ? msg.successEmote : msg.errorEmote} **Spam**`, - ].join('\n'), - inline: true, - }, - { - name: '❯ Logging', - value: [ - `• **Channels Ignored**: ${logging.ignoreChannels.length}`, - `• **Users Ignored**: ${logging.ignoreUsers.length}`, - `• **Channel**: ${ - logging.channelID !== null ? `<#${logging.channelID}> (**${logging.channelID}**)` : 'None' - }`, - `• **Enabled**: ${logging.enabled ? msg.successEmote : msg.errorEmote}`, - `• **Events**: ${logging.events.map((ev) => humanizedEvents[ev]).join(', ') || 'None'}`, - ].join('\n'), - inline: true, - }, - ]); - - return msg.reply(embed); - } -} diff --git a/src/components/Config.ts b/src/components/Config.ts deleted file mode 100644 index 2595af7d..00000000 --- a/src/components/Config.ts +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import { writeFileSync, existsSync } from 'fs'; -import { Component, Inject } from '@augu/lilith'; -import { readFile } from 'fs/promises'; -import { Logger } from 'tslog'; -import { join } from 'path'; -import yaml from 'js-yaml'; - -const NOT_FOUND_SYMBOL = Symbol.for('$nino::config::not.found'); - -interface Configuration { - runPendingMigrations?: boolean; - prometheusPort?: number; - defaultLocale?: string; - environment: 'development' | 'production'; - sentryDsn?: string; - botlists?: BotlistConfig; - timeouts: TimeoutsConfig; - database: DatabaseConfig; - prefixes: string[]; - status: StatusConfig; - owners: string[]; - ksoft?: string; - redis: RedisConfig; - token: string; - api?: boolean; -} - -interface BotlistConfig { - dservices?: string; - dboats?: string; - topgg?: string; - delly?: string; - dbots?: string; - bfd?: string; -} - -interface DatabaseConfig { - username: string; - password: string; - database: string; - host: string; - port: number; - url?: string; -} - -interface RedisConfig { - sentinels?: RedisSentinelConfig[]; - password?: string; - master?: string; - index?: number; - host: string; - port: number; -} - -interface TimeoutsConfig { - host?: string; - auth: string; - port: number; -} - -interface StatusConfig { - presence?: 'online' | 'idle' | 'dnd' | 'offline'; - status: string; - type: 0 | 1 | 2 | 3 | 5; -} - -// eslint-disable-next-line -interface RedisSentinelConfig extends Pick {} - -@Component({ - priority: 0, - name: 'config', -}) -export default class Config { - private config!: Configuration; - - @Inject - private readonly logger!: Logger; - - async load() { - if (!existsSync(join(__dirname, '..', '..', 'config.yml'))) { - const config = yaml.dump( - { - runPendingMigrations: true, - defaultLocale: 'en_US', - environment: 'production', - prefixes: ['!'], - owners: [], - token: '-- replace me --', - }, - { - indent: 2, - noArrayIndent: false, - } - ); - - writeFileSync(join(__dirname, '..', '..', 'config.yml'), config); - return Promise.reject( - new SyntaxError( - "Weird, you didn't have a configuration file... So, I may have provided you a default one, if you don't mind... >W<" - ) - ); - } - - this.logger.info('Loading configuration...'); - const contents = await readFile(join(__dirname, '..', '..', 'config.yml'), 'utf8'); - const config = yaml.load(contents) as unknown as Configuration; - - this.config = { - runPendingMigrations: config.runPendingMigrations ?? false, - prometheusPort: config.prometheusPort, - defaultLocale: config.defaultLocale ?? 'en_US', - environment: config.environment ?? 'production', - sentryDsn: config.sentryDsn, - botlists: config.botlists, - database: config.database, - timeouts: config.timeouts, - prefixes: config.prefixes, - owners: config.owners, - status: config.status ?? { - type: 0, - status: '$prefix$help | $guilds$ Guilds', - presence: 'online', - }, - ksoft: config.ksoft, - redis: config.redis, - token: config.token, - api: config.api, - }; - - if (this.config.token === '-- replace me --') - return Promise.reject(new TypeError('Restore `token` in config with your discord bot token.')); - - // resolve the promise - return Promise.resolve(); - } - - getProperty>(key: K): KeyToPropType | undefined { - const nodes = key.split('.'); - let value: any = this.config; - - for (const frag of nodes) { - try { - value = value[frag]; - } catch { - value = NOT_FOUND_SYMBOL; - } - } - - return value === NOT_FOUND_SYMBOL ? undefined : value; - } -} diff --git a/src/components/Database.ts b/src/components/Database.ts deleted file mode 100644 index f5a3ee4b..00000000 --- a/src/components/Database.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { createConnection, Connection, ConnectionOptions } from 'typeorm'; -import { Component, ComponentAPI, Inject } from '@augu/lilith'; -import { humanize, Stopwatch } from '@augu/utils'; -import { Logger } from 'tslog'; -import Config from './Config'; - -// Controllers -import GuildSettingsController from '../controllers/GuildSettingsController'; -import UserSettingsController from '../controllers/UserSettingsController'; -import PunishmentsController from '../controllers/PunishmentsController'; -import BlacklistController from '../controllers/BlacklistController'; -import WarningsController from '../controllers/WarningsController'; -import LoggingController from '../controllers/LoggingController'; -import AutomodController from '../controllers/AutomodController'; -import CasesController from '../controllers/CasesController'; - -// Import entities -import PunishmentEntity from '../entities/PunishmentsEntity'; -import BlacklistEntity from '../entities/BlacklistEntity'; -import LoggingEntity from '../entities/LoggingEntity'; -import WarningEntity from '../entities/WarningsEntity'; -import AutomodEntity from '../entities/AutomodEntity'; -import GuildEntity from '../entities/GuildEntity'; -import CaseEntity from '../entities/CaseEntity'; -import UserEntity from '../entities/UserEntity'; - -@Component({ - priority: 1, - name: 'database', -}) -export default class Database { - public punishments!: PunishmentsController; - public blacklists!: BlacklistController; - public connection!: Connection; - public warnings!: WarningsController; - public logging!: LoggingController; - public automod!: AutomodController; - public guilds!: GuildSettingsController; - public cases!: CasesController; - public users!: UserSettingsController; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - private api!: ComponentAPI; - - async load() { - this.logger.info('Now connecting to the database...'); - - const url = this.config.getProperty('database.url'); - const entities = [ - // readability mmmmm - PunishmentEntity, - BlacklistEntity, - LoggingEntity, - WarningEntity, - AutomodEntity, - GuildEntity, - CaseEntity, - UserEntity, - ]; - - const config: ConnectionOptions = - url !== undefined - ? { - migrations: ['./migrations/*.ts'], - entities, - type: 'postgres', - name: 'Nino', - url, - } - : { - migrations: ['./migrations/*.ts'], - username: this.config.getProperty('database.username'), - password: this.config.getProperty('database.password'), - database: this.config.getProperty('database.database'), - entities, - host: this.config.getProperty('database.host'), - port: this.config.getProperty('database.port'), - type: 'postgres', - name: 'Nino', - }; - - this.connection = await createConnection(config); - this.initRepos(); - - const migrations = await this.connection.showMigrations(); - const shouldRun = this.config.getProperty('runPendingMigrations'); - if (migrations && (shouldRun === undefined || shouldRun === false)) { - this.logger.info( - 'There are pending migrations to be ran, but you have `runPendingMigrations` disabled! Run `npm run migrations` to migrate the database or set `runPendingMigrations` = true to run them at runtime.' - ); - } else if (migrations && shouldRun === true) { - this.logger.info('Found pending migrations and `runPendingMigrations` is enabled, now running...'); - - try { - const ran = await this.connection.runMigrations({ transaction: 'all' }); - this.logger.info(`Ran ${ran.length} migrations! You're all to go.`); - } catch (ex) { - this.logger.fatal(ex); - - if ((ex as any).message.indexOf('already exists') !== -1) { - this.logger.warn('Seems like relations or indexes existed!'); - return Promise.resolve(); - } - - return Promise.reject(ex); - } - } else { - this.logger.info('No migrations needs to be ran and the connection to the database is healthy.'); - return Promise.resolve(); - } - - this.logger.info('All migrations has been migrated and the connection has been established correctly!'); - return Promise.resolve(); - } - - dispose() { - return this.connection.close(); - } - - async getStatistics() { - const stopwatch = new Stopwatch(); - stopwatch.start(); - await this.connection.query('SELECT * FROM guilds'); - const ping = stopwatch.end(); - - let dbName: string = 'nino'; - const url = this.config.getProperty('database.url'); - if (url !== undefined) { - const parts = url.split('/'); - dbName = parts[parts.length - 1]; - } else { - dbName = this.config.getProperty('database.database') ?? 'nino'; - } - - // collect shit - const data = await Promise.all([ - this.connection.query( - `SELECT tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted FROM pg_stat_database WHERE datname = '${dbName}';` - ), - this.connection.query('SELECT version();'), - this.connection.query('SELECT extract(epoch FROM current_timestamp - pg_postmaster_start_time()) AS uptime;'), - ]); - - return { - inserted: Number(data[0]?.[0]?.tup_inserted ?? 0), - updated: Number(data[0]?.[0]?.tup_updated ?? 0), - deleted: Number(data[0]?.[0]?.tup_deleted ?? 0), - fetched: Number(data[0]?.[0]?.tup_fetched ?? 0), - version: data[1][0].version.split(', ').shift().replace('PostgreSQL ', '').trim(), - uptime: humanize(Math.floor(data[2][0].uptime * 1000), true), - ping, - }; - } - - private initRepos() { - this.punishments = new PunishmentsController(this); - this.blacklists = new BlacklistController(this); - this.warnings = new WarningsController(this); - this.logging = new LoggingController(this); - this.automod = new AutomodController(this); - this.guilds = new GuildSettingsController(this); - this.cases = new CasesController(this); - this.users = new UserSettingsController(this); - - for (const controller of [ - this.punishments, - this.blacklists, - this.warnings, - this.logging, - this.automod, - this.guilds, - this.cases, - this.users, - ]) { - this.api.container.addInjections(controller); - } - } -} diff --git a/src/components/Discord.ts b/src/components/Discord.ts deleted file mode 100644 index a137fdaa..00000000 --- a/src/components/Discord.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { USER_MENTION_REGEX, USERNAME_DISCRIM_REGEX, ID_REGEX, CHANNEL_REGEX, ROLE_REGEX } from '../util/Constants'; - -import { Component, Inject, ComponentAPI, Subscribe } from '@augu/lilith'; -import { Client, Role, Guild, AnyChannel } from 'eris'; -import { Logger } from 'tslog'; -import Config from './Config'; - -@Component({ - priority: 0, - name: 'discord', -}) -export default class Discord { - public mentionRegex?: RegExp; - public client!: Client; - - api!: ComponentAPI; - - @Inject - private readonly config!: Config; - - @Inject - private readonly logger!: Logger; - - load() { - if (this.client !== undefined) { - this.logger.warn('A client has already been created.'); - return; - } - - const token = this.config.getProperty('token'); - if (token === undefined) { - this.logger.fatal("Property `token` doesn't exist in the config file, please populate it."); - return; - } - - this.logger.info('Booting up the bot...'); - this.client = new Client(token, { - getAllUsers: true, - maxShards: 'auto', - restMode: true, - intents: ['guilds', 'guildBans', 'guildMembers', 'guildMessages', 'guildVoiceStates'], - }); - - this.api.container.addEmitter('discord', this.client); - return this.client.connect(); - } - - dispose() { - return this.client.disconnect({ reconnect: false }); - } - - get emojis() { - return this.client.guilds - .map((guild) => guild.emojis.map((emoji) => `<${emoji.animated ? 'a:' : ':'}${emoji.name}:${emoji.id}>`)) - .flat(); - } - - async getUser(query: string) { - if (USER_MENTION_REGEX.test(query)) { - const match = USER_MENTION_REGEX.exec(query); - if (match === null) return null; - - const user = this.client.users.get(match[1]); - if (user !== undefined) { - return user; - } else { - return this.client.getRESTUser(match[1]).catch(() => null); - } - } - - if (USERNAME_DISCRIM_REGEX.test(query)) { - const match = query.match(USERNAME_DISCRIM_REGEX)!; - const users = this.client.users.filter( - (user) => user.username === match[1] && Number(user.discriminator) === Number(match[2]) - ); - - // TODO: pagination? - if (users.length > 0) return users[0]; - } - - if (ID_REGEX.test(query)) { - const user = this.client.users.get(query); - if (user !== undefined) return user; - else return this.client.getRESTUser(query); - } - - return null; - } - - getChannel(query: string, guild?: Guild) { - return new Promise((resolve) => { - if (CHANNEL_REGEX.test(query)) { - const match = CHANNEL_REGEX.exec(query); - if (match === null) return resolve(null); - - if (guild) { - return resolve(guild.channels.has(match[1]) ? (guild.channels.get(match[1])! as T) : null); - } else { - const channel = - match[1] in this.client.channelGuildMap && - this.client.guilds.get(this.client.channelGuildMap[match[1]])?.channels.get(match[1]); - return resolve((channel as T) || null); - } - } - - if (ID_REGEX.test(query)) { - if (guild) { - return resolve(guild.channels.has(query) ? (guild.channels.get(query)! as T) : null); - } else { - const channel = - query in this.client.channelGuildMap && - this.client.guilds.get(this.client.channelGuildMap[query])?.channels.get(query); - return resolve((channel as T) || null); - } - } - - if (guild !== undefined) { - const channels = guild.channels.filter((chan) => chan.name.toLowerCase().includes(query.toLowerCase())); - if (channels.length > 0) { - return resolve(channels[0] as T); - } - } - - resolve(null); - }); - } - - getRole(query: string, guild: Guild) { - return new Promise((resolve) => { - if (ROLE_REGEX.test(query)) { - const match = ROLE_REGEX.exec(query)!; - if (match === null) return resolve(null); - - const role = guild.roles.get(match[1]); - - if (role !== undefined) return resolve(role); - } - - if (ID_REGEX.test(query)) return resolve(guild.roles.has(query) ? guild.roles.get(query)! : null); - - const roles = guild.roles.filter((role) => role.name.toLowerCase() === query.toLowerCase()); - - // TODO: pagination? - resolve(roles.length > 0 ? roles[0] : null); - }); - } - - @Subscribe('shardReady', { emitter: 'discord' }) - private onShardReady(id: number) { - this.logger.info(`Shard #${id} is now ready.`); - } - - @Subscribe('shardDisconnect', { emitter: 'discord' }) - private onShardDisconnect(error: any, id: number) { - this.logger.fatal( - `Shard #${id} has disconnected from the universe\n`, - error || 'Connection has been reset by peer.' - ); - } - - @Subscribe('shardResume', { emitter: 'discord' }) - private onShardResume(id: number) { - this.logger.info(`Shard #${id} has resumed it's connection!`); - } -} diff --git a/src/components/Prometheus.ts b/src/components/Prometheus.ts deleted file mode 100644 index a054aae0..00000000 --- a/src/components/Prometheus.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { createServer, IncomingMessage, ServerResponse } from 'http'; -import { Component, Inject } from '@augu/lilith'; -import { Logger } from 'tslog'; -import Config from './Config'; -import prom from 'prom-client'; - -@Component({ - priority: 3, - name: 'prometheus', -}) -export default class Prometheus { - public commandsExecuted!: prom.Counter; - public messagesSeen!: prom.Counter; - public rawWSEvents!: prom.Counter; - public guildCount!: prom.Gauge; - #server!: ReturnType; // yes - - @Inject - private logger!: Logger; - - @Inject - private config!: Config; - - load() { - const port = this.config.getProperty('prometheusPort'); - if (port === undefined) { - this.logger.warn( - 'Prometheus will not be available! This is not recommended for private instances unless you want analytics.' - ); - return Promise.resolve(); - } - - prom.collectDefaultMetrics(); - this.commandsExecuted = new prom.Counter({ - labelNames: ['command'], - name: 'nino_commands_executed', - help: 'How many commands Nino has executed successfully', - }); - - this.messagesSeen = new prom.Counter({ - name: 'nino_messages_seen', - help: 'How many messages Nino has seen throughout the process lifespan', - }); - - this.rawWSEvents = new prom.Counter({ - labelNames: ['event'], - name: 'nino_discord_websocket_events', - help: 'Received WebSocket events from Discord and what they were', - }); - - this.guildCount = new prom.Gauge({ - name: 'nino_guild_count', - help: 'Number of guilds Nino is in', - }); - - this.#server = createServer(this.onRequest.bind(this)); - this.#server.once('listening', () => this.logger.info(`Prometheus: Listening at http://localhost:${port}`)); - this.#server.on('error', (error) => this.logger.fatal(error)); - this.#server.listen(port); - } - - private async onRequest(req: IncomingMessage, res: ServerResponse) { - if (req.url! === '/metrics') { - res.writeHead(200, { 'Content-Type': prom.register.contentType }); - res.write(await prom.register.metrics()); - } else if (req.url! === '/favicon.ico') { - res.writeHead(404, { 'Content-Type': 'application/json' }); - res.write('{"fuck":"you uwu"}'); - } else { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.write('{"uwu":"owo"}'); - } - - res.end(); - } -} diff --git a/src/components/Redis.ts b/src/components/Redis.ts deleted file mode 100644 index 34ebf06e..00000000 --- a/src/components/Redis.ts +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Component, Inject } from '@augu/lilith'; -import type { Timeout } from './timeouts/types'; -import { Stopwatch } from '@augu/utils'; -import { Logger } from 'tslog'; -import IORedis from 'ioredis'; -import Config from './Config'; - -@Component({ - priority: 4, - name: 'redis', -}) -export default class Redis { - public client!: IORedis.Redis; - - @Inject - private logger!: Logger; - - @Inject - private config!: Config; - - async load() { - this.logger.info('Connecting to Redis...'); - - const sentinels = this.config.getProperty('redis.sentinels'); - const password = this.config.getProperty('redis.password'); - const masterName = this.config.getProperty('redis.master'); - const index = this.config.getProperty('redis.index'); - const host = this.config.getProperty('redis.host'); - const port = this.config.getProperty('redis.port'); - - const config = - (sentinels ?? []).length > 0 - ? { - enableReadyCheck: true, - connectionName: 'Nino', - lazyConnect: true, - sentinels, - password: password, - name: masterName, - db: index, - } - : { - enableReadyCheck: true, - connectionName: 'Nino', - lazyConnect: true, - password: password, - host: host, - port: port, - db: index, - }; - - this.client = new IORedis(config); - - await this.client.client('SETNAME', 'Nino'); - this.client.on('ready', () => this.logger.info('Connected to Redis!')); - - this.client.on('error', this.logger.error); - return this.client.connect().catch(() => {}); // eslint-disable-line - } - - dispose() { - return this.client.disconnect(); - } - - getTimeouts(guild: string) { - return this.client - .hget('nino:timeouts', guild) - .then((value) => (value !== null ? JSON.parse(value) : [])) - .catch(() => [] as Timeout[]); - } - - async getStatistics() { - const stopwatch = new Stopwatch(); - stopwatch.start(); - await this.client.ping('Ice is cute as FUCK'); - - const ping = stopwatch.end(); - - // stole this from donny - // Credit: https://github.com/FurryBotCo/FurryBot/blob/master/src/commands/information/stats-cmd.ts#L22 - const [stats, server] = await Promise.all([ - this.client.info('stats').then( - (info) => - info - .split(/\n\r?/) - .slice(1, -1) - .map((item) => ({ - [item.split(':')[0]]: item.split(':')[1].trim(), - })) - .reduce((a, b) => ({ ...a, ...b })) as unknown as RedisInfo - ), - - this.client.info('server').then( - (info) => - info - .split(/\n\r?/) - .slice(1, -1) - .map((item) => ({ - [item.split(':')[0]]: item.split(':')[1].trim(), - })) - .reduce((a, b) => ({ ...a, ...b })) as unknown as RedisServerInfo - ), - ]); - - return { - server, - stats, - ping, - }; - } -} diff --git a/src/components/Sentry.ts b/src/components/Sentry.ts deleted file mode 100644 index 7ea91638..00000000 --- a/src/components/Sentry.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { version, commitHash } from '../util/Constants'; -import { Component, Inject } from '@augu/lilith'; -import { hostname } from 'os'; -import { Logger } from 'tslog'; -import * as sentry from '@sentry/node'; -import Config from './Config'; - -@Component({ - priority: 5, - name: 'sentry', -}) -export default class Sentry { - @Inject - private logger!: Logger; - - @Inject - private config!: Config; - - load() { - this.logger.info('Initializing Sentry...'); - - const dsn = this.config.getProperty('sentryDsn'); - if (dsn === undefined) { - this.logger.warn("Missing sentryDsn variable in config.yml! Don't worry, this is optional."); - return; - } - - sentry.init({ - tracesSampleRate: 1.0, - integrations: [new sentry.Integrations.Http({ tracing: true })], - environment: this.config.getProperty('environment')!, - serverName: hostname(), - release: version, - dsn, - }); - - sentry.configureScope((scope) => - scope.setTags({ - 'nino.environment': this.config.getProperty('environment')!, - 'nino.commitHash': commitHash, - 'nino.version': version, - 'system.user': require('os').userInfo().username, - 'system.os': process.platform, - }) - ); - - this.logger.info('Sentry has been installed.'); - } - - report(ex: Error) { - sentry.captureException(ex); - } -} diff --git a/src/components/timeouts/Timeouts.ts b/src/components/timeouts/Timeouts.ts deleted file mode 100644 index 73a0f094..00000000 --- a/src/components/timeouts/Timeouts.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PunishmentService from '../../services/PunishmentService'; -import { Component, Inject } from '@augu/lilith'; -import * as types from './types'; -import { Logger } from 'tslog'; -import WebSocket from 'ws'; -import Discord from '../../components/Discord'; -import Config from '../../components/Config'; -import Redis from '../../components/Redis'; - -interface ApplyTimeoutOptions { - moderator: string; - reason?: string; - victim: string; - guild: string; - time: number; - type: types.PunishmentTimeoutType; -} - -@Component({ - priority: 6, - name: 'timeouts', -}) -export default class TimeoutsManager { - protected _reconnectTimeout?: NodeJS.Timeout; - protected _connectTimeout?: NodeJS.Timeout; - protected _readyPromise?: { resolve(): void; reject(error: Error): void }; - - private socket!: WebSocket; - public state: types.SocketState = types.SocketState.Unknown; - - @Inject - private punishments!: PunishmentService; - - @Inject - private discord!: Discord; - - @Inject - private logger!: Logger; - - @Inject - private redis!: Redis; - - @Inject - private config!: Config; - - load() { - return new Promise((resolve, reject) => { - this._readyPromise = { resolve, reject }; - this.state = types.SocketState.Connecting; - this.logger.info( - this._reconnectTimeout !== undefined - ? 'Reconnecting to the timeouts service...' - : 'Connecting to the timeouts service!' - ); - - const host = this.config.getProperty('timeouts.host'); - const port = this.config.getProperty('timeouts.port'); - const auth = this.config.getProperty('timeouts.auth'); - - // @ts-ignore yes - if (this.config.getProperty('timeouts') === undefined) - return reject( - 'Missing `timeouts` configuration, refer to the Process section: https://github.com/NinoDiscord/Nino#config-timeouts' - ); - - this.socket = new WebSocket(`ws://${host ?? 'localhost'}:${port}`, { - headers: { - Authorization: auth, - }, - }); - - if (this._reconnectTimeout !== undefined) clearTimeout(this._reconnectTimeout); - - delete this._reconnectTimeout; - this.socket.on('open', this._onOpen.bind(this)); - this.socket.on('error', this._onError.bind(this)); - this.socket.on('close', this._onClose.bind(this)); - this.socket.on('message', this._onMessage.bind(this)); - - this._connectTimeout = setTimeout(() => { - clearTimeout(this._connectTimeout!); - - delete this._connectTimeout; - delete this._readyPromise; - return reject(new Error('Connection to timeouts service took too long.')); - }, 15000); - }); - } - - dispose() { - return this.socket.close(); - } - - send(op: types.OPCodes.Request, data: types.RequestPacket['d']): void; - send(op: types.OPCodes.Acknowledged, data: types.AcknowledgedPacket['d']): void; - send(op: types.OPCodes, d?: any) { - this.socket.send( - JSON.stringify({ - op, - d, - }) - ); - } - - async apply({ moderator, reason, victim, guild, time, type }: ApplyTimeoutOptions) { - const list = await this.redis.getTimeouts(guild); - list.push({ - moderator, - reason: reason === undefined ? null : reason, - expired: Date.now() + time, - issued: Date.now(), - guild, - user: victim, - type, - }); - - await this.redis.client.hmset('nino:timeouts', [guild, JSON.stringify(list)]); - this.send(types.OPCodes.Request, { - moderator, - reason: reason === undefined ? null : reason, - expired: Date.now() + time, - issued: Date.now(), - guild, - user: victim, - type, - }); - - return Promise.resolve(); - } - - private _onOpen() { - this.logger.info('Established a connection with the timeouts service.'); - this.state = types.SocketState.Connected; - - if (this._connectTimeout !== undefined) clearTimeout(this._connectTimeout); - this._readyPromise?.resolve(); - - delete this._readyPromise; - } - - private _onError(error: Error) { - this.logger.error(error); - } - - private _onClose(code: number, reason: string) { - this.logger.warn(`Timeouts service has closed our connection with "${code}: ${reason}"`); - - this._reconnectTimeout = setTimeout(() => { - this.logger.info('Attempting to reconnect...'); - this.load(); - }, 2500); - } - - private async _onMessage(message: string) { - const data: types.DataPacket = JSON.parse(message); - switch (data.op) { - case types.OPCodes.Ready: - { - this.logger.info('Authenicated successfully, now sending timeouts...'); - const timeouts = await this.redis.client - .hvals('nino:timeouts') - .then((value) => - value[0] !== '' ? value.map((val) => JSON.parse(val)).flat() : ([] as types.Timeout[]) - ); - this.logger.info(`Received ${timeouts.length} timeouts to relay`); - - this.send(types.OPCodes.Acknowledged, timeouts); - } - break; - - case types.OPCodes.Apply: - { - const packet = data as types.ApplyPacket; - this.logger.debug(`Told to apply a packet on user ${packet.d.user} in guild ${packet.d.guild}.`); - - const guild = this.discord.client.guilds.get(packet.d.guild); - if (guild === undefined) { - this.logger.warn(`Guild ${packet.d.guild} has pending timeouts but Nino isn't in the guild? Skipping...`); - break; - } - - const timeouts = await this.redis.getTimeouts(packet.d.guild); - const available = timeouts.filter( - (pkt) => - packet.d.user !== pkt.user && - packet.d.type.toLowerCase() !== pkt.type.toLowerCase() && - pkt.guild === packet.d.guild - ); - - await this.redis.client.hmset('nino:timeouts', [guild.id, JSON.stringify(available)]); - await this.punishments.apply({ - moderator: this.discord.client.user, - publish: true, - reason: packet.d.reason === null ? '[Automod] Time is up.' : packet.d.reason, - member: { id: packet.d.user, guild }, - type: packet.d.type, - }); - } - break; - } - } -} diff --git a/src/components/timeouts/types.ts b/src/components/timeouts/types.ts deleted file mode 100644 index 3b24145a..00000000 --- a/src/components/timeouts/types.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { PunishmentType } from '../../entities/PunishmentsEntity'; - -/** - * Current state of the WebSocket connection with the timeouts service - */ -export const enum SocketState { - Connecting = 'connecting', - Connected = 'connected', - Unknown = 'unknown', - Closed = 'closed', -} - -/** - * List of OPCodes to send or receive from - */ -export const enum OPCodes { - // receive - Ready, - Apply, - - // send - Request, - Acknowledged, -} - -/** - * Represents a data packet that is sent out - * @typeparam T - The data packet received - * @typeparam OP - The OPCode that this data represents - */ -export interface DataPacket { - op: OP; - d: T; -} - -export interface Timeout { - moderator: string; - expired: number; - issued: number; - reason: string | null; - guild: string; - user: string; - type: PunishmentTimeoutType; -} - -/** - * Represents that the service is ready and probably has - * incoming timeouts to take action on - */ -export type ReadyPacket = DataPacket; - -/** - * Represents what a request to send to the service - */ -export type RequestPacket = DataPacket; - -/** - * Represents the data payload when we acknowledged - */ -export type AcknowledgedPacket = DataPacket; - -export type ApplyPacket = DataPacket; -export type PunishmentTimeoutType = Exclude< - PunishmentType, - | PunishmentType.Kick - | PunishmentType.WarningAdded - | PunishmentType.WarningRemoved - | PunishmentType.Mute - | PunishmentType.Ban - | PunishmentType.VoiceMute - | PunishmentType.VoiceDeafen ->; diff --git a/src/container.ts b/src/container.ts deleted file mode 100644 index 41b87b67..00000000 --- a/src/container.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Container } from '@augu/lilith'; -import { join } from 'path'; -import logger from './singletons/Logger'; -import http from './singletons/Http'; - -const app = new Container({ - componentsDir: join(__dirname, 'components'), - servicesDir: join(__dirname, 'services'), - singletons: [http, logger], -}); - -app.on('onBeforeChildInit', (cls, child) => logger.debug(`>> ${cls.name}->${child.constructor.name}: initializing...`)); -app.on('onAfterChildInit', (cls, child) => logger.debug(`>> ✔ ${cls.name}->${child.constructor.name}: initialized`)); -app.on('onBeforeInit', (cls) => logger.debug(`>> ${cls.name}: initializing...`)); -app.on('onAfterInit', (cls) => logger.debug(`>> ✔ ${cls.name}: initialized`)); -app.on('debug', (message) => logger.debug(`lilith: ${message}`)); - -app.on('initError', console.error); -app.on('childInitError', console.error); - -(global as any).app = app; -export default app; diff --git a/src/controllers/AutomodController.ts b/src/controllers/AutomodController.ts deleted file mode 100644 index bd72ff25..00000000 --- a/src/controllers/AutomodController.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import type Database from '../components/Database'; -import AutomodEntity from '../entities/AutomodEntity'; - -export default class AutomodController { - constructor(private database: Database) {} - - private get repository() { - return this.database.connection.getRepository(AutomodEntity); - } - - get(guildID: string) { - return this.repository.findOne({ guildID }); - } - - create(guildID: string) { - // all automod is disabled by default - const entry = new AutomodEntity(); - entry.blacklistWords = []; - entry.guildID = guildID; - - return this.repository.save(entry); - } - - delete(guildID: string) { - return this.repository.delete({ guildID }); - } - - update(guildID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(AutomodEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .execute(); - } -} diff --git a/src/controllers/BlacklistController.ts b/src/controllers/BlacklistController.ts deleted file mode 100644 index 43a80dfa..00000000 --- a/src/controllers/BlacklistController.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import BlacklistEntity, { BlacklistType } from '../entities/BlacklistEntity'; -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import type Database from '../components/Database'; - -interface CreateBlacklistOptions { - reason?: string; - issuer: string; - type: BlacklistType; - id: string; -} - -export default class BlacklistController { - constructor(private database: Database) {} - - private get repository() { - return this.database.connection.getRepository(BlacklistEntity); - } - - get(id: string) { - return this.repository.findOne({ id }); - } - - getByType(type: BlacklistType) { - return this.repository.find({ type }); - } - - create({ reason, issuer, type, id }: CreateBlacklistOptions) { - const entry = new BlacklistEntity(); - entry.issuer = issuer; - entry.type = type; - entry.id = id; - - if (reason !== undefined) entry.reason = reason; - - return this.repository.save(entry); - } - - delete(id: string) { - return this.repository.delete({ id }); - } - - update(id: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(BlacklistEntity) - .set(values) - .where('id = :id', { id }) - .execute(); - } -} diff --git a/src/controllers/CasesController.ts b/src/controllers/CasesController.ts deleted file mode 100644 index 6c51bce8..00000000 --- a/src/controllers/CasesController.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import { PunishmentType } from '../entities/PunishmentsEntity'; -import type Database from '../components/Database'; -import CaseEntity from '../entities/CaseEntity'; - -interface CreateCaseOptions { - attachments: string[]; - moderatorID: string; - victimID: string; - guildID: string; - reason?: string; - soft?: boolean; - time?: number; - type: PunishmentType; -} - -export default class CasesController { - constructor(private database: Database) {} - - get repository() { - return this.database.connection.getRepository(CaseEntity); - } - - get(guildID: string, caseID: number) { - return this.repository.findOne({ guildID, index: caseID }); - } - - getAll(guildID: string) { - return this.repository.find({ guildID }); - } - - async create({ attachments, moderatorID, victimID, guildID, reason, soft, time, type }: CreateCaseOptions) { - const cases = await this.getAll(guildID); - const index = (cases[cases.length - 1]?.index ?? 0) + 1; - - const entry = new CaseEntity(); - entry.attachments = attachments; - entry.moderatorID = moderatorID; - entry.victimID = victimID; - entry.guildID = guildID; - entry.index = index; - entry.soft = soft === true; // if it's undefined, then it'll be false so no ternaries :crab: - entry.type = type; - - if (reason !== undefined) entry.reason = reason; - - if (time !== undefined) entry.time = String(time); - - return this.repository.save(entry); - } - - update(guildID: string, index: number, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(CaseEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .andWhere('index = :idx', { idx: index }) - .execute(); - } -} diff --git a/src/controllers/GuildSettingsController.ts b/src/controllers/GuildSettingsController.ts deleted file mode 100644 index dc2a3d53..00000000 --- a/src/controllers/GuildSettingsController.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import type Database from '../components/Database'; -import GuildEntity from '../entities/GuildEntity'; -import { Inject } from '@augu/lilith'; -import Config from '../components/Config'; - -export default class GuildSettingsController { - @Inject - private readonly config!: Config; - - constructor(private database: Database) {} - - get repository() { - return this.database.connection.getRepository(GuildEntity); - } - - get(id: string, create?: true): Promise; - get(id: string, create?: false): Promise; - async get(id: string, create: boolean = true) { - const settings = await this.repository.findOne({ guildID: id }); - if (settings === undefined && create) return this.create(id); - - return settings; - } - - async create(id: string) { - const entry = new GuildEntity(); - entry.prefixes = this.config.getProperty('prefixes') ?? []; - entry.language = 'en_US'; - entry.guildID = id; - - await this.repository.save(entry); - - try { - await this.database.logging.create(id); - await this.database.automod.create(id); - } catch { - // swallow for now - } - - return entry; - } - - delete(id: string) { - return this.repository.delete({ guildID: id }); - } - - update(guildID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(GuildEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .execute(); - } -} diff --git a/src/controllers/LoggingController.ts b/src/controllers/LoggingController.ts deleted file mode 100644 index ef53a741..00000000 --- a/src/controllers/LoggingController.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import LoggingEntity from '../entities/LoggingEntity'; -import type Database from '../components/Database'; - -export default class LoggingController { - constructor(private database: Database) {} - - private get repository() { - return this.database.connection.getRepository(LoggingEntity); - } - - async get(guildID: string) { - const entry = await this.repository.findOne({ guildID }); - if (entry === undefined) return this.create(guildID); - - return entry; - } - - create(guildID: string) { - const entry = new LoggingEntity(); - entry.ignoreChannels = []; - entry.ignoreUsers = []; - entry.enabled = false; - entry.events = []; - entry.guildID = guildID; - - return this.repository.save(entry); - } - - update(guildID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(LoggingEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .execute(); - } -} diff --git a/src/controllers/PunishmentsController.ts b/src/controllers/PunishmentsController.ts deleted file mode 100644 index bc3abf61..00000000 --- a/src/controllers/PunishmentsController.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PunishmentEntity, { PunishmentType } from '../entities/PunishmentsEntity'; -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import Database from '../components/Database'; - -interface CreatePunishmentOptions { - warnings: number; - guildID: string; - soft?: boolean; - time?: number; - days?: number; - type: PunishmentType; -} - -export default class PunishmentsController { - constructor(private database: Database) {} - - private get repository() { - return this.database.connection.getRepository(PunishmentEntity); - } - - async create({ warnings, guildID, soft, time, days, type }: CreatePunishmentOptions) { - const all = await this.getAll(guildID); - const entry = new PunishmentEntity(); - entry.warnings = warnings; - entry.guildID = guildID; - entry.index = all.length + 1; // increment by 1 - entry.type = type; - - if (soft !== undefined && soft === true) entry.soft = true; - - if (time !== undefined) entry.time = time; - - if (days !== undefined) entry.days = days; - - return this.repository.save(entry); - } - - getAll(guildID: string) { - return this.repository.find({ guildID }); - } - - get(guildID: string, index: number) { - return this.repository.findOne({ guildID, index }); - } - - update(guildID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(PunishmentEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .execute(); - } -} diff --git a/src/controllers/UserSettingsController.ts b/src/controllers/UserSettingsController.ts deleted file mode 100644 index 488ad3d3..00000000 --- a/src/controllers/UserSettingsController.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import type Database from '../components/Database'; -import UserEntity from '../entities/UserEntity'; - -export default class UserSettingsController { - constructor(private database: Database) {} - - get repository() { - return this.database.connection.getRepository(UserEntity); - } - - async get(id: string) { - const settings = await this.repository.findOne({ id }); - if (settings === undefined) { - const entry = new UserEntity(); - entry.prefixes = []; - entry.language = 'en_US'; - entry.id = id; - - try { - await this.repository.save(entry); - } catch { - // swallow the error for now - } - - return entry; - } - - return settings; - } - - update(userID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(UserEntity) - .set(values) - .where('user_id = :id', { id: userID }) - .execute(); - } -} diff --git a/src/controllers/WarningsController.ts b/src/controllers/WarningsController.ts deleted file mode 100644 index 13ad0d8c..00000000 --- a/src/controllers/WarningsController.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import WarningEntity from '../entities/WarningsEntity'; -import type Database from '../components/Database'; - -interface CreateWarningOptions { - userID: string; - guildID: string; - reason?: string; - amount: number; -} - -export default class WarningsController { - constructor(private database: Database) {} - - private get repository() { - return this.database.connection.getRepository(WarningEntity); - } - - get(guildID: string, userID: string) { - return this.repository.findOne({ guildID, userID }); - } - - getAll(guildID: string, userID?: string) { - const filter = userID !== undefined ? { guildID, userID } : { guildID }; - return this.repository.find(filter); - } - - create({ guildID, userID, reason, amount }: CreateWarningOptions) { - if (amount < 0) throw new RangeError('amount index out of bounds'); - - const entry = new WarningEntity(); - entry.guildID = guildID; - entry.reason = reason; - entry.amount = amount; - entry.userID = userID; - - return this.repository.save(entry); - } - - update(guildID: string, userID: string, values: QueryDeepPartialEntity) { - return this.database.connection - .createQueryBuilder() - .update(WarningEntity) - .set(values) - .where('guild_id = :id', { id: guildID }) - .andWhere('user_id = :id', { id: userID }) - .execute(); - } - - clean(guildID: string, userID: string) { - return this.repository.delete({ guildID, userID }); - } -} diff --git a/src/entities/AutomodEntity.ts b/src/entities/AutomodEntity.ts deleted file mode 100644 index 3d601117..00000000 --- a/src/entities/AutomodEntity.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Entity, Column, PrimaryColumn } from 'typeorm'; - -@Entity({ name: 'automod' }) -export default class AutomodEntity { - @Column({ - array: true, - type: 'text', - name: 'whitelist_channels_during_raid', - default: '{}', - }) - public whitelistChannelsDuringRaid!: string[]; - - @Column({ array: true, type: 'text', name: 'blacklist_words', default: '{}' }) - public blacklistWords!: string[]; - - @Column({ default: false, name: 'short_links' }) - public shortLinks!: boolean; - - @Column({ default: false }) - public blacklist!: boolean; - - @Column({ default: false }) - public mentions!: boolean; - - @Column({ default: false }) - public invites!: boolean; - - @Column({ default: false }) - public dehoist!: boolean; - - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; - - @Column({ default: false }) - public spam!: boolean; - - @Column({ default: false }) - public raid!: boolean; -} diff --git a/src/entities/CaseEntity.ts b/src/entities/CaseEntity.ts deleted file mode 100644 index e597213a..00000000 --- a/src/entities/CaseEntity.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Entity, Column, PrimaryColumn } from 'typeorm'; -import { PunishmentType } from './PunishmentsEntity'; - -@Entity({ name: 'cases' }) -export default class CaseEntity { - @Column({ default: '{}', array: true, type: 'text' }) - public attachments!: string[]; - - @Column({ name: 'moderator_id' }) - public moderatorID!: string; - - @Column({ name: 'message_id', nullable: true, default: undefined }) - public messageID?: string; - - @Column({ name: 'victim_id' }) - public victimID!: string; - - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; - - @Column({ nullable: true, default: undefined }) - public reason?: string; - - @PrimaryColumn() - public index!: number; - - @Column({ - type: 'enum', - enum: PunishmentType, - }) - public type!: PunishmentType; - - @Column({ default: false }) - public soft!: boolean; - - @Column({ nullable: true, default: undefined, type: 'bigint' }) - public time?: string; -} diff --git a/src/entities/LoggingEntity.ts b/src/entities/LoggingEntity.ts deleted file mode 100644 index 101fc1b2..00000000 --- a/src/entities/LoggingEntity.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Entity, Column, PrimaryColumn } from 'typeorm'; - -export enum LoggingEvents { - VoiceMemberDeafened = 'voice_member_deafened', - VoiceChannelSwitch = 'voice_channel_switch', - VoiceMemberMuted = 'voice_member_muted', - VoiceChannelLeft = 'voice_channel_left', - VoiceChannelJoin = 'voice_channel_join', - MessageDeleted = 'message_delete', - MessageUpdated = 'message_update', -} - -@Entity({ name: 'logging' }) -export default class LoggingEntity { - @Column({ default: '{}', array: true, type: 'text', name: 'ignore_channels' }) - public ignoreChannels!: string[]; - - @Column({ default: '{}', array: true, type: 'text', name: 'ignore_users' }) - public ignoreUsers!: string[]; - - @Column({ name: 'channel_id', nullable: true }) - public channelID?: string; - - @Column({ default: false }) - public enabled!: boolean; - - @Column({ type: 'enum', array: true, enum: LoggingEvents, default: '{}' }) - public events!: LoggingEvents[]; - - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; -} diff --git a/src/entities/PunishmentsEntity.ts b/src/entities/PunishmentsEntity.ts deleted file mode 100644 index 81985cdc..00000000 --- a/src/entities/PunishmentsEntity.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn } from 'typeorm'; - -export enum PunishmentType { - WarningRemoved = 'warning.removed', - VoiceUndeafen = 'voice.undeafen', - WarningAdded = 'warning.added', - VoiceUnmute = 'voice.unmute', - VoiceDeafen = 'voice.deafen', - VoiceMute = 'voice.mute', - Unmute = 'unmute', - Unban = 'unban', - Kick = 'kick', - Mute = 'mute', - Ban = 'ban', -} - -@Entity({ name: 'punishments' }) -export default class PunishmentEntity { - @Column({ default: 1 }) - public warnings!: number; - - @PrimaryColumn({ name: 'guild_id' }) - public guildID!: string; - - @PrimaryColumn() - public index!: number; - - @Column({ default: false }) - public soft!: boolean; - - @Column({ default: undefined, nullable: true }) - public time?: number; - - @Column({ default: undefined, nullable: true }) - public days?: number; - - @Column({ - type: 'enum', - enum: PunishmentType, - }) - public type!: PunishmentType; -} diff --git a/src/listeners/GuildBansListener.ts b/src/listeners/GuildBansListener.ts deleted file mode 100644 index b225562d..00000000 --- a/src/listeners/GuildBansListener.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PunishmentService, { PunishmentEntryType } from '../services/PunishmentService'; -import { Constants, Guild, User } from 'eris'; -import { Inject, Subscribe } from '@augu/lilith'; -import { PunishmentType } from '../entities/PunishmentsEntity'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class GuildBansListener { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Subscribe('guildBanAdd', { emitter: 'discord' }) - async onGuildBanAdd(guild: Guild, user: User) { - if (!guild.members.get(this.discord.client.user.id)?.permissions.has('viewAuditLogs')) { - return; - } - - const audits = await guild.getAuditLog({ - actionType: Constants.AuditLogActions.MEMBER_BAN_ADD, - limit: 3, - }); - - const entry = audits.entries.find( - (entry) => entry.targetID === user.id && entry.user.id !== this.discord.client.user.id - ); - - if (entry === undefined) return; - - const caseModel = await this.database.cases.create({ - attachments: [], - moderatorID: entry.user.id, - victimID: entry.targetID, - guildID: entry.guild.id, - reason: entry.reason ?? 'No reason was provided', - type: PunishmentType.Ban, - }); - - await this.punishments['publishToModLog']( - { - moderator: this.discord.client.users.get(entry.user.id)!, - victim: this.discord.client.users.get(entry.targetID)!, - reason: entry.reason ?? 'No reason was provided', - guild: entry.guild, - type: PunishmentEntryType.Banned, - }, - caseModel - ); - } - - @Subscribe('guildBanRemove', { emitter: 'discord' }) - async onGuildBanRemove(guild: Guild, user: User) { - if (!guild.members.get(this.discord.client.user.id)?.permissions.has('viewAuditLogs')) return; - - const audits = await guild.getAuditLog({ - actionType: Constants.AuditLogActions.MEMBER_BAN_REMOVE, - limit: 3, - }); - - const entry = audits.entries.find( - (entry) => entry.targetID === user.id && entry.user.id !== this.discord.client.user.id - ); - - if (entry === undefined) return; - - const caseModel = await this.database.cases.create({ - attachments: [], - moderatorID: entry.user.id, - victimID: entry.targetID, - guildID: entry.guild.id, - reason: entry.reason ?? 'No reason was provided', - type: PunishmentType.Unban, - }); - - await this.punishments['publishToModLog']( - { - moderator: this.discord.client.users.get(entry.user.id)!, - victim: this.discord.client.users.get(entry.targetID)!, - reason: 'Moderator has unbanned on their own accord.', - guild: entry.guild, - type: PunishmentEntryType.Unban, - }, - caseModel - ); - } -} diff --git a/src/listeners/GuildListener.ts b/src/listeners/GuildListener.ts deleted file mode 100644 index e719cda0..00000000 --- a/src/listeners/GuildListener.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { Guild, TextChannel } from 'eris'; -import { Inject, Subscribe } from '@augu/lilith'; -import { EmbedBuilder } from '../structures'; -import BotlistsService from '../services/BotlistService'; -import { Logger } from 'tslog'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; -import Config from '../components/Config'; -import Prom from '../components/Prometheus'; - -export default class VoidListener { - @Inject - private readonly prometheus?: Prom; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly botlists?: BotlistsService; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - - @Subscribe('guildCreate', { emitter: 'discord' }) - async onGuildCreate(guild: Guild) { - if (guild.name === undefined) return; - - this.logger.info(`✔ New Guild: ${guild.name} (${guild.id})`); - await this.database.guilds.create(guild.id); - this.prometheus?.guildCount?.inc(); - await this.botlists?.post(); - - const channel = this.discord.client.getChannel('844410521599737878') as TextChannel; - const owner = this.discord.client.users.get(guild.ownerID); - const bots = guild.members.filter((r) => r.bot).length; - const humans = guild.members.filter((r) => !r.bot).length; - - const prefixes = this.config.getProperty('prefixes') ?? ['x!']; - const statusType = this.config.getProperty('status.type'); - const status = this.config.getProperty('status.status')!; - - for (const shard of this.discord.client.shards.values()) { - this.discord.client.editStatus(this.config.getProperty('status.presence') ?? 'online', { - name: status - .replace('$prefix$', prefixes[Math.floor(Math.random() * prefixes.length)]) - .replace('$guilds$', this.discord.client.guilds.size.toLocaleString()) - .replace('$shard$', `#${shard.id}`), - - type: statusType ?? 0, - }); - } - - if (channel !== undefined && channel.type === 0) { - const embed = EmbedBuilder.create() - .setAuthor( - `[ Joined ${guild.name} (${guild.id}) ]`, - undefined, - this.discord.client.user.dynamicAvatarURL('png', 1024) - ) - .setDescription([ - `• **Members [Bots / Total]**: ${humans.toLocaleString()} members with ${bots} bots (large?: ${ - guild.large ? 'Yes' : 'No' - })`, - `• **Owner**: ${owner ? `${owner.username}#${owner.discriminator} (${owner.id})` : 'Not cached'}`, - ]) - .setFooter(`✔ Now at ${this.discord.client.guilds.size.toLocaleString()} Guilds`); - - return channel.createMessage({ embed: embed.build() }); - } - } - - @Subscribe('guildDelete', { emitter: 'discord' }) - async onGuildDelete(guild: Guild) { - if (guild.name === undefined) return; - - this.logger.info(`❌ Left Guild: ${guild.name} (${guild.id})`); - await this.database.guilds.delete(guild.id); - this.prometheus?.guildCount?.dec(); - await this.botlists?.post(); - - const channel = this.discord.client.getChannel('844410521599737878') as TextChannel; - const owner = this.discord.client.users.get(guild.ownerID); - const bots = guild.members.filter((r) => r.bot).length; - const humans = guild.members.filter((r) => !r.bot).length; - - const prefixes = this.config.getProperty('prefixes') ?? ['x!']; - const statusType = this.config.getProperty('status.type'); - const status = this.config.getProperty('status.status')!; - - for (const shard of this.discord.client.shards.values()) { - this.discord.client.editStatus(this.config.getProperty('status.presence') ?? 'online', { - name: status - .replace('$prefix$', prefixes[Math.floor(Math.random() * prefixes.length)]) - .replace('$guilds$', this.discord.client.guilds.size.toLocaleString()) - .replace('$shard$', `#${shard.id}`), - - type: statusType ?? 0, - }); - } - - if (channel !== undefined && channel.type === 0) { - const embed = EmbedBuilder.create() - .setAuthor( - `[ Left ${guild.name} (${guild.id}) ]`, - undefined, - this.discord.client.user.dynamicAvatarURL('png', 1024) - ) - .setDescription([ - `• **Members [Bots / Total]**: ${humans.toLocaleString()} members with ${bots} bots (large?: ${ - guild.large ? 'Yes' : 'No' - })`, - `• **Owner**: ${owner ? `${owner.username}#${owner.discriminator} (${owner.id})` : 'Not cached'}`, - ]) - .setFooter(`✔ Now at ${this.discord.client.guilds.size.toLocaleString()} Guilds`); - - return channel.createMessage({ embed: embed.build() }); - } - } -} diff --git a/src/listeners/GuildMemberListener.ts b/src/listeners/GuildMemberListener.ts deleted file mode 100644 index f501eefd..00000000 --- a/src/listeners/GuildMemberListener.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PunishmentService, { PunishmentEntryType } from '../services/PunishmentService'; -import { Constants, Guild, Member } from 'eris'; -import { Inject, Subscribe } from '@augu/lilith'; -import { PunishmentType } from '../entities/PunishmentsEntity'; -import AutomodService from '../services/AutomodService'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -interface OldMember { - premiumSince: number | null; - pending: boolean; - nick?: string; - roles: string[]; -} - -export default class GuildMemberListener { - @Inject - private readonly punishments!: PunishmentService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly automod!: AutomodService; - - private async findAuditLog(guild: Guild, member: Member) { - if (!guild.members.get(this.discord.client.user.id)?.permissions.has('viewAuditLogs')) return undefined; - - try { - const audits = await guild.getAuditLog({ - limit: 3, - actionType: Constants.AuditLogActions.MEMBER_ROLE_UPDATE, - }); - return audits.entries - .sort((a, b) => b.createdAt - a.createdAt) - .find( - (entry) => - entry.user.id !== this.discord.client.user.id && // Check if the user that did it was not Nino - entry.targetID === member.id && // Check if the target ID is the member - entry.user.id !== member.id // Check if the user isn't thereselves - ); - } catch { - return undefined; - } - } - - @Subscribe('guildMemberUpdate', { emitter: 'discord' }) - async onGuildMemberUpdate(guild: Guild, member: Member, old: OldMember) { - const settings = await this.database.automod.get(guild.id); - const gSettings = await this.database.guilds.get(guild.id); - - // cannot really do anything if `old` = null - if (old === null) return; - - if (old.hasOwnProperty('nick') && (old.nick !== undefined || old.nick !== null) && member.nick !== old.nick) { - if (settings !== undefined && settings.dehoist === false) return; - - const result = await this.automod.run('memberNick', member); - if (result) return; - } - - if (member.user.bot) return; - - if (gSettings.mutedRoleID === undefined) return; - - // taken away - if (!member.roles.includes(gSettings.mutedRoleID) && old.roles.includes(gSettings.mutedRoleID)) { - const entry = await this.findAuditLog(guild, member); - if (!entry) return; - - await this.punishments.apply({ - moderator: entry.user, - member, - reason: '[Automod] Moderator has removed the Muted role', - type: PunishmentType.Unmute, - }); - } - - // added it - if (member.roles.includes(gSettings.mutedRoleID) && !old.roles.includes(gSettings.mutedRoleID)) { - const entry = await this.findAuditLog(guild, member); - if (!entry) return; - - await this.punishments.apply({ - moderator: entry.user, - member, - reason: '[Automod] Moderator has added the Muted role', - type: PunishmentType.Mute, - }); - } - } - - @Subscribe('guildMemberAdd', { emitter: 'discord' }) - async onGuildMemberJoin(guild: Guild, member: Member) { - const result = await this.automod.run('memberJoin', member); - if (result) return; - - const cases = await this.database.cases.getAll(guild.id); - const all = cases.filter((c) => c.victimID === member.id).sort((c) => c.index); - - if (all.length > 0 && all[all.length - 1]?.type === PunishmentType.Mute) { - await this.punishments.apply({ - moderator: this.discord.client.user, - member, - reason: '[Automod] Mute Evading', - type: PunishmentType.Mute, - }); - } - } - - @Subscribe('guildMemberRemove', { emitter: 'discord' }) - async onGuildMemberRemove(guild: Guild, member: Member) { - const logs = await guild - .getAuditLog({ - limit: 3, - actionType: Constants.AuditLogActions.MEMBER_KICK, - }) - .catch(() => undefined); - - if (logs === undefined) return; - - if (!logs.entries.length) return; - - const entry = logs.entries.find( - (entry) => entry.targetID === member.id && entry.user.id !== this.discord.client.user.id - ); - - if (!entry) return; - - const model = await this.database.cases.create({ - attachments: [], - moderatorID: entry.user.id, - victimID: entry.targetID, - guildID: guild.id, - reason: '[Automod] User was kicked by moderator', - type: PunishmentType.Kick, - }); - - await this.punishments['publishToModLog']( - { - moderator: entry.user, - victim: this.discord.client.users.get(entry.targetID)!, - reason: `[Automod] Automatic kick: ${entry.reason ?? 'unknown'}`, - guild, - type: PunishmentEntryType.Kicked, - }, - model - ); - } -} diff --git a/src/listeners/MessageListener.ts b/src/listeners/MessageListener.ts deleted file mode 100644 index 44ceaff6..00000000 --- a/src/listeners/MessageListener.ts +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Constants, Message, OldMessage, TextChannel } from 'eris'; -import { Inject, Subscribe } from '@augu/lilith'; -import { LoggingEvents } from '../entities/LoggingEntity'; -import { EmbedBuilder } from '../structures'; -import CommandService from '../services/CommandService'; -import AutomodService from '../services/AutomodService'; -import { Color } from '../util/Constants'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -const HTTP_REGEX = /^https?:\/\/(.*)/; - -export default class MessageListener { - @Inject - private readonly commands!: CommandService; - - @Inject - private readonly automod!: AutomodService; - - @Inject - private readonly database!: Database; - - @Inject - private readonly discord!: Discord; - - @Subscribe('messageCreate', { emitter: 'discord' }) - onMessageCreate(msg: Message) { - return this.commands.handleCommand(msg); - } - - @Subscribe('messageDelete', { emitter: 'discord' }) - async onMessageDelete(msg: Message) { - if (!msg.author || ![0, 5].includes(msg.channel.type)) return; - - const settings = await this.database.logging.get(msg.guildID); - if (!settings.enabled || !settings.events.includes(LoggingEvents.MessageDeleted)) return; - - if (settings.ignoreChannels.length > 0 && settings.ignoreChannels.includes(msg.channel.id)) return; - - if (settings.ignoreUsers.length > 0 && settings.ignoreUsers.includes(msg.author.id)) return; - - if ( - settings.channelID !== undefined && - (!msg.channel.guild.channels.has(settings.channelID) || - !msg.channel.guild.channels - .get(settings.channelID) - ?.permissionsOf(this.discord.client.user.id) - .has('sendMessages')) - ) - return; - - if (msg.content.indexOf('pinned a message') !== -1) return; - - if (msg.author.id === this.discord.client.user.id) return; - - if (msg.author.system) return; - - // It's in a closure so we don't have to use `return;` on the outer scope - const auditLog = await (async () => { - if (!msg.channel.guild.members.get(this.discord.client.user.id)?.permissions.has('viewAuditLogs')) - return undefined; - - const audits = await msg.channel.guild.getAuditLog({ - limit: 3, - actionType: Constants.AuditLogActions.MESSAGE_DELETE, - }); - return audits.entries.find( - (entry) => - entry.targetID === msg.author.id && - entry.user.id !== msg.author.id && - entry.user.id !== this.discord.client.user.id - ); - })(); - - const channel = msg.channel.guild.channels.get(settings.channelID!); - const author = msg.author.system ? 'System' : `${msg.author.username}#${msg.author.discriminator}`; - const embed = new EmbedBuilder().setColor(Color); - - if (auditLog !== undefined) - embed.setFooter( - `Message was actually deleted by ${auditLog.user.username}#${auditLog.user.discriminator} (${auditLog.user.id})` - ); - - if (msg.embeds.length > 0) { - const em = msg.embeds[0]; - if (em.author) embed.setAuthor(em.author.name, em.author.url, em.author.icon_url); - if (em.description) - embed.setDescription(em.description.length > 2000 ? `${em.description.slice(0, 1993)}...` : em.description); - if (em.fields && em.fields.length > 0) { - for (const field of em.fields) embed.addField(field.name, field.value, field.inline || false); - } - - if (em.footer) { - const footer = embed.footer; - embed.setFooter( - footer !== undefined ? `${em.footer.text} (${footer.text})` : em.footer.text, - em.footer.icon_url - ); - } - - if (em.title) embed.setTitle(em.title); - if (em.url) embed.setURL(em.url); - } else { - embed.setDescription( - msg.content.length > 1997 - ? `${msg.content.slice(0, 1995)}...` - : msg.content || 'Nothing was provided (probably attachments)' - ); - } - - return channel.createMessage({ - content: `**[** A message was deleted by **${author}** (⁄ ⁄•⁄ω⁄•⁄ ⁄) in <#${msg.channel.id}> **]**`, - embed: embed.build(), - }); - } - - @Subscribe('messageUpdate', { emitter: 'discord' }) - async onMessageUpdate(msg: Message, old: OldMessage | null) { - await this.automod.run('message', msg); - - if (old === null) return; - - if (old.content === msg.content) return; - - // discord is shit send help please - if (old.pinned && !msg.pinned) return; - - if (msg.content !== old.content) await this.commands.handleCommand(msg); - - const result = await this.automod.run('message', msg); - if (result) return; - - const settings = await this.database.logging.get(msg.channel.guild.id); - if (!settings.enabled || !settings.events.includes(LoggingEvents.MessageUpdated)) return; - - if (settings.ignoreChannels.length > 0 && settings.ignoreChannels.includes(msg.channel.id)) return; - - if (settings.ignoreUsers.length > 0 && settings.ignoreUsers.includes(msg.author.id)) return; - - if ( - settings.channelID !== undefined && - (!msg.channel.guild.channels.has(settings.channelID) || - !msg.channel.guild.channels - .get(settings.channelID) - ?.permissionsOf(this.discord.client.user.id) - .has('sendMessages')) - ) - return; - - if (msg.content.indexOf('pinned a message') !== -1) return; - - if (msg.author.id === this.discord.client.user.id) return; - - // discord being shit part 2 - if (HTTP_REGEX.test(old.content)) return; - - const channel = msg.channel.guild.channels.get(settings.channelID!); - const author = msg.author.system ? 'System' : `${msg.author.username}#${msg.author.discriminator}`; - const jumpUrl = `https://discord.com/channels/${msg.guildID}/${msg.channel.id}/${msg.id}`; - const embed = new EmbedBuilder() - .setColor(Color) - .setDescription(`**[[Jump Here]](${jumpUrl})**`) - .addFields([ - { - name: '❯ Old Message Content', - value: old.content || 'Nothing was provided (probably attachments?)', - inline: false, - }, - { - name: '❯ Message Content', - value: msg.content || 'Nothing was provided?', - }, - ]); - - return channel.createMessage({ - content: `**[** A message was updated by **${author}** (⁄ ⁄•⁄ω⁄•⁄ ⁄) in <#${msg.channel.id}> **]**`, - embed: embed.build(), - }); - } - - @Subscribe('messageDeleteBulk', { emitter: 'discord' }) - async onMessageDeleteBulk(messages: Message[]) { - const allMsgs = messages - .filter((msg) => msg.guildID !== undefined) - .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); - - const msg = allMsgs[0]; - const settings = await this.database.logging.get(msg.channel.guild.id); - if (!settings.enabled || !settings.events.includes(LoggingEvents.MessageUpdated)) return; - - if (!settings.channelID) return; - - if (!msg.channel.guild) return; - - if ( - !msg.channel.guild.channels.has(settings.channelID) || - !msg.channel.guild.channels - .get(settings.channelID) - ?.permissionsOf(this.discord.client.user.id) - .has('sendMessages') - ) - return; - - const buffers: Buffer[] = []; - for (let i = 0; i < allMsgs.length; i++) { - const msg = allMsgs[i]; - - // skip all that don't have an author - if (!msg.author) continue; - - const contents = [ - `♥*♡∞:。.。 [ Message #${i + 1} / ${allMsgs.length} ] 。.。:∞♡*♥`, - `❯ Created At: ${new Date(msg.createdAt).toUTCString()}`, - `❯ Author : ${msg.author.username}#${msg.author.discriminator}`, - `❯ Channel : #${msg.channel.name} (${msg.channel.id})`, - '', - ]; - - if (msg.embeds.length > 0) { - contents.push(msg.content); - contents.push('\n'); - - for (let j = 0; j < msg.embeds.length; j++) { - const embed = msg.embeds[j]; - let content = `[ Embed ${j + 1}/${msg.embeds.length} ]\n`; - if (embed.author !== undefined) - content += `❯ ${embed.author.name}${embed.author.url !== undefined ? ` (${embed.author.url})` : ''}\n`; - - if (embed.title !== undefined) - content += `❯ ${embed.title}${embed.url !== undefined ? ` (${embed.url})` : ''}\n`; - - if (embed.description !== undefined) content += `${embed.description}\n\n`; - - if (embed.fields !== undefined) - content += embed.fields.map((field) => `• ${field.name}: ${field.value}`).join('\n') + '\n'; - - if (embed.footer !== undefined) - content += `${embed.footer.text}${ - embed.timestamp !== undefined - ? ` (${(embed.timestamp instanceof Date ? embed.timestamp : new Date(embed.timestamp)).toUTCString()})` - : '' - }`; - - contents.push(content, '\n'); - } - } else { - contents.push(msg.content, '\n'); - } - - buffers.push(Buffer.from(contents.join('\n'))); - } - - // Don't do anything if we can't create a message - if (buffers.length > 0) return; - - const buffer = Buffer.concat(buffers); - const channel = msg.channel.guild.channels.get(settings.channelID!); - const users: string[] = [...new Set(allMsgs.map((m) => `${m.author.username}#${m.author.discriminator}`))]; - const embed = new EmbedBuilder() - .setColor(Color) - .setDescription([ - `${allMsgs.length} messages were deleted in ${msg.channel.mention}, view the file below to read all messages`, - '', - '```apache', - `❯ Messages Deleted ~> ${allMsgs.length}/${messages.length} (${( - (allMsgs.length / messages.length) * - 100 - ).toFixed(1)}% cached)`, - `❯ Affected Users ~> ${users.join(', ')}`, - '```', - ]); - - await Promise.all([ - channel.createMessage({ embed: embed.build() }), - channel.createMessage('', { - file: buffer, - name: `trace_${Date.now()}.txt`, - }), - ]); - } -} diff --git a/src/listeners/VoiceStateListener.ts b/src/listeners/VoiceStateListener.ts deleted file mode 100644 index 8e43bf92..00000000 --- a/src/listeners/VoiceStateListener.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Guild, Member, TextChannel, VoiceChannel } from 'eris'; -import { Inject, Subscribe } from '@augu/lilith'; -import { LoggingEvents } from '../entities/LoggingEntity'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; - -export default class VoiceStateListener { - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - private async getAuditLog(guild: Guild, actionType: number, condition?: string) { - if (!guild.members.get(this.discord.client.user.id)?.permissions.has('viewAuditLogs')) return undefined; - - try { - const audits = await guild.getAuditLog({ limit: 3, actionType }); - return audits.entries - .sort((a, b) => b.createdAt - a.createdAt) - .find( - (entry) => - entry.user.id === this.discord.client.user.id && // If Nino has done this action - condition !== undefined && - entry.reason?.startsWith(condition) - ); - } catch { - return undefined; - } - } - - @Subscribe('voiceChannelJoin', { emitter: 'discord' }) - async onVoiceChannelJoin(member: Member, voice: VoiceChannel) { - const settings = await this.database.logging.get(member.guild.id); - if (!settings.enabled || !settings.events.includes(LoggingEvents.VoiceChannelJoin)) return; - - const channel = - settings.channelID !== undefined ? await this.discord.getChannel(settings.channelID) : null; - if ( - channel === null || - !member.guild.channels.has(settings.channelID!) || - !member.guild.channels.get(settings.channelID!)!.permissionsOf(this.discord.client.user.id).has('sendMessages') - ) - return; - - return channel.createMessage( - `:loudspeaker: **${member.user.username}#${member.user.discriminator}** (${member.user.id}) has joined channel **${voice.name}** with ${voice.voiceMembers.size} members.` - ); - } - - @Subscribe('voiceChannelLeave', { emitter: 'discord' }) - async onVoiceChannelLeave(member: Member, voice: VoiceChannel) { - const settings = await this.database.logging.get(member.guild.id); - if (!settings.enabled || !settings.events.includes(LoggingEvents.VoiceChannelJoin)) return; - - // Don't log entries if Nino has kicked them - const entry = await this.getAuditLog(member.guild, 27, '[Voice Kick]'); - if (entry !== undefined) return; - - const channel = - settings.channelID !== undefined ? await this.discord.getChannel(settings.channelID) : null; - if ( - channel === null || - !member.guild.channels.has(settings.channelID!) || - !member.guild.channels.get(settings.channelID!)!.permissionsOf(this.discord.client.user.id).has('sendMessages') - ) - return; - - return channel.createMessage( - `:bust_in_silhouette: **${member.username}#${member.discriminator}** (${member.id}) has left channel **${voice.name}**` - ); - } - - @Subscribe('voiceChannelSwitch', { emitter: 'discord' }) - async onVoiceChannelSwitch(member: Member, voice: VoiceChannel, old: VoiceChannel) { - const settings = await this.database.logging.get(member.guild.id); - if (!settings.enabled || !settings.events.includes(LoggingEvents.VoiceChannelJoin)) return; - - const channel = - settings.channelID !== undefined ? await this.discord.getChannel(settings.channelID) : null; - if ( - channel === null || - !member.guild.channels.has(settings.channelID!) || - !member.guild.channels.get(settings.channelID!)!.permissionsOf(this.discord.client.user.id).has('sendMessages') - ) - return; - - return channel.createMessage( - `:radio_button: **${member.username}#${member.discriminator}** (${member.id}) has switch from channel ${old.name} to ${voice.name}.` - ); - } -} diff --git a/src/listeners/VoidListener.ts b/src/listeners/VoidListener.ts deleted file mode 100644 index 372ebf82..00000000 --- a/src/listeners/VoidListener.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import type { GatewayInteractionCreateDispatchData, APIApplicationCommandInteractionData } from 'discord-api-types'; -import { RawPacket, AnyGuildChannel, Message } from 'eris'; -import type { ApplicationCommandOption } from 'slash-commands'; -import { Inject, Subscribe } from '@augu/lilith'; -import BotlistsService from '../services/BotlistService'; -import { Logger } from 'tslog'; -import Discord from '../components/Discord'; -import Config from '../components/Config'; -import Prom from '../components/Prometheus'; - -export default class VoidListener { - @Inject - private readonly prometheus?: Prom; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly botlists?: BotlistsService; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - - @Subscribe('rawWS', { emitter: 'discord' }) - async onRawWS(packet: RawPacket) { - if (!packet.t) return; - - if (packet.t === 'INTERACTION_CREATE') { - const data = packet.d as GatewayInteractionCreateDispatchData; - if (data.type === 2) { - this.logger.info('Received slash command metadata!'); - - // skip on dm interaction - if (data.guild_id === undefined) return; - - await this._handleInteractionCreatePacket(data); - } - } - - this.prometheus?.rawWSEvents?.labels(packet.t).inc(); - } - - @Subscribe('ready', { emitter: 'discord' }) - async onReady() { - this.logger.info( - `Connected as ${this.discord.client.user.username}#${this.discord.client.user.discriminator} (ID: ${this.discord.client.user.id})` - ); - this.logger.info( - `Guilds: ${this.discord.client.guilds.size.toLocaleString()} | Users: ${this.discord.client.users.size.toLocaleString()}` - ); - - this.prometheus?.guildCount?.set(this.discord.client.guilds.size); - await this.botlists?.post(); - this.discord.mentionRegex = new RegExp(`^<@!?${this.discord.client.user.id}> `); - - const prefixes = this.config.getProperty('prefixes') ?? ['x!']; - const statusType = this.config.getProperty('status.type'); - const status = this.config.getProperty('status.status')!; - - for (const shard of this.discord.client.shards.values()) { - this.discord.client.editStatus(this.config.getProperty('status.presence') ?? 'online', { - name: status - .replace('$prefix$', prefixes[Math.floor(Math.random() * prefixes.length)]) - .replace('$guilds$', this.discord.client.guilds.size.toLocaleString()) - .replace('$shard$', `#${shard.id}`), - - type: statusType ?? 0, - }); - } - } - - private _format(command: ApplicationCommandOption): [content: string, users?: string[], roles?: string[]] { - if (!command.options) return [`/${command.name}`, undefined, undefined]; - - const roleMentions: string[] = []; - const userMentions: string[] = []; - let content = `/${command.name}`; - - const formatArg = (arg: ApplicationCommandOption & { value: any }) => { - if (arg.options !== undefined) { - const args = arg.options?.map(formatArg as any); - return `${arg.name}${args !== undefined ? ` ${args.join(' ')}` : ''}`; - } - - if (['member', 'user'].some((a) => arg.name.startsWith(a))) { - userMentions.push(arg.value); - return `<@!${arg.value}>`; - } else if (arg.name === 'channel') { - return `<@#${arg.value}>`; - } else if (arg.name === 'role') { - roleMentions.push(arg.value); - return `<@&${arg.value}>`; - } else { - return arg.value; - } - }; - - command.options?.forEach((option) => { - const choices = - option.choices?.length ?? false - ? option - .choices!.map((c: any) => (c !== undefined ? undefined : `${c.name}: ${c.value}`)) - .filter((s) => s !== undefined) - : null; - if (choices !== null) { - content += ` ${choices.join(', ')}`; - return; - } - - content += ` ${formatArg(option as any)}`; - }); - - return [content, roleMentions, userMentions]; - } - - private async _handleInteractionCreatePacket(data: GatewayInteractionCreateDispatchData) { - const { data: command, guild_id: guildID, channel_id: channelID } = data; - const guild = this.discord.client.guilds.get(guildID as string); - const channel = await this.discord.getChannel(channelID as string); - - // not cached, don't care lol - if (!guild || channel === null) return; - - this.logger.info(`Received slash command /${(command as APIApplicationCommandInteractionData).name} uwu`); - const [content, userMentions, roleMentions] = this._format(command as any); - const uncachedUsers = userMentions?.filter((s) => !this.discord.client.users.has(s)) ?? []; - - if (uncachedUsers.length > 0) { - (await Promise.all(uncachedUsers.map((s) => this.discord.client.getRESTUser(s)))).map((user) => - this.discord.client.users.update(user) - ); - } - - this.discord.client.emit( - 'messageCreate', - new Message( - { - id: null as any, - timestamp: new Date().toISOString(), - channel_id: channelID as string, - guild_id: guildID, - content, - mention_roles: roleMentions, - type: 0, - author: {}, - }, - this.discord.client - ) - ); - } -} diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 31dd2127..00000000 --- a/src/main.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import 'source-map-support/register'; -import 'reflect-metadata'; - -(require('@augu/dotenv') as typeof import('@augu/dotenv')).parse({ - populate: true, - delimiter: ',', - file: require('path').join(process.cwd(), '..', '.env'), - schema: { - NODE_ENV: { - oneOf: ['production', 'development'], - default: 'development', - type: 'string', - }, - }, -}); - -import { commitHash, version } from './util/Constants'; -import Discord from './components/Discord'; -import Sentry from './components/Sentry'; -import logger from './singletons/Logger'; -import app from './container'; -import Api from './api/API'; -import ts from 'typescript'; - -(async () => { - logger.info(`Loading Nino v${version} (${commitHash ?? ''})`); - logger.info(`-> TypeScript: ${ts.version}`); - logger.info(`-> Node.js: ${process.version}`); - if (process.env.REGION !== undefined) logger.info(`-> Region: ${process.env.REGION}`); - - try { - // call patch before container load - await import('./util/ErisPatch'); - await app.load(); - await app.addComponent(Api); - } catch (ex) { - logger.fatal('Unable to load container'); - console.error(ex); - process.exit(1); - } - - logger.info('✔ Nino has started successfully'); - process.on('SIGINT', () => { - logger.warn('Received CTRL+C call!'); - - app.dispose(); - process.exit(0); - }); -})(); - -const ReconnectCodes = [ - 1001, // Going Away (re-connect now) - 1006, // Connection reset by peer -]; - -const OtherPossibleReconnectCodes = [ - 'WebSocket was closed before the connection was established', - "Server didn't acknowledge previous heartbeat, possible lost connection", -]; - -process.on('unhandledRejection', (error) => { - const sentry = app.$ref(Sentry); - if (error !== null || error !== undefined) { - logger.fatal('Received unhandled Promise rejection:', error); - if (error instanceof Error) sentry?.report(error); - } -}); - -process.on('uncaughtException', async (error) => { - const sentry = app.$ref(Sentry); - - if ((error as any).code !== undefined) { - if (ReconnectCodes.includes((error as any).code) || OtherPossibleReconnectCodes.includes(error.message)) { - logger.fatal('Disconnected due to peer to peer connection ended, restarting client...'); - - const discord = app.$ref(Discord); - discord.client.disconnect({ reconnect: false }); - await discord.client.connect(); - } - } else { - sentry?.report(error); - logger.fatal('Uncaught exception has occured\n', error); - } -}); diff --git a/src/migrations/1617170164138-initialization.ts b/src/migrations/1617170164138-initialization.ts deleted file mode 100644 index 6d074bd7..00000000 --- a/src/migrations/1617170164138-initialization.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class initialization1617170164138 implements MigrationInterface { - name = 'initialization1617170164138'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - 'CREATE TABLE "automod" ("blacklistWords" text array NOT NULL, "blacklist" boolean NOT NULL DEFAULT false, "mentions" boolean NOT NULL DEFAULT false, "invites" boolean NOT NULL DEFAULT false, "dehoist" boolean NOT NULL DEFAULT false, "guild_id" character varying NOT NULL, "spam" boolean NOT NULL DEFAULT false, "raid" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_8592ba75741fd8ff52adde5de53" PRIMARY KEY ("guild_id"))' - ); - await queryRunner.query("CREATE TYPE \"blacklists_type_enum\" AS ENUM('0', '1')"); - await queryRunner.query( - 'CREATE TABLE "blacklists" ("reason" character varying, "issuer" character varying NOT NULL, "type" "blacklists_type_enum" NOT NULL, "id" character varying NOT NULL, CONSTRAINT "PK_69894f41b74b226aae9ea763bc2" PRIMARY KEY ("id"))' - ); - await queryRunner.query( - "CREATE TYPE \"punishments_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'CREATE TABLE "punishments" ("warnings" integer NOT NULL DEFAULT \'1\', "guild_id" character varying NOT NULL, "index" SERIAL NOT NULL, "soft" boolean NOT NULL DEFAULT false, "time" integer, "type" "punishments_type_enum" NOT NULL, CONSTRAINT "PK_b08854374ef88515861c1bf6cd8" PRIMARY KEY ("guild_id", "index"))' - ); - await queryRunner.query( - "CREATE TYPE \"cases_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'CREATE TABLE "cases" ("moderator_id" character varying NOT NULL, "message_id" character varying, "victim_id" character varying NOT NULL, "guild_id" character varying NOT NULL, "reason" character varying, "index" integer NOT NULL, "type" "cases_type_enum" NOT NULL, "soft" boolean NOT NULL DEFAULT false, "time" integer, CONSTRAINT "PK_70fc7fe12ee1488af12aaea83af" PRIMARY KEY ("guild_id", "index"))' - ); - await queryRunner.query( - 'CREATE TABLE "guilds" ("modlog_channel_id" character varying DEFAULT null, "muted_role_id" character varying DEFAULT null, "prefixes" text array NOT NULL, "language" character varying NOT NULL DEFAULT \'en_US\', "guild_id" character varying NOT NULL, CONSTRAINT "PK_e8887ee637b1f465673e957dd0a" PRIMARY KEY ("guild_id"))' - ); - await queryRunner.query( - "CREATE TYPE \"logging_events_enum\" AS ENUM('voice_channel_switch', 'voice_channel_left', 'voice_channel_join', 'message_delete', 'message_update', 'settings_update')" - ); - await queryRunner.query( - 'CREATE TABLE "logging" ("ignoreChannels" text array NOT NULL DEFAULT \'{}\'::text[], "ignoreUsers" text array NOT NULL DEFAULT \'{}\'::text[], "channel_id" character varying, "enabled" boolean NOT NULL DEFAULT false, "events" "logging_events_enum" array NOT NULL DEFAULT \'{}\', "guild_id" character varying NOT NULL, CONSTRAINT "PK_cbd7eb1495206472bb71b7a6d68" PRIMARY KEY ("guild_id"))' - ); - await queryRunner.query( - 'CREATE TABLE "users" ("language" character varying NOT NULL DEFAULT \'en_US\', "prefixes" text array NOT NULL, "user_id" character varying NOT NULL, CONSTRAINT "PK_96aac72f1574b88752e9fb00089" PRIMARY KEY ("user_id"))' - ); - await queryRunner.query( - 'CREATE TABLE "warnings" ("guild_id" character varying NOT NULL, "reason" character varying, "amount" integer NOT NULL DEFAULT \'0\', "user_id" character varying NOT NULL, CONSTRAINT "PK_7a14eba00a6aaf0dc04f76aff02" PRIMARY KEY ("user_id"))' - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('DROP TABLE "warnings"'); - await queryRunner.query('DROP TABLE "users"'); - await queryRunner.query('DROP TABLE "logging"'); - await queryRunner.query('DROP TYPE "logging_events_enum"'); - await queryRunner.query('DROP TABLE "guilds"'); - await queryRunner.query('DROP TABLE "cases"'); - await queryRunner.query('DROP TYPE "cases_type_enum"'); - await queryRunner.query('DROP TABLE "punishments"'); - await queryRunner.query('DROP TYPE "punishments_type_enum"'); - await queryRunner.query('DROP TABLE "blacklists"'); - await queryRunner.query('DROP TYPE "blacklists_type_enum"'); - await queryRunner.query('DROP TABLE "automod"'); - } -} diff --git a/src/migrations/1617402812079-firstMigration.ts b/src/migrations/1617402812079-firstMigration.ts deleted file mode 100644 index a90745f9..00000000 --- a/src/migrations/1617402812079-firstMigration.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class firstMigration1617402812079 implements MigrationInterface { - name = 'firstMigration1617402812079'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "automod" ADD "shortLinks" boolean NOT NULL DEFAULT false'); - await queryRunner.query('ALTER TYPE "logging_events_enum" RENAME TO "logging_events_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"logging_events_enum\" AS ENUM('voice_channel_switch', 'voice_channel_left', 'voice_channel_join', 'message_delete', 'message_update')" - ); - await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT'); - await queryRunner.query( - 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum"[] USING "events"::"text"::"logging_events_enum"[]' - ); - await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\''); - await queryRunner.query('DROP TYPE "logging_events_enum_old"'); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - "CREATE TYPE \"logging_events_enum_old\" AS ENUM('voice_channel_switch', 'voice_channel_left', 'voice_channel_join', 'message_delete', 'message_update', 'settings_update')" - ); - await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT'); - await queryRunner.query( - 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum_old"[] USING "events"::"text"::"logging_events_enum_old"[]' - ); - await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\''); - await queryRunner.query('DROP TYPE "logging_events_enum"'); - await queryRunner.query('ALTER TYPE "logging_events_enum_old" RENAME TO "logging_events_enum"'); - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "shortLinks"'); - } -} diff --git a/src/migrations/1618173354506-fixPrimaryColumnInWarnings.ts b/src/migrations/1618173354506-fixPrimaryColumnInWarnings.ts deleted file mode 100644 index c8d59d78..00000000 --- a/src/migrations/1618173354506-fixPrimaryColumnInWarnings.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class fixPrimaryColumnInWarnings1618173354506 implements MigrationInterface { - name = 'fixPrimaryColumnInWarnings1618173354506'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_7a14eba00a6aaf0dc04f76aff02"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a" PRIMARY KEY ("user_id", "guild_id")' - ); - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa" PRIMARY KEY ("guild_id")' - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a" PRIMARY KEY ("guild_id", "user_id")' - ); - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_7a14eba00a6aaf0dc04f76aff02" PRIMARY KEY ("user_id")' - ); - } -} diff --git a/src/migrations/1618173954276-addIdPropToWarnings.ts b/src/migrations/1618173954276-addIdPropToWarnings.ts deleted file mode 100644 index 105df105..00000000 --- a/src/migrations/1618173954276-addIdPropToWarnings.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class addIdPropToWarnings1618173954276 implements MigrationInterface { - name = 'addIdPropToWarnings1618173954276'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "warnings" ADD "id" SERIAL NOT NULL'); - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_1a1c969d7e8d8aad2231021420f" PRIMARY KEY ("guild_id", "id")' - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_1a1c969d7e8d8aad2231021420f"'); - await queryRunner.query( - 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa" PRIMARY KEY ("guild_id")' - ); - await queryRunner.query('ALTER TABLE "warnings" DROP COLUMN "id"'); - } -} diff --git a/src/migrations/1618174668865-snakeCaseColumnNames.ts b/src/migrations/1618174668865-snakeCaseColumnNames.ts deleted file mode 100644 index 7808317c..00000000 --- a/src/migrations/1618174668865-snakeCaseColumnNames.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class snakeCaseColumnNames1618174668865 implements MigrationInterface { - name = 'snakeCaseColumnNames1618174668865'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "blacklistWords"'); - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "shortLinks"'); - await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignoreChannels"'); - await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignoreUsers"'); - await queryRunner.query('ALTER TABLE "automod" ADD "blacklist_words" text array NOT NULL DEFAULT \'{}\''); - await queryRunner.query('ALTER TABLE "automod" ADD "short_links" boolean NOT NULL DEFAULT false'); - await queryRunner.query('ALTER TABLE "logging" ADD "ignore_channels" text array NOT NULL DEFAULT \'{}\''); - await queryRunner.query('ALTER TABLE "logging" ADD "ignore_users" text array NOT NULL DEFAULT \'{}\''); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignore_users"'); - await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignore_channels"'); - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "short_links"'); - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "blacklist_words"'); - await queryRunner.query('ALTER TABLE "logging" ADD "ignoreUsers" text array NOT NULL DEFAULT \'{}\''); - await queryRunner.query('ALTER TABLE "logging" ADD "ignoreChannels" text array NOT NULL DEFAULT \'{}\''); - await queryRunner.query('ALTER TABLE "automod" ADD "shortLinks" boolean NOT NULL DEFAULT false'); - await queryRunner.query('ALTER TABLE "automod" ADD "blacklistWords" text array NOT NULL'); - } -} diff --git a/src/migrations/1621720227973-addNewLogTypes.ts b/src/migrations/1621720227973-addNewLogTypes.ts deleted file mode 100644 index 20297262..00000000 --- a/src/migrations/1621720227973-addNewLogTypes.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; -import { LoggingEvents } from '../entities/LoggingEntity'; - -const NewTypes = ['voice_member_muted', 'voice_member_deafened']; - -export class addNewLogTypes1621720227973 implements MigrationInterface { - name = 'addNewLogTypes1621720227973'; - - public async up(runner: QueryRunner) { - await runner.query('ALTER TYPE "logging_events_enum" RENAME TO "logging_events_enum_old";'); - await runner.query( - `CREATE TYPE "logging_events_enum" AS ENUM(${Object.values(LoggingEvents) - .map((value) => `'${value}'`) - .join(', ')});` - ); - await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT;'); - await runner.query( - 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum"[] USING "events"::"text"::"logging_events_enum"[];' - ); - await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\';'); - await runner.query('DROP TYPE "logging_events_enum_old";'); - } - - public async down(runner: QueryRunner) { - await runner.query( - `CREATE TYPE "logging_events_enum_old" AS ENUM(${Object.values(LoggingEvents) - .filter((v) => !NewTypes.includes(v)) - .map((value) => `'${value}'`) - .join(', ')});` - ); - await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT;'); - await runner.query( - 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum_old"[] USING "events"::"text"::"logging_events_enum_old"[];' - ); - await runner.query('DROP TYPE "logging_events_enum";'); - await runner.query('ALTER TYPE "logging_events_enum_old" RENAME TO "logging_events_enum";'); - } -} diff --git a/src/migrations/1621895533962-fixCaseTimeType.ts b/src/migrations/1621895533962-fixCaseTimeType.ts deleted file mode 100644 index 4d08b244..00000000 --- a/src/migrations/1621895533962-fixCaseTimeType.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class fixCaseTimeType1621895533962 implements MigrationInterface { - name = 'fixCaseTimeType1621895533962'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "time"'); - await queryRunner.query('ALTER TABLE "cases" ADD "time" bigint'); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "time"'); - await queryRunner.query('ALTER TABLE "cases" ADD "time" integer'); - } -} diff --git a/src/migrations/1625456992070-fixPunishmentIndex.ts b/src/migrations/1625456992070-fixPunishmentIndex.ts deleted file mode 100644 index 7dff8b5e..00000000 --- a/src/migrations/1625456992070-fixPunishmentIndex.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class fixPunishmentIndex1625456992070 implements MigrationInterface { - name = 'fixPunishmentIndex1625456992070'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "punishments" ALTER COLUMN "index" DROP DEFAULT'); - await queryRunner.query('DROP SEQUENCE "punishments_index_seq"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum" RENAME TO "punishments_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"punishments_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum" USING "type"::"text"::"punishments_type_enum"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum_old"'); - await queryRunner.query('ALTER TYPE "cases_type_enum" RENAME TO "cases_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"cases_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum" USING "type"::"text"::"cases_type_enum"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum_old"'); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum_old" USING "type"::"text"::"cases_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum"'); - await queryRunner.query('ALTER TYPE "cases_type_enum_old" RENAME TO "cases_type_enum"'); - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum_old" USING "type"::"text"::"punishments_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum_old" RENAME TO "punishments_type_enum"'); - await queryRunner.query('CREATE SEQUENCE "punishments_index_seq" OWNED BY "punishments"."index"'); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "index" SET DEFAULT nextval(\'punishments_index_seq\')' - ); - } -} diff --git a/src/migrations/1625457655665-addDaysColumn.ts b/src/migrations/1625457655665-addDaysColumn.ts deleted file mode 100644 index 5a0aaab2..00000000 --- a/src/migrations/1625457655665-addDaysColumn.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class addDaysColumn1625457655665 implements MigrationInterface { - name = 'addDaysColumn1625457655665'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query('ALTER TABLE "punishments" ADD "days" integer'); - await queryRunner.query('ALTER TABLE "punishments" ALTER COLUMN "index" DROP DEFAULT'); - await queryRunner.query('DROP SEQUENCE IF EXISTS "punishments_index_seq"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum" RENAME TO "punishments_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"punishments_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum" USING "type"::"text"::"punishments_type_enum"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum_old"'); - await queryRunner.query('ALTER TYPE "cases_type_enum" RENAME TO "cases_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"cases_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum" USING "type"::"text"::"cases_type_enum"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum_old"'); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum_old" USING "type"::"text"::"cases_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum"'); - await queryRunner.query('ALTER TYPE "cases_type_enum_old" RENAME TO "cases_type_enum"'); - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum_old" USING "type"::"text"::"punishments_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum_old" RENAME TO "punishments_type_enum"'); - await queryRunner.query('CREATE SEQUENCE "punishments_index_seq" OWNED BY "punishments"."index"'); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "index" SET DEFAULT nextval(\'punishments_index_seq\')' - ); - await queryRunner.query('ALTER TABLE "punishments" DROP COLUMN "days"'); - } -} diff --git a/src/migrations/1625605609322-addWhitelistChannelsAutomod.ts b/src/migrations/1625605609322-addWhitelistChannelsAutomod.ts deleted file mode 100644 index 7b8e992b..00000000 --- a/src/migrations/1625605609322-addWhitelistChannelsAutomod.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class addWhitelistChannelsAutomod1625605609322 implements MigrationInterface { - name = 'addWhitelistChannelsAutomod1625605609322'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - 'ALTER TABLE "automod" ADD "whitelist_channels_during_raid" text array NOT NULL DEFAULT \'{}\'' - ); - await queryRunner.query('ALTER TABLE "punishments" ALTER COLUMN "index" DROP DEFAULT'); - await queryRunner.query('DROP SEQUENCE IF EXISTS "punishments_index_seq"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum" RENAME TO "punishments_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"punishments_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum" USING "type"::"text"::"punishments_type_enum"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum_old"'); - await queryRunner.query('ALTER TYPE "cases_type_enum" RENAME TO "cases_type_enum_old"'); - await queryRunner.query( - "CREATE TYPE \"cases_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum" USING "type"::"text"::"cases_type_enum"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum_old"'); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum_old" USING "type"::"text"::"cases_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "cases_type_enum"'); - await queryRunner.query('ALTER TYPE "cases_type_enum_old" RENAME TO "cases_type_enum"'); - await queryRunner.query( - "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')" - ); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum_old" USING "type"::"text"::"punishments_type_enum_old"' - ); - await queryRunner.query('DROP TYPE "punishments_type_enum"'); - await queryRunner.query('ALTER TYPE "punishments_type_enum_old" RENAME TO "punishments_type_enum"'); - await queryRunner.query('CREATE SEQUENCE "punishments_index_seq" OWNED BY "punishments"."index"'); - await queryRunner.query( - 'ALTER TABLE "punishments" ALTER COLUMN "index" SET DEFAULT nextval(\'punishments_index_seq\')' - ); - await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "whitelist_channels_during_raid"'); - } -} diff --git a/src/services/AutomodService.ts b/src/services/AutomodService.ts deleted file mode 100644 index af63c32d..00000000 --- a/src/services/AutomodService.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { ComponentOrServiceHooks, Inject, Service } from '@augu/lilith'; -import type { Member, Message, TextChannel, User } from 'eris'; -import type { Automod } from '../structures'; -import { Collection } from '@augu/collections'; -import { Logger } from 'tslog'; -import { join } from 'path'; - -@Service({ - priority: 1, - children: join(process.cwd(), 'automod'), - name: 'automod', -}) -export default class AutomodService extends Collection implements ComponentOrServiceHooks { - @Inject - private logger!: Logger; - - onChildLoad(automod: Automod) { - this.logger.info(`✔ Loaded automod ${automod.name}!`); - this.set(automod.name, automod); - } - - run(type: 'userUpdate', user: User): Promise; - run(type: 'memberNick', member: Member): Promise; - run(type: 'memberJoin', member: Member): Promise; - run(type: 'message', msg: Message): Promise; - async run(type: string, ...args: any[]) { - switch (type) { - case 'userUpdate': { - const automod = this.filter((am) => am.onUserUpdate !== undefined); - for (const am of automod) { - const res = await am.onUserUpdate!(args[0]); - if (res === true) return true; - } - - return false; - } - - case 'memberNick': { - const automod = this.filter((am) => am.onMemberNickUpdate !== undefined); - for (const am of automod) { - const res = await am.onMemberNickUpdate!(args[0]); - if (res === true) return true; - } - - return false; - } - - case 'memberJoin': { - const automod = this.filter((am) => am.onMemberJoin !== undefined); - for (const am of automod) { - const res = await am.onMemberJoin!(args[0]); - if (res === true) return true; - } - - return false; - } - - case 'message': { - const automod = this.filter((am) => am.onMessage !== undefined); - for (const am of automod) { - const res = await am.onMessage!(args[0]); - if (res === true) return true; - } - - return false; - } - - default: - return true; - } - } -} diff --git a/src/services/BotlistService.ts b/src/services/BotlistService.ts deleted file mode 100644 index 6d377955..00000000 --- a/src/services/BotlistService.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import { Service, Inject } from '@augu/lilith'; -import { HttpClient } from '@augu/orchid'; -import { Logger } from 'tslog'; -import Discord from '../components/Discord'; -import Config from '../components/Config'; - -@Service({ - priority: 1, - name: 'botlists', -}) -export default class BotlistsService { - @Inject - private readonly discord!: Discord; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - - @Inject - private readonly http!: HttpClient; - - #interval?: NodeJS.Timer; - - async load() { - const botlists = this.config.getProperty('botlists'); - if (botlists === undefined) { - this.logger.warn("`botlists` is missing, don't need to add it if running privately."); - return Promise.resolve(); - } - - this.logger.info('Built scheduler for posting to botlists!'); - this.#interval = setInterval(this.post.bind(this), 86400000).unref(); - } - - dispose() { - if (this.#interval) clearInterval(this.#interval); - } - - async post() { - const list: { - name: 'Discord Services' | 'Discord Boats' | 'Discord Bots' | 'top.gg' | 'Delly' | 'Bots for Discord'; - success: boolean; - data: Record; - }[] = []; - - let success = 0; - let errored = 0; - const botlists = this.config.getProperty('botlists')!; - - if (botlists === undefined) return; - - if (botlists.dservices !== undefined) { - this.logger.info('Found Discord Services token, now posting...'); - - await this.http - .request({ - url: `https://api.discordservices.net/bot/${this.discord.client.user.id}/stats`, - method: 'POST', - data: { - server_count: this.discord.client.guilds.size, - }, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.dservices, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'Discord Services', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [discordservices.net]:', ex)); - } - - if (botlists.dboats !== undefined) { - this.logger.info('Found Discord Boats token, now posting...'); - - await this.http - .request({ - data: { - server_count: this.discord.client.guilds.size, - }, - method: 'POST', - url: `https://discord.boats/api/bot/${this.discord.client.user.id}`, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.dboats, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'Discord Boats', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [discord.boats]:', ex)); - } - - if (botlists.dbots !== undefined) { - this.logger.info('Found Discord Bots token, now posting...'); - - await this.http - .request({ - url: `https://discord.bots.gg/api/v1/bots/${this.discord.client.user.id}/stats`, - method: 'POST', - data: { - shardCount: this.discord.client.shards.size, - guildCount: this.discord.client.guilds.size, - }, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.dbots, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'Discord Bots', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [discord.bots.gg]:', ex)); - } - - if (botlists.topgg !== undefined) { - this.logger.info('Found top.gg token, now posting...'); - - await this.http - .request({ - url: `https://top.gg/api/bots/${this.discord.client.user.id}/stats`, - method: 'POST', - data: { - server_count: this.discord.client.guilds.size, - shard_count: this.discord.client.shards.size, - }, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.topgg, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'top.gg', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [top.gg]:', ex)); - } - - // Ice is a cute boyfriend btw <3 - if (botlists.delly !== undefined) { - this.logger.info('Found Discord Extreme List token, now posting...'); - - await this.http - .request({ - url: `https://api.discordextremelist.xyz/v2/bot/${this.discord.client.user.id}/stats`, - method: 'POST', - data: { - guildCount: this.discord.client.guilds.size, - shardCount: this.discord.client.shards.size, - }, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.delly, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'Delly', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [Delly]:', ex)); - } - - if (botlists.bfd !== undefined) { - this.logger.info('Found Bots for Discord token, now posting...'); - - const res = await this.http - .request({ - method: 'POST', - url: `https://botsfordiscord.com/api/bot/${this.discord.client.user.id}`, - data: { - server_count: this.discord.client.guilds.size, - }, - headers: { - 'Content-Type': 'application/json', - 'Authorization': botlists.bfd, - }, - }) - .then((res) => { - res.statusCode === 200 ? success++ : errored++; - list.push({ - name: 'Bots for Discord', - success: res.statusCode === 200, - data: res.json(), - }); - }) - .catch((ex) => this.logger.warn('Unable to parse JSON [Bots for Discord]:', ex)); - } - - const successRate = ((success / list.length) * 100).toFixed(2); - this.logger.info( - [ - `ℹ️ listly posted to ${list.length} botlists with a success rate of ${successRate}%`, - 'Serialized output will be displayed:', - ].join('\n') - ); - - for (const botlist of list) { - this.logger.info(`${botlist.success ? '✔' : '❌'} ${botlist.name}`, botlist.data); - } - } -} diff --git a/src/services/CommandService.ts b/src/services/CommandService.ts deleted file mode 100644 index 3a2450b6..00000000 --- a/src/services/CommandService.ts +++ /dev/null @@ -1,292 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { EmbedBuilder, CommandMessage } from '../structures'; -import type { Message, TextChannel } from 'eris'; -import { Service, Inject } from '@augu/lilith'; -import LocalizationService from './LocalizationService'; -import type NinoCommand from '../structures/Command'; -import AutomodService from './AutomodService'; -import { Collection } from '@augu/collections'; -import Subcommand from '../structures/Subcommand'; -import Prometheus from '../components/Prometheus'; -import { Logger } from 'tslog'; -import Database from '../components/Database'; -import { join } from 'path'; -import Discord from '../components/Discord'; -import Config from '../components/Config'; -import Sentry from '../components/Sentry'; - -const FLAG_REGEX = /(?:--?|—)([\w]+)(=?(\w+|['"].*['"]))?/gi; - -@Service({ - priority: 1, - children: join(process.cwd(), 'commands'), - name: 'commands', -}) -export default class CommandService extends Collection { - public commandsExecuted: number = 0; - public messagesSeen: number = 0; - public cooldowns: Collection> = new Collection(); - - @Inject - private readonly config!: Config; - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly discord!: Discord; - - @Inject - private readonly database!: Database; - - @Inject - private readonly prometheus!: Prometheus; - - @Inject - private readonly automod!: AutomodService; - - @Inject - private readonly localization!: LocalizationService; - - @Inject - private readonly sentry?: Sentry; - - onChildLoad(command: NinoCommand) { - if (!command.name) { - this.logger.warn(`Unfinished command: ${command.constructor.name}`); - return; - } - - this.logger.info(`✔ Loaded command ${command.name}!`); - this.set(command.name, command); - } - - async handleCommand(msg: Message) { - this.prometheus.messagesSeen?.inc(); - this.messagesSeen++; - - if ((await this.automod.run('message', msg)) === true) return; - - if (msg.author.bot) return; - if (![0, 5].includes(msg.channel.type)) return; - - const settings = await this.database.guilds.get(msg.channel.guild.id); - const userSettings = await this.database.users.get(msg.author.id); - - const _prefixes = ([] as string[]) - .concat(settings.prefixes, userSettings.prefixes, this.config.getProperty('prefixes')!) - .filter(Boolean); - - const mentionRegex = this.discord.mentionRegex ?? new RegExp(`<@!?${this.discord.client.user.id}> `); - const mention = mentionRegex.exec(msg.content); - if (mention !== null) _prefixes.push(`${mention}`); - - // remove duplicates - if (this.discord.client.user.id === '531613242473054229') _prefixes.push('nino '); - - // Removes any duplicates - const prefixes = [...new Set(_prefixes)]; - const prefix = prefixes.find((prefix) => msg.content.startsWith(prefix)); - if (prefix === undefined) return; - - let rawArgs = msg.content.slice(prefix.length).trim().split(/ +/g); - const name = rawArgs.shift()!; - const command = this.find((command) => command.name === name || command.aliases.includes(name)); - - if (command === null) return; - - // Check for if the guild is blacklisted - const guildBlacklist = await this.database.blacklists.get(msg.guildID); - if (guildBlacklist !== undefined) { - const issuer = this.discord.client.users.get(guildBlacklist.issuer); - await msg.channel.createMessage( - [ - `:pencil2: **This guild is blacklisted by ${issuer?.username ?? 'Unknown User'}#${ - issuer?.discriminator ?? '0000' - }**`, - `> ${guildBlacklist.reason ?? '*(no reason provided)*'}`, - '', - 'If there is a issue or want to be unblacklisted, reach out to the developers here: discord.gg/ATmjFH9kMH in under #support.', - 'I will attempt to leave this guild, goodbye. :wave:', - ].join('\n') - ); - - await msg.channel.guild.leave(); - return; - } - - // Check if the user is blacklisted - const userBlacklist = await this.database.blacklists.get(msg.author.id); - if (userBlacklist !== undefined) { - const issuer = this.discord.client.users.get(userBlacklist.issuer); - return msg.channel.createMessage( - [ - `:pencil2: **You were blacklisted by ${issuer?.username ?? 'Unknown User'}#${ - issuer?.discriminator ?? '0000' - }**`, - `> ${userBlacklist.reason ?? '*(no reason provided)*'}`, - '', - 'If there is a issue or want to be unblacklisted, reach out to the developers here: discord.gg/ATmjFH9kMH in under #support.', - ].join('\n') - ); - } - - const locale = this.localization.get(settings.language, userSettings.language); - const message = new CommandMessage(msg, locale, settings, userSettings); - app.addInjections(message); - - const owners = this.config.getProperty('owners') ?? []; - if (command.ownerOnly && !owners.includes(msg.author.id)) - return message.reply(`Command **${command.name}** is a developer-only command, nice try...`); - - // Check for permissions of Nino - if (command.botPermissions.length) { - const permissions = msg.channel.permissionsOf(this.discord.client.user.id); - const missing = command.botPermissions.filter((perm) => !permissions.has(perm)); - - if (missing.length > 0) return message.reply(`I am missing the following permissions: **${missing.join(', ')}**`); - } - - // Check for the user's permissions - if (command.userPermissions.length) { - const permissions = msg.channel.permissionsOf(msg.author.id); - const missing = command.userPermissions.filter((perm) => !permissions.has(perm)); - - if (missing.length > 0 && !owners.includes(msg.author.id)) - return message.reply(`You are missing the following permission: **${missing.join(', ')}**`); - } - - // Cooldowns - if (!this.cooldowns.has(command.name)) this.cooldowns.set(command.name, new Collection()); - - const now = Date.now(); - const timestamps = this.cooldowns.get(command.name)!; - const amount = command.cooldown * 1000; - - if (!owners.includes(msg.author.id) && timestamps.has(msg.author.id)) { - const time = timestamps.get(msg.author.id)! + amount; - if (now < time) { - const left = (time - now) / 1000; - return message.reply(`Please wait **${left.toFixed()}** seconds before executing this command.`); - } - } - - timestamps.set(msg.author.id, now); - setTimeout(() => timestamps.delete(msg.author.id), amount); - - // Figure out the subcommand - let methodName = 'run'; - let subcommand: Subcommand | undefined = undefined; - for (const arg of rawArgs) { - if (command.subcommands.length > 0) { - if (command.subcommands.find((r) => r.aliases.includes(arg)) !== undefined) { - subcommand = command.subcommands.find((r) => r.aliases.includes(arg))!; - methodName = subcommand.name; - break; - } - - if (command.subcommands.find((r) => r.name === arg) !== undefined) { - subcommand = command.subcommands.find((r) => r.name === arg)!; - methodName = subcommand.name; - break; - } - } - } - - if (subcommand !== undefined) rawArgs.shift(); - - message['_flags'] = this.parseFlags(rawArgs.join(' ')); - if (command.name !== 'eval') { - rawArgs = rawArgs.filter((arg) => !FLAG_REGEX.test(arg)); - } - - if (subcommand !== undefined) { - if (subcommand.permissions !== undefined) { - const perms = msg.channel.permissionsOf(msg.author.id); - if (!perms.has(subcommand.permissions)) - return message.reply(`You are missing the **${subcommand.permissions}** permission.`); - } - } - - try { - const executor = Reflect.get(command, methodName); - if (typeof executor !== 'function') - throw new SyntaxError( - `${subcommand ? 'Subc' : 'C'}ommand "${subcommand ? methodName : command.name}" was not a function.` - ); - - this.prometheus.commandsExecuted?.labels(command.name).inc(); - this.commandsExecuted++; - await executor.call(command, message, rawArgs); - this.logger.info( - `Command "${command.name}" has been ran by ${msg.author.username}#${msg.author.discriminator} in guild ${msg.channel.guild.name} (${msg.channel.guild.id})` - ); - } catch (ex) { - const _owners = await Promise.all( - owners.map((id) => { - const user = this.discord.client.users.get(id); - if (user === undefined) return this.discord.client.getRESTUser(id); - else return Promise.resolve(user); - }) - ); - - const contact = _owners - .map((r, index) => `${index + 1 === owners.length ? 'or ' : ''}**${r.username}#${r.discriminator}**`) - .join(', '); - - const embed = new EmbedBuilder() - .setColor(0xdaa2c6) - .setDescription([ - `${ - subcommand !== undefined - ? `Subcommand **${methodName}** (parent **${command.name}**)` - : `Command **${command.name}**` - } has failed to execute.`, - `If this is a re-occuring issue, contact ${contact} at , under the <#824071651486335036> channel.`, - ]) - .build(); - - await msg.channel.createMessage({ embed }); - this.logger.error( - `${subcommand !== undefined ? `Subcommand ${methodName}` : `Command ${command.name}`} has failed to execute:`, - ex - ); - - this.sentry?.report(ex as any); - } - } - - // credit for regex: Ice <3 - private parseFlags(content: string): Record { - const record: Record = {}; - content.replaceAll(FLAG_REGEX, (_, key: string, value: string) => { - record[key.trim()] = value ? value.replaceAll(/(^[='"]+|['"]+$)/g, '').trim() : true; - return value; - }); - - // keep it immutable so - // the application doesn't mutate its state - return Object.freeze(record); - } -} diff --git a/src/services/LocalizationService.ts b/src/services/LocalizationService.ts deleted file mode 100644 index b0a2f544..00000000 --- a/src/services/LocalizationService.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Service, Inject } from '@augu/lilith'; -import { Collection } from '@augu/collections'; -import { readdir } from '@augu/utils'; -import { Logger } from 'tslog'; -import { join } from 'path'; -import Locale from '../structures/Locale'; -import Config from '../components/Config'; -import { readFileSync } from 'fs'; - -@Service({ - priority: 1, - name: 'localization', -}) -export default class LocalizationService { - public defaultLocale!: Locale; - public locales: Collection = new Collection(); - - @Inject - private readonly logger!: Logger; - - @Inject - private readonly config!: Config; - - async load() { - this.logger.info('Loading in localization files...'); - - const directory = join(process.cwd(), '..', 'locales'); - const files = await readdir(directory); - - if (!files.length) { - this.logger.fatal('Missing localization files, did you clone the wrong commit?'); - process.exit(1); - } - - for (let i = 0; i < files.length; i++) { - const contents = readFileSync(files[i], 'utf-8'); - const lang = JSON.parse>(contents); - - this.logger.info(`✔ Found language ${lang.meta.full} (${lang.meta.code}) by ${lang.meta.translator}`); - this.locales.set(lang.meta.code, new Locale(lang as { meta: LocalizationMeta; strings: LocalizationStrings })); - } - - const defaultLocale = this.config.getProperty('defaultLocale') ?? 'en_US'; - this.logger.info(`Default localization language was set to ${defaultLocale}, applying...`); - - const locale = this.locales.find((locale) => locale.code === defaultLocale); - if (locale === null) { - this.logger.fatal(`Localization "${defaultLocale}" was not found, defaulting to en_US...`); - this.defaultLocale = this.locales.get('en_US')!; - - this.logger.warn( - `Due to locale "${defaultLocale}" not being found and want to translate, read up on our translating guide:` - ); - } else { - this.logger.info(`Localization "${defaultLocale}" was found!`); - this.defaultLocale = locale; - } - } - - /** - * Gets the localization for the [CommandService], determined by the [guild] and [user]'s locale. - * @param guild The guild's localization code - * @param user The user's localization code - */ - get(guild: string, user: string) { - // this shouldn't happen but you never know - if (!this.locales.has(guild) || !this.locales.has(user)) return this.defaultLocale; - - // committing yanderedev over here - if (user === this.defaultLocale.code && guild === this.defaultLocale.code) return this.defaultLocale; - else if (user !== this.defaultLocale.code && guild === this.defaultLocale.code) return this.locales.get(user)!; - else if (guild !== this.defaultLocale.code && user === this.defaultLocale.code) return this.locales.get(guild)!; - else return this.defaultLocale; - } -} diff --git a/src/services/PunishmentService.ts b/src/services/PunishmentService.ts deleted file mode 100644 index 38e3e624..00000000 --- a/src/services/PunishmentService.ts +++ /dev/null @@ -1,719 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Constants, Guild, Member, User, VoiceChannel, TextChannel, Message, Attachment, DiscordRESTError } from 'eris'; -import { Inject, Service } from '@augu/lilith'; -import { PunishmentType } from '../entities/PunishmentsEntity'; -import { EmbedBuilder } from '../structures'; -import type GuildEntity from '../entities/GuildEntity'; -import type CaseEntity from '../entities/CaseEntity'; -import TimeoutsManager from '../components/timeouts/Timeouts'; -import Permissions from '../util/Permissions'; -import { Logger } from 'tslog'; -import Database from '../components/Database'; -import Discord from '../components/Discord'; -import ms = require('ms'); - -type MemberLike = Member | { id: string; guild: Guild }; - -export enum PunishmentEntryType { - WarningRemoved = 'Warning Removed', - WarningAdded = 'Warning Added', - VoiceUndeafen = 'Voice Undeafen', - VoiceUnmute = 'Voice Unmute', - VoiceMute = 'Voice Mute', - VoiceDeaf = 'Voice Deafen', - Unban = 'Unban', - Unmuted = 'Unmuted', - Muted = 'Muted', - Kicked = 'Kicked', - Banned = 'Banned', -} - -interface ApplyPunishmentOptions { - attachments?: Attachment[]; - moderator: User; - publish?: boolean; - reason?: string; - member: MemberLike; - soft?: boolean; - time?: number; - days?: number; - type: PunishmentType; -} - -interface PublishModLogOptions { - warningsRemoved?: number | 'all'; - warningsAdded?: number; - attachments?: string[]; - moderator: User; - channel?: VoiceChannel; - reason?: string; - victim: User; - guild: Guild; - time?: number; - type: PunishmentEntryType; -} - -interface ApplyGenericMuteOptions extends ApplyActionOptions { - moderator: User; - settings: GuildEntity; -} - -interface ApplyActionOptions { - reason?: string; - member: Member; - guild: Guild; - time?: number; - self: Member; -} - -interface ApplyGenericVoiceAction extends Exclude { - statement: PublishModLogOptions; - moderator: User; -} - -interface ApplyBanActionOptions extends ApplyActionOptions { - moderator: User; - soft: boolean; - days: number; -} - -function stringifyDBType(type: PunishmentType): PunishmentEntryType | null { - switch (type) { - case PunishmentType.VoiceUndeafen: - return PunishmentEntryType.VoiceUndeafen; - case PunishmentType.VoiceUnmute: - return PunishmentEntryType.VoiceUnmute; - case PunishmentType.VoiceDeafen: - return PunishmentEntryType.VoiceDeaf; - case PunishmentType.VoiceMute: - return PunishmentEntryType.VoiceMute; - case PunishmentType.Unmute: - return PunishmentEntryType.Unmuted; - case PunishmentType.Unban: - return PunishmentEntryType.Unban; - case PunishmentType.Mute: - return PunishmentEntryType.Muted; - case PunishmentType.Kick: - return PunishmentEntryType.Kicked; - case PunishmentType.Ban: - return PunishmentEntryType.Banned; - - default: - return null; // shouldn't come here but oh well - } -} - -const emojis: { [P in PunishmentEntryType]: string } = { - [PunishmentEntryType.WarningRemoved]: ':pencil:', - [PunishmentEntryType.VoiceUndeafen]: ':speaking_head:', - [PunishmentEntryType.WarningAdded]: ':pencil:', - [PunishmentEntryType.VoiceUnmute]: ':loudspeaker:', - [PunishmentEntryType.VoiceMute]: ':mute:', - [PunishmentEntryType.VoiceDeaf]: ':mute:', - [PunishmentEntryType.Unmuted]: ':loudspeaker:', - [PunishmentEntryType.Kicked]: ':boot:', - [PunishmentEntryType.Banned]: ':hammer:', - [PunishmentEntryType.Unban]: ':bust_in_silhouette:', - [PunishmentEntryType.Muted]: ':mute:', -}; - -@Service({ - priority: 1, - name: 'punishments', -}) -export default class PunishmentService { - @Inject - private database!: Database; - - @Inject - private discord!: Discord; - - @Inject - private logger!: Logger; - - private async resolveMember(member: MemberLike, rest: boolean = true) { - return member instanceof Member - ? member - : member.guild.members.has(member.id) - ? member.guild.members.get(member.id)! - : rest - ? await this.discord.client - .getRESTGuildMember(member.guild.id, member.id) - .catch(() => new Member({ id: member.id }, member.guild, this.discord.client)) - : new Member({ id: member.id }, member.guild, this.discord.client); - } - - get timeouts(): TimeoutsManager { - return app.$ref(TimeoutsManager); - } - - permissionsFor(type: PunishmentType) { - switch (type) { - case PunishmentType.Unmute: - case PunishmentType.Mute: - return Constants.Permissions.manageRoles; - - case PunishmentType.VoiceUndeafen: - case PunishmentType.VoiceDeafen: - return Constants.Permissions.voiceDeafenMembers; - - case PunishmentType.VoiceUnmute: - case PunishmentType.VoiceMute: - return Constants.Permissions.voiceMuteMembers; - - case PunishmentType.Unban: - case PunishmentType.Ban: - return Constants.Permissions.banMembers; - - case PunishmentType.Kick: - return Constants.Permissions.kickMembers; - - default: - return 0n; - } - } - - async createWarning(member: Member, reason?: string, amount?: number) { - const self = member.guild.members.get(this.discord.client.user.id)!; - const warnings = await this.database.warnings.getAll(member.guild.id, member.id); - const current = warnings.reduce((acc, curr) => acc + curr.amount, 0); - const count = amount !== undefined ? current + amount : current + 1; - - if (count < 0) throw new RangeError('amount out of bounds'); - - const punishments = await this.database.punishments.getAll(member.guild.id); - const results = punishments.filter((x) => x.warnings === count); - - await this.database.warnings.create({ - guildID: member.guild.id, - reason, - amount: amount ?? 1, - userID: member.id, - }); - - // run the actual punishments - for (let i = 0; i < results.length; i++) { - const result = results[i]; - await this.apply({ - moderator: this.discord.client.users.get(this.discord.client.user.id)!, - publish: false, - member, - type: result.type, - }); - } - - const model = await this.database.cases.create({ - attachments: [], - moderatorID: this.discord.client.user.id, - victimID: member.id, - guildID: member.guild.id, - reason, - soft: false, - type: PunishmentType.WarningAdded, - }); - - return results.length > 0 - ? Promise.resolve() - : this.publishToModLog( - { - warningsAdded: amount ?? 1, - moderator: self.user, - reason, - victim: member.user, - guild: member.guild, - type: PunishmentEntryType.WarningAdded, - }, - model - ); - } - - async removeWarning(member: Member, reason?: string, amount?: number | 'all') { - const self = member.guild.members.get(this.discord.client.user.id)!; - const warnings = await this.database.warnings.getAll(member.guild.id, member.id); - - if (warnings.length === 0) throw new SyntaxError("user doesn't have any punishments to be removed"); - - const count = warnings.reduce((acc, curr) => acc + curr.amount, 0); - if (amount === 'all') { - await this.database.warnings.clean(member.guild.id, member.id); - const model = await this.database.cases.create({ - attachments: [], - moderatorID: this.discord.client.user.id, - victimID: member.id, - guildID: member.guild.id, - reason, - type: PunishmentType.WarningRemoved, - }); - - return this.publishToModLog( - { - warningsRemoved: 'all', - moderator: self.user, - victim: member.user, - reason, - guild: member.guild, - type: PunishmentEntryType.WarningRemoved, - }, - model - ); - } else { - const model = await this.database.cases.create({ - attachments: [], - moderatorID: this.discord.client.user.id, - victimID: member.id, - guildID: member.guild.id, - reason, - type: PunishmentType.WarningRemoved, - }); - - await this.database.warnings.create({ - guildID: member.guild.id, - userID: member.user.id, - amount: -1, - reason, - }); - - return this.publishToModLog( - { - warningsRemoved: count, - moderator: self.user, - victim: member.user, - reason, - guild: member.guild, - type: PunishmentEntryType.WarningRemoved, - }, - model - ); - } - } - - async apply({ attachments, moderator, publish, reason, member, soft, type, days, time }: ApplyPunishmentOptions) { - this.logger.info( - `Told to apply punishment ${type} on member ${member.id}${reason ? `, with reason: ${reason}` : ''}${ - publish ? ', publishing to modlog!' : '' - }` - ); - - const settings = await this.database.guilds.get(member.guild.id); - const self = member.guild.members.get(this.discord.client.user.id)!; - - if ( - (member instanceof Member && !Permissions.isMemberAbove(self, member)) || - (BigInt(self.permissions.allow) & this.permissionsFor(type)) === 0n - ) - return; - - let user!: Member; - if (type === PunishmentType.Unban || (type === PunishmentType.Ban && member.guild.members.has(member.id))) { - user = await this.resolveMember(member, false); - } else { - user = await this.resolveMember(member, true); - } - - const modlogStatement: PublishModLogOptions = { - attachments: attachments?.map((s) => s.url) ?? [], - moderator, - reason, - victim: user.user, - guild: member.guild, - type: stringifyDBType(type)!, - time, - }; - - switch (type) { - case PunishmentType.Ban: - await this.applyBan({ - moderator, - member: user, - reason, - guild: member.guild, - self, - days: days ?? 7, - soft: soft === true, - time, - }); - break; - - case PunishmentType.Kick: - await user.kick(reason ? encodeURIComponent(reason) : 'No reason was specified.'); - break; - - case PunishmentType.Mute: - await this.applyMute({ - moderator, - settings, - member: user, - reason, - guild: member.guild, - self, - time, - }); - - break; - - case PunishmentType.Unban: - await member.guild.unbanMember(member.id, reason ? encodeURIComponent(reason) : 'No reason was specified.'); - break; - - case PunishmentType.Unmute: - await this.applyUnmute({ - moderator, - settings, - member: user, - reason, - guild: member.guild, - self, - time, - }); - - break; - - case PunishmentType.VoiceMute: - await this.applyVoiceMute({ - moderator, - statement: modlogStatement, - member: user, - reason, - guild: member.guild, - self, - time, - }); - - break; - - case PunishmentType.VoiceDeafen: - await this.applyVoiceDeafen({ - moderator, - statement: modlogStatement, - member: user, - reason, - guild: member.guild, - self, - time, - }); - - break; - - case PunishmentType.VoiceUnmute: - await this.applyVoiceUnmute({ - moderator, - statement: modlogStatement, - member: user, - reason, - guild: member.guild, - self, - }); - - break; - - case PunishmentType.VoiceUndeafen: - await this.applyVoiceUndeafen({ - moderator, - statement: modlogStatement, - member: user, - reason, - guild: member.guild, - self, - }); - - break; - } - - const model = await this.database.cases.create({ - attachments: attachments?.slice(0, 5).map((v) => v.url) ?? [], - moderatorID: moderator.id, - victimID: member.id, - guildID: member.guild.id, - reason, - soft: soft === true, - time, - type, - }); - - if (publish) { - await this.publishToModLog(modlogStatement, model); - } - } - - private async applyBan({ moderator, reason, member, guild, days, soft, time }: ApplyBanActionOptions) { - await guild.banMember(member.id, days, reason); - if (soft) await guild.unbanMember(member.id, reason); - if (!soft && time !== undefined && time > 0) { - if (this.timeouts.state !== 'connected') - this.logger.warn('Timeouts service is not connected! Will relay once done...'); - - await this.timeouts.apply({ - moderator: moderator.id, - victim: member.id, - guild: guild.id, - type: PunishmentType.Unban, - time, - }); - } - } - - private async applyUnmute({ settings, reason, member, guild }: ApplyGenericMuteOptions) { - const role = guild.roles.get(settings.mutedRoleID!)!; - if (member.roles.includes(role.id)) - await member.removeRole(role.id, reason ? encodeURIComponent(reason) : 'No reason was specified.'); - } - - private async applyMute({ moderator, settings, reason, member, guild, time }: ApplyGenericMuteOptions) { - const roleID = await this.getOrCreateMutedRole(guild, settings); - - if (reason) reason = encodeURIComponent(reason); - if (!member.roles.includes(roleID)) { - await member.addRole(roleID, reason ?? 'No reason was specified.'); - } - - if (time !== undefined && time > 0) { - if (this.timeouts.state !== 'connected') - this.logger.warn('Timeouts service is not connected! Will relay once done...'); - - await this.timeouts.apply({ - moderator: moderator.id, - victim: member.id, - guild: guild.id, - type: PunishmentType.Unmute, - time, - }); - } - } - - private async applyVoiceMute({ moderator, reason, member, guild, statement, time }: ApplyGenericVoiceAction) { - if (reason) reason = encodeURIComponent(reason); - if (member.voiceState.channelID !== null && !member.voiceState.mute) - await member.edit({ mute: true }, reason ?? 'No reason was specified.'); - - statement.channel = (await this.discord.client.getRESTChannel(member.voiceState.channelID!)) as VoiceChannel; - if (time !== undefined && time > 0) { - if (this.timeouts.state !== 'connected') - this.logger.warn('Timeouts service is not connected! Will relay once done...'); - - await this.timeouts.apply({ - moderator: moderator.id, - victim: member.id, - guild: guild.id, - type: PunishmentType.VoiceUnmute, - time, - }); - } - } - - private async applyVoiceDeafen({ moderator, reason, member, guild, statement, time }: ApplyGenericVoiceAction) { - if (reason) reason = encodeURIComponent(reason); - if (member.voiceState.channelID !== null && !member.voiceState.deaf) - await member.edit({ deaf: true }, reason ?? 'No reason was specified.'); - - statement.channel = (await this.discord.client.getRESTChannel(member.voiceState.channelID!)) as VoiceChannel; - if (time !== undefined && time > 0) { - if (this.timeouts.state !== 'connected') - this.logger.warn('Timeouts service is not connected! Will relay once done...'); - - await this.timeouts.apply({ - moderator: moderator.id, - victim: member.id, - guild: guild.id, - type: PunishmentType.VoiceUndeafen, - time, - }); - } - } - - private async applyVoiceUnmute({ reason, member, statement }: ApplyGenericVoiceAction) { - if (reason) reason = encodeURIComponent(reason); - if (member.voiceState !== undefined && member.voiceState.mute) - await member.edit({ mute: false }, reason ?? 'No reason was specified.'); - - statement.channel = (await this.discord.client.getRESTChannel(member.voiceState.channelID!)) as VoiceChannel; - } - - private async applyVoiceUndeafen({ reason, member, statement }: ApplyGenericVoiceAction) { - if (reason) reason = encodeURIComponent(reason); - if (member.voiceState !== undefined && member.voiceState.deaf) - await member.edit({ deaf: false }, reason ?? 'No reason was specified.'); - - statement.channel = (await this.discord.client.getRESTChannel(member.voiceState.channelID!)) as VoiceChannel; - } - - private async publishToModLog( - { - warningsRemoved, - warningsAdded, - moderator, - attachments, - channel, - reason, - victim, - guild, - time, - type, - }: PublishModLogOptions, - caseModel: CaseEntity - ) { - const settings = await this.database.guilds.get(guild.id); - if (!settings.modlogChannelID) return; - - const modlog = guild.channels.get(settings.modlogChannelID) as TextChannel; - if (!modlog) return; - - if ( - !modlog.permissionsOf(this.discord.client.user.id).has('sendMessages') || - !modlog.permissionsOf(this.discord.client.user.id).has('embedLinks') - ) - return; - - const embed = this.getModLogEmbed(caseModel.index, { - attachments, - warningsRemoved, - warningsAdded, - moderator, - channel, - reason, - victim, - guild, - time, - type: stringifyDBType(caseModel.type)!, - }).build(); - const content = `**[** ${emojis[type] ?? ':question:'} **~** Case #**${caseModel.index}** (${type}) ]`; - const message = await modlog.createMessage({ - embed, - content, - }); - - await this.database.cases.update(guild.id, caseModel.index, { - messageID: message.id, - }); - } - - async editModLog(model: CaseEntity, message: Message) { - const warningRemovedField = message.embeds[0].fields?.find((field) => field.name.includes('Warnings Removed')); - const warningsAddField = message.embeds[0].fields?.find((field) => field.name.includes('Warnings Added')); - - const obj: Record = {}; - if (warningsAddField !== undefined) obj.warningsAdded = Number(warningsAddField.value); - - if (warningRemovedField !== undefined) - obj.warningsRemoved = warningRemovedField.value === 'All' ? 'All' : Number(warningRemovedField.value); - - return message.edit({ - content: `**[** ${emojis[stringifyDBType(model.type)!] ?? ':question:'} ~ Case #**${model.index}** (${ - stringifyDBType(model.type) ?? '... unknown ...' - }) **]**`, - embed: this.getModLogEmbed(model.index, { - moderator: this.discord.client.users.get(model.moderatorID)!, - victim: this.discord.client.users.get(model.victimID)!, - reason: model.reason, - guild: this.discord.client.guilds.get(model.guildID)!, - time: model.time !== undefined ? Number(model.time) : undefined, - type: stringifyDBType(model.type)!, - - ...obj, - }).build(), - }); - } - - private async getOrCreateMutedRole(guild: Guild, settings: GuildEntity) { - let muteRole = settings.mutedRoleID; - if (muteRole) return muteRole; - - let role = guild.roles.find((x) => x.name.toLowerCase() === 'muted'); - if (!role) { - role = await guild.createRole( - { - mentionable: false, - permissions: 0, - hoist: false, - name: 'Muted', - }, - `[${this.discord.client.user.username}#${this.discord.client.user.discriminator}] Created "Muted" role` - ); - - muteRole = role.id; - - const topRole = Permissions.getTopRole(guild.members.get(this.discord.client.user.id)!); - if (topRole !== undefined) { - await role.editPosition(topRole.position - 1); - for (const channel of guild.channels.values()) { - const permissions = channel.permissionsOf(this.discord.client.user.id); - if (permissions.has('manageChannels')) - await channel.editPermission( - /* overwriteID */ role.id, - /* allowed */ 0, - /* denied */ Constants.Permissions.sendMessages, - /* type */ 0, - /* reason */ `[${this.discord.client.user.username}#${this.discord.client.user.discriminator}] Overrided permissions for new Muted role` - ); - } - } - } - - await this.database.guilds.update(guild.id, { mutedRoleID: role.id }); - return role.id; - } - - getModLogEmbed( - caseID: number, - { warningsRemoved, warningsAdded, attachments, moderator, channel, reason, victim, time }: PublishModLogOptions - ) { - const embed = new EmbedBuilder() - .setColor(0xdaa2c6) - .setAuthor( - `${victim.username}#${victim.discriminator} (${victim.id})`, - undefined, - victim.dynamicAvatarURL('png', 1024) - ) - .addField('• Moderator', `${moderator.username}#${moderator.discriminator} (${moderator.id})`, true); - - const _reason = - reason !== undefined - ? Array.isArray(reason) - ? reason.join(' ') - : reason - : ` - • No reason was provided. Use \`reason ${caseID} \` to update it! - `; - - const _attachments = attachments?.map((url, index) => `• [**\`Attachment #${index}\`**](${url})`).join('\n') ?? ''; - - embed.setDescription([_reason, _attachments]); - - if (warningsRemoved !== undefined) - embed.addField('• Warnings Removed', warningsRemoved === 'all' ? 'All' : warningsRemoved.toString(), true); - - if (warningsAdded !== undefined) embed.addField('• Warnings Added', warningsAdded.toString(), true); - - if (channel !== undefined) embed.addField('• Voice Channel', `${channel.name} (${channel.id})`, true); - - if (time !== undefined || time !== null) { - try { - embed.addField('• Time', ms(time!, { long: true }), true); - } catch { - // ignore since fuck you - } - } - - return embed; - } -} diff --git a/src/singletons/Logger.ts b/src/singletons/Logger.ts deleted file mode 100644 index 9d0eb1f8..00000000 --- a/src/singletons/Logger.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { hostname } from 'os'; -import { Logger } from 'tslog'; - -const logger = new Logger({ - displayFunctionName: false, - exposeErrorCodeFrame: true, - displayInstanceName: true, - dateTimePattern: '[hour:minute:second @ day/month/year]', - displayFilePath: 'hideNodeModulesOnly', - displayTypes: false, - instanceName: hostname(), - minLevel: process.env.NODE_ENV === 'production' ? 'info' : 'silly', - name: 'Nino', -}); - -export default logger; diff --git a/src/structures/Command.ts b/src/structures/Command.ts deleted file mode 100644 index 18b9d32e..00000000 --- a/src/structures/Command.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { getSubcommandsIn } from './decorators/Subcommand'; -import type CommandMessage from './CommandMessage'; -import type { Constants } from 'eris'; -import { Categories } from '../util/Constants'; -import Subcommand from './Subcommand'; - -export type PermissionField = keyof Constants['Permissions']; -interface CommandInfo { - userPermissions?: PermissionField | PermissionField[]; - botPermissions?: PermissionField | PermissionField[]; - description?: string; - ownerOnly?: boolean; - examples?: string[]; - category?: Categories; - cooldown?: number; - aliases?: string[]; - hidden?: boolean; - usage?: string; - name: string; -} - -export default abstract class NinoCommand { - public userPermissions: PermissionField[]; - public botPermissions: PermissionField[]; - public description: ObjectKeysWithSeperator; - public ownerOnly: boolean; - public examples: string[]; - public category: Categories; - public cooldown: number; - public aliases: string[]; - public hidden: boolean; - public usage: string; - public name: string; - - constructor(info: CommandInfo) { - this.userPermissions = - typeof info.userPermissions === 'string' - ? [info.userPermissions] - : Array.isArray(info.userPermissions) - ? info.userPermissions - : []; - - this.botPermissions = - typeof info.botPermissions === 'string' - ? [info.botPermissions] - : Array.isArray(info.botPermissions) - ? info.botPermissions - : []; - - this.description = - (info.description as unknown as ObjectKeysWithSeperator) ?? 'descriptions.unknown'; - this.ownerOnly = info.ownerOnly ?? false; - this.examples = info.examples ?? []; - this.category = info.category ?? Categories.Core; - this.cooldown = info.cooldown ?? 5; - this.aliases = info.aliases ?? []; - this.hidden = info.hidden ?? false; - this.usage = info.usage ?? ''; - this.name = info.name; - } - - get subcommands() { - return getSubcommandsIn(this).map((sub) => new Subcommand(sub)); - } - - get format() { - const subcommands = this.subcommands.map((sub) => `[${sub.name} ${sub.usage.trim()}]`.trim()).join(' | '); - return `${this.name}${this.usage !== '' ? ` ${this.usage.trim()}` : ''} ${subcommands}`; - } - - abstract run(msg: CommandMessage, ...args: any[]): any; -} diff --git a/src/structures/CommandMessage.ts b/src/structures/CommandMessage.ts deleted file mode 100644 index 333f8e41..00000000 --- a/src/structures/CommandMessage.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { AdvancedMessageContent, Message, TextChannel } from 'eris'; -import type GuildEntity from '../entities/GuildEntity'; -import { EmbedBuilder } from '.'; -import type UserEntity from '../entities/UserEntity'; -import type Locale from './Locale'; -import { Inject } from '@augu/lilith'; -import Discord from '../components/Discord'; - -export default class CommandMessage { - public userSettings: UserEntity; - public settings: GuildEntity; - private _flags: any = {}; - public locale: Locale; - #message: Message; - - @Inject - private discord!: Discord; - - constructor(message: Message, locale: Locale, settings: GuildEntity, userSettings: UserEntity) { - this.userSettings = userSettings; - this.settings = settings; - this.#message = message; - this.locale = locale; - } - - get attachments() { - return this.#message.attachments; - } - - get channel() { - return this.#message.channel; - } - - get author() { - return this.#message.author; - } - - get member() { - return this.#message.member; - } - - get guild() { - return this.#message.channel.guild; - } - - get self() { - return this.guild.members.get(this.discord.client.user.id); - } - - get successEmote() { - return this.discord.emojis.find((e) => e === '<:success:464708611260678145>') ?? ':black_check_mark:'; - } - - get errorEmote() { - return this.discord.emojis.find((e) => e === '<:xmark:464708589123141634>') ?? ':x:'; - } - - flags(): T { - return this._flags; - } - - translate>(key: K, args?: any[] | Record) { - return this.reply(this.locale.translate(key, args)); - } - - reply(content: string | EmbedBuilder, allowReply: boolean = true) { - const payload: AdvancedMessageContent = { - allowedMentions: { - repliedUser: false, - everyone: false, - roles: false, - users: false, - }, - }; - - if (allowReply) payload.messageReference = { messageID: this.#message.id }; - - if (typeof content === 'string') { - payload.content = content; - return this.channel.createMessage(payload); - } else { - if (this.guild) { - if (this.self?.permissions.has('embedLinks')) - return this.channel.createMessage({ - embed: content.build(), - ...payload, - }); - // TODO: unembedify util - else - return this.channel.createMessage({ - content: content.description!, - ...payload, - }); - } else { - return this.channel.createMessage({ - embed: content.build(), - ...payload, - }); - } - } - } - - success(content: string) { - return this.reply(`${this.successEmote} ${content}`); - } - - error(content: string) { - return this.reply(`${this.errorEmote} ${content}`); - } -} diff --git a/src/structures/EmbedBuilder.ts b/src/structures/EmbedBuilder.ts deleted file mode 100644 index 206e6a4a..00000000 --- a/src/structures/EmbedBuilder.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* eslint-disable camelcase */ - -import type { - APIEmbedAuthor, - APIEmbedField, - APIEmbedFooter, - APIEmbedImage, - APIEmbedThumbnail, -} from 'discord-api-types'; -import { omitUndefinedOrNull } from '@augu/utils'; -import type { EmbedOptions } from 'eris'; -import { Color } from '../util/Constants'; - -export default class EmbedBuilder { - public description?: string; - public timestamp?: string | Date; - public thumbnail?: APIEmbedThumbnail; - public author?: APIEmbedAuthor; - public footer?: APIEmbedFooter; - public fields?: APIEmbedField[]; - public image?: APIEmbedImage; - public color?: number; - public title?: string; - public url?: string; - - constructor(data: EmbedOptions = {}) { - this.patch(data); - } - - patch(data: EmbedOptions) { - if (data.description !== undefined) this.description = data.description; - - if (data.thumbnail !== undefined) this.thumbnail = data.thumbnail; - - if (data.timestamp !== undefined) this.timestamp = data.timestamp; - - if (data.author !== undefined) this.author = data.author; - - if (data.fields !== undefined) this.fields = data.fields; - - if (data.image !== undefined) this.image = data.image; - - if (data.color !== undefined) this.color = data.color; - - if (data.title !== undefined) this.title = data.title; - - if (data.url !== undefined) this.url = data.url; - } - - setDescription(description: string | string[]) { - this.description = Array.isArray(description) ? description.join('\n') : description; - return this; - } - - setTimestamp(stamp: Date | number = new Date()) { - let timestamp!: number; - - if (stamp instanceof Date) timestamp = stamp.getTime(); - else if (typeof stamp === 'number') timestamp = stamp; - - this.timestamp = String(timestamp); - return this; - } - - setThumbnail(thumb: string) { - this.thumbnail = { url: thumb }; - return this; - } - - setAuthor(name: string, url?: string, iconUrl?: string | null) { - this.author = { name, url, icon_url: iconUrl === null ? undefined : iconUrl }; - return this; - } - - addField(name: string, value: string, inline: boolean = false) { - if (this.fields === undefined) this.fields = []; - if (this.fields.length > 25) throw new RangeError('Maximum amount of fields reached.'); - - this.fields.push({ name, value, inline }); - return this; - } - - addBlankField(inline: boolean = false) { - return this.addField('\u200b', '\u200b', inline); - } - - addFields(fields: APIEmbedField[]) { - for (let i = 0; i < fields.length; i++) this.addField(fields[i].name, fields[i].value, fields[i].inline); - - return this; - } - - setColor(color: string | number | [r: number, g: number, b: number] | 'random' | 'default') { - if (typeof color === 'number') { - this.color = color; - return this; - } - - if (typeof color === 'string') { - if (color === 'default') { - this.color = 0; - return this; - } - - if (color === 'random') { - this.color = Math.floor(Math.random() * (0xffffff + 1)); - return this; - } - - const int = parseInt(color.replace('#', ''), 16); - - this.color = (int << 16) + (int << 8) + int; - return this; - } - - if (Array.isArray(color)) { - if (color.length > 2) throw new RangeError('RGB value cannot exceed to 3 or more elements'); - - const [r, g, b] = color; - this.color = (r << 16) + (g << 8) + b; - - return this; - } - - throw new TypeError( - `'color' argument was not a hexadecimal, number, RGB value, 'random', or 'default' (${typeof color})` - ); - } - - setTitle(title: string) { - this.title = title; - return this; - } - - setURL(url: string) { - this.url = url; - return this; - } - - setImage(url: string) { - this.image = { url }; - return this; - } - - setFooter(text: string, iconUrl?: string) { - this.footer = { text, icon_url: iconUrl }; - return this; - } - - static create() { - return new EmbedBuilder().setColor(Color); - } - - build() { - return omitUndefinedOrNull({ - description: this.description, - thumbnail: this.thumbnail, - timestamp: this.timestamp, - footer: this.footer, - author: this.author - ? { - name: this.author.name!, - url: this.author.url, - icon_url: this.author.icon_url, - } - : undefined, - fields: this.fields, - image: this.image, - color: this.color, - title: this.title, - url: this.url, - }); - } -} diff --git a/src/structures/Locale.ts b/src/structures/Locale.ts deleted file mode 100644 index b3c90a25..00000000 --- a/src/structures/Locale.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { isObject } from '@augu/utils'; - -const NOT_FOUND_SYMBOL = Symbol.for('$nino::localization::not_found'); - -interface Localization { - meta: LocalizationMeta; - strings: LocalizationStrings; -} - -const KEY_REGEX = /[$]\{([\w\.]+)\}/g; - -export default class Locale { - public contributors: string[]; - public translator: string; - public aliases: string[]; - public flag: string; - public full: string; - public code: string; - - private strings: LocalizationStrings; - - constructor({ meta, strings }: Localization) { - this.contributors = meta.contributors; - this.translator = meta.translator; - this.strings = strings; - this.aliases = meta.aliases; - this.flag = meta.flag; - this.full = meta.full; - this.code = meta.code; - } - - translate, R = KeyToPropType>( - key: K, - args?: { [x: string]: any } | any[] - ): R extends string[] ? string : string { - const nodes = key.split('.'); - let value: any = this.strings; - - for (const node of nodes) { - try { - value = value[node]; - } catch (ex) { - if (ex.message.includes('of undefined')) value = NOT_FOUND_SYMBOL; - - break; - } - } - - if (value === undefined || value === NOT_FOUND_SYMBOL) throw new TypeError(`Node '${key}' doesn't exist...`); - - if (isObject(value)) throw new TypeError(`Node '${key}' is a object!`); - - if (Array.isArray(value)) { - return value.map((val) => this.stringify(val, args)).join('\n') as unknown as any; - } else { - return this.stringify(value, args); - } - } - - private stringify(value: any, rawArgs?: { [x: string]: any } | (string | number)[]) { - // If no arguments are provided, best to assume to return the string - if (!rawArgs) return value; - - // Convert it to a string - if (typeof value !== 'string') value = String(value); - - let _i = 0; - if (Array.isArray(rawArgs)) { - const matches = /%s|%d/g.exec(value); - if (matches === null) return value; - - for (let i = 0; i < matches.length; i++) { - const match = matches[i]; - if (match === '%s') { - _i++; - return value.replace(/%s/g, () => String(rawArgs.shift())); - } else if (match === '%d') { - if (isNaN(Number(rawArgs[_i]))) throw new TypeError(`Value "${rawArgs[_i]}" was not a number (index: ${_i})`); - - _i++; - return value.replace(/%d/g, () => String(rawArgs.shift())); - } - } - } else { - return (value as string).replace(KEY_REGEX, (_, key) => { - const value = String(rawArgs[key]); - return value === '' ? '?' : value || '?'; - }); - } - } -} diff --git a/src/structures/Subcommand.ts b/src/structures/Subcommand.ts deleted file mode 100644 index 5704495d..00000000 --- a/src/structures/Subcommand.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type CommandMessage from './CommandMessage'; -import type { Constants } from 'eris'; -import type Command from './Command'; - -export interface SubcommandInfo { - run(this: Command, msg: CommandMessage): Promise; - - permissions?: keyof Constants['Permissions']; - methodName: string; - aliases?: string[]; - usage: string; -} - -export default class Subcommand { - public permissions?: keyof Constants['Permissions']; - public aliases: string[]; - public usage: string; - public name: string; - public run: (this: Command, msg: CommandMessage) => Promise; - - constructor(info: SubcommandInfo) { - this.permissions = info.permissions; - this.aliases = info.aliases ?? []; - this.usage = info.usage; - this.name = info.methodName; - this.run = info.run; - } -} diff --git a/src/structures/decorators/Subcommand.ts b/src/structures/decorators/Subcommand.ts deleted file mode 100644 index f0bbf171..00000000 --- a/src/structures/decorators/Subcommand.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import type { SubcommandInfo } from '../Subcommand'; -import { MetadataKeys } from '../../util/Constants'; - -export const getSubcommandsIn = (target: any) => - Reflect.getMetadata(MetadataKeys.Subcommand, target) ?? []; - -interface SubcommandDecoratorOptions { - aliases?: string[]; - permissions?: SubcommandInfo['permissions']; -} - -export default function Subcommand( - usage?: string, - aliasesOrOptions?: SubcommandDecoratorOptions | string[] -): MethodDecorator { - return (target, methodName, descriptor: TypedPropertyDescriptor) => { - const subcommands = getSubcommandsIn(target); - - subcommands.push({ - permissions: Array.isArray(aliasesOrOptions) ? undefined : aliasesOrOptions?.permissions, - aliases: Array.isArray(aliasesOrOptions) ? aliasesOrOptions : aliasesOrOptions?.aliases, - methodName: String(methodName), - usage: usage ?? '', - run: descriptor.value!, - }); - - Reflect.defineMetadata(MetadataKeys.Subcommand, subcommands, target); - }; -} diff --git a/src/util/Constants.ts b/src/util/Constants.ts deleted file mode 100644 index 7a54e4ba..00000000 --- a/src/util/Constants.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { readFileSync } from 'fs'; -import { execSync } from 'child_process'; -import { join } from 'path'; - -/** - * Returns the current version of Nino - */ -export const version: string = require('../../package.json').version; - -/** - * Returns the commit hash of the bot. - */ -export const commitHash: string | null = (() => { - try { - const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); - return hash.slice(0, 8); - } catch { - return null; - } -})(); - -export const SHORT_LINKS = JSON.parse( - readFileSync(join(process.cwd(), '..', 'assets', 'shortlinks.json'), 'utf8').split(/\n\r?/).join('\n') -); -export const Color = 0xdaa2c6; - -export const USERNAME_DISCRIM_REGEX = /^(.+)#(\d{4})$/; -export const DISCORD_INVITE_REGEX = - /(http(s)?:\/\/(www.)?)?(discord.gg|discord.io|discord.me|discord.link|invite.gg)\/\w+/; -export const USER_MENTION_REGEX = /^<@!?([0-9]+)>$/; -export const CHANNEL_REGEX = /<#([0-9]+)>$/; -export const QUOTE_REGEX = /['"]/; -export const ROLE_REGEX = /^<@&([0-9]+)>$/; -export const ID_REGEX = /^\d+$/; - -/** - * List of categories available to commands - */ -export enum Categories { - Moderation = 'moderation', - ThreadMod = 'thread moderation', - VoiceMod = 'voice moderation', - Settings = 'settings', - Owner = 'owner', - Core = 'core', -} - -/** - * List of metadata keys for decorators - */ -export const enum MetadataKeys { - Subcommand = '$nino::subcommands', - Subscribe = '$nino::subscriptions', - APIRoute = '$nino::api-route', -} diff --git a/src/util/ErisPatch.ts b/src/util/ErisPatch.ts deleted file mode 100644 index 7ddba81b..00000000 --- a/src/util/ErisPatch.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Logger } from 'tslog'; -import { User } from 'eris'; - -const logger = app.$ref(Logger); -logger.info('monkeypatching eris...'); - -Object.defineProperty(User.prototype, 'tag', { - get(this: User) { - return `${this.username}#${this.discriminator}`; - }, - - set: () => { - throw new TypeError('cannot set user tags :('); - }, -}); - -logger.info('Monkey patched the following items:', ['User#tag'].join('\n')); diff --git a/src/util/Permissions.ts b/src/util/Permissions.ts deleted file mode 100644 index 245f947b..00000000 --- a/src/util/Permissions.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Constants, Role, Member, Permission } from 'eris'; - -/** - * Contains utility functions to help with permission checking and hierarchy. - */ -export default class Permissions { - /** - * Returns the highest role the member has, `undefined` if none was found. - * @param member The member to check - */ - static getTopRole(member: Member) { - // eris why - if (member === undefined || member === null) return; - - // For some reason, `roles` will become undefined? So we have to check for that. - // It could be a bug in Discord or `member` is undefined. - if (member.roles === undefined) return; - - if (member.roles.length === 0) return; - - return member.roles - .map((roleID) => member.guild.roles.get(roleID)) - .filter((role) => role !== undefined) - .sort((a, b) => b!.position - a!.position)[0]; - } - - /** - * Checks if role A is above role B in hierarchy (vice-versa) - * @param a The role that should be higher - * @param b The role that should be lower - */ - static isRoleAbove(a?: Role, b?: Role) { - if (!a) return false; - if (!b) return true; - - return a.position > b.position; - } - - /** - * Checks if member A is above member B in hierarchy (vice-versa) - * @param a The member that should be higher - * @param b The member that should be lower - */ - static isMemberAbove(a: Member, b: Member) { - const topRoleA = this.getTopRole(a); - const topRoleB = this.getTopRole(b); - - return this.isRoleAbove(topRoleA, topRoleB); - } - - /** - * Shows a string representation of all of the permissions - * @param bits The permission bitfield - */ - static stringify(permission: bigint) { - const permissions = new Permission(Number(permission), 0).json; - const names: string[] = []; - - for (const key of Object.keys(Constants.Permissions)) { - if (permissions.hasOwnProperty(key)) names.push(key); - } - - return names.join(', '); - } - - /** - * Returns if the user's bitfield reaches the threshold of the [required] bitfield. - * @param user The user permission bitfield - * @param required The required permission bitfield - */ - static hasOverlap(user: number, required: number) { - return (user & 8) !== 0 || (user & required) === required; - } -} diff --git a/src/util/index.ts b/src/util/index.ts deleted file mode 100644 index e6e89cf4..00000000 --- a/src/util/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2019-2021 Nino - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { QUOTE_REGEX } from './Constants'; - -/** - * Iterator function to provide a tuple of `[index, item]` in a Array. - * @param arr The array to run this iterator function - * @example - * ```ts - * const arr = ['str', 'uwu', 'owo']; - * for (const [index, item] of withIndex(arr)) { - * console.log(`${index}: ${item}`); - * // prints out: - * // 0: str - * // 1: uwu - * // 2: owo - * } - * ``` - */ -export function* withIndex(arr: T): Generator<[index: number, item: T[any]]> { - for (let i = 0; i < arr.length; i++) { - yield [i, arr[i]]; - } -} - -export function formatSize(bytes: number) { - const kilo = bytes / 1024; - const mega = kilo / 1024; - const giga = mega / 1024; - - if (kilo < 1024) return `${kilo.toFixed(1)}KB`; - else if (kilo > 1024 && mega < 1024) return `${mega.toFixed(1)}MB`; - else return `${giga.toFixed(1)}GB`; -} - -// credit: Ice (https://github.com/IceeMC) -export function getQuotedStrings(content: string) { - const parsed: string[] = []; - let curr = ''; - let opened = false; - for (let i = 0; i < content.length; i++) { - const char = content[i]; - if (char === ' ' && !opened) { - opened = false; - if (curr.length > 0) parsed.push(curr); - - curr = ''; - } - - if (QUOTE_REGEX.test(char)) { - if (opened) { - opened = false; - if (curr.length > 0) parsed.push(curr); - - curr = ''; - continue; - } - - opened = true; - continue; - } - - if (!opened && char === ' ') continue; - curr += char; - } - - if (curr.length > 0) parsed.push(curr); - return parsed; -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 56797ba7..00000000 --- a/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "@augu/tsconfig", - "compilerOptions": { - "moduleResolution": "node", - "typeRoots": ["./src/@types", "./node_modules/@types"], - "rootDir": "./src", - "types": ["node", "reflect-metadata"], - "outDir": "./build", - "skipLibCheck": true - }, - "exclude": ["node_modules"], - "include": ["src/**/*.ts"] -} diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 33771a6a..00000000 --- a/yarn.lock +++ /dev/null @@ -1,2695 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@augu/collections@1.0.12": - version "1.0.12" - resolved "https://registry.npmjs.org/@augu/collections/-/collections-1.0.12.tgz#6682bdafbf30ad1071f8335ddcfcfd5fc0517202" - integrity sha512-bs+NT4q2t6krJZt6CWn8OCFLGD4fidDnGCTYmnbD5atmOTU2pBUKYqg0QsdZK2vk7+B+mL1ydTj+2Wc7eV43BQ== - -"@augu/collections@1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@augu/collections/-/collections-1.0.3.tgz#435b9880777715fa33f0a9c4d8236d8dcb37d51b" - integrity sha512-/1rC3744iImEBMn84VK1Hk73PMcRMBT7UcNHCsxPFJa3Pwr2klmZd3bk+X3gT6bbeS21l51IARH/+jFp2i9W9Q== - -"@augu/collections@1.0.8": - version "1.0.8" - resolved "https://registry.npmjs.org/@augu/collections/-/collections-1.0.8.tgz#773f4bad2ed4000f007c05bbb98c431e2e01693d" - integrity sha512-N/cYv0ZdL5uyU2sx+HugleHIYN0WEDQUD/buFGgvC39lMUl0/V909h514EleJaFTLe2MG7Jrl6sVjhpQkbzldA== - -"@augu/dotenv@1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@augu/dotenv/-/dotenv-1.3.0.tgz#b9c62df088b4d5b14af54e4c33774983ce1b9fba" - integrity sha512-TSp5nIyyrmsZLRu+aeciDk4ko8lfEFOu7AYnnubS2EOs8GdK378Q38I1nkti6b9goVN5Iz/YC2P2jgPXOOqpbg== - dependencies: - "@augu/collections" "1.0.3" - -"@augu/eslint-config@2.2.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@augu/eslint-config/-/eslint-config-2.2.0.tgz#fb69f47dd11c7640408eb5e55b358710b20ef37e" - integrity sha512-VyrUTNdog2RdSynUddKNasfvONpJLFDJHYEQP7GKOzrlzLOEJrXE2x5cy6I6GFMiZbYoh/QEpcPv+fbTv9M2mA== - optionalDependencies: - "@typescript-eslint/eslint-plugin" "4.29.0" - "@typescript-eslint/parser" "4.29.0" - -"@augu/lilith@5.0.4": - version "5.0.4" - resolved "https://registry.npmjs.org/@augu/lilith/-/lilith-5.0.4.tgz#e629e1cbdff6eb440b92343413d250169170c99e" - integrity sha512-hDIuW6CL3UMuu7Qo+cGb3CQimnt0dClir9NR2XyDXP5HhNrp0eHPh70YxkaIH74WZW70XVrEBrPO0jDKivE9rg== - dependencies: - "@augu/collections" "1.0.12" - "@augu/utils" "1.5.3" - reflect-metadata "0.1.13" - -"@augu/orchid@3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@augu/orchid/-/orchid-3.1.1.tgz#0de1ee098cba3d64ce6903f8114d20dd35adbc12" - integrity sha512-1eYeun7FPd+3MegfFG59gRGitMWKlBr0JojNhAjx5R+at+uQKWH+JR91D9Bocw9jn06vZMTnFd6I9eG/XqhxIw== - dependencies: - "@augu/collections" "1.0.8" - "@augu/utils" "1.5.3" - form-data "4.0.0" - undici "3.3.6" - -"@augu/prettier-config@1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@augu/prettier-config/-/prettier-config-1.0.2.tgz#60d6bccff6d4d1902a9fb281fdaaa91e3e09d01a" - integrity sha512-hKFLHGisXNHAuv3F6tUKE2XJln9TA2Pn/w3KTcCwiIxMWkQFHbpfV8/3tcuWIke0V/pogU+muvTJPXlzdqIwgw== - -"@augu/tsconfig@1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@augu/tsconfig/-/tsconfig-1.1.1.tgz#1c3a80f0734749a63f85ebf5b22889de9ab2e976" - integrity sha512-qTqAK8+kTefw3PTixTFUHYATvl5inkFKnz3ByaYXO6P0prq5csA2T4weyVSWzR7dKL7rto9kHXnnN/8bTuPTKg== - -"@augu/utils@1.5.3": - version "1.5.3" - resolved "https://registry.npmjs.org/@augu/utils/-/utils-1.5.3.tgz#eb82352e2690b0467ef690885e6496f0e7a12da9" - integrity sha512-Pyu+JoK7f7AUjZvffCjf6ZLEpwa09ig7WNOn/ozzZDGeWNad9PMj8EsYzVsKLRyP/SnvbLDvpRWGEObgcACtYg== - -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== - -"@babel/highlight@^7.10.4": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" - integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== - dependencies: - ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" - import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - strip-json-comments "^3.1.1" - -"@fastify/ajv-compiler@^1.0.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-1.1.0.tgz#5ce80b1fc8bebffc8c5ba428d5e392d0f9ed10a1" - integrity sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg== - dependencies: - ajv "^6.12.6" - -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== - dependencies: - "@humanwhocodes/object-schema" "^1.2.0" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@sentry/core@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/core/-/core-6.10.0.tgz#70af9dc72bb6a5b59062a31b7de023f7f1878357" - integrity sha512-5KlxHJlbD7AMo+b9pMGkjxUOfMILtsqCtGgI7DMvZNfEkdohO8QgUY+hPqr540kmwArFS91ipQYWhqzGaOhM3Q== - dependencies: - "@sentry/hub" "6.10.0" - "@sentry/minimal" "6.10.0" - "@sentry/types" "6.10.0" - "@sentry/utils" "6.10.0" - tslib "^1.9.3" - -"@sentry/hub@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/hub/-/hub-6.10.0.tgz#d59be18016426fd3a5e8d38712c2080466aafe3c" - integrity sha512-MV8wjhWiFAXZAhmj7Ef5QdBr2IF93u8xXiIo2J+dRZ7eVa4/ZszoUiDbhUcl/TPxczaw4oW2a6tINBNFLzXiig== - dependencies: - "@sentry/types" "6.10.0" - "@sentry/utils" "6.10.0" - tslib "^1.9.3" - -"@sentry/minimal@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.10.0.tgz#9404b93fae649b6c48e1da8f0991b87cf9999561" - integrity sha512-yarm046UgUFIBoxqnBan2+BEgaO9KZCrLzsIsmALiQvpfW92K1lHurSawl5W6SR7wCYBnNn7CPvPE/BHFdy4YA== - dependencies: - "@sentry/hub" "6.10.0" - "@sentry/types" "6.10.0" - tslib "^1.9.3" - -"@sentry/node@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/node/-/node-6.10.0.tgz#d91a6877f3447d7349a7f343a1fcadc319002c09" - integrity sha512-buGmOjsTnxebHSfa3r/rhpjDk8xmrILG4xslTgV1C2JpbUtf96QnYNNydfsfAGcZrLWO0gid/wigxsx1fdXT8A== - dependencies: - "@sentry/core" "6.10.0" - "@sentry/hub" "6.10.0" - "@sentry/tracing" "6.10.0" - "@sentry/types" "6.10.0" - "@sentry/utils" "6.10.0" - cookie "^0.4.1" - https-proxy-agent "^5.0.0" - lru_map "^0.3.3" - tslib "^1.9.3" - -"@sentry/tracing@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.10.0.tgz#8dcdc28cccfad976540a3c801acb6914b9c0802e" - integrity sha512-jZj6Aaf8kU5wgyNXbAJHosHn8OOFdK14lgwYPb/AIDsY35g9a9ncTOqIOBp8X3KkmSR8lcBzAEyiUzCxAis2jA== - dependencies: - "@sentry/hub" "6.10.0" - "@sentry/minimal" "6.10.0" - "@sentry/types" "6.10.0" - "@sentry/utils" "6.10.0" - tslib "^1.9.3" - -"@sentry/types@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" - integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== - -"@sentry/utils@6.10.0": - version "6.10.0" - resolved "https://registry.npmjs.org/@sentry/utils/-/utils-6.10.0.tgz#839a099fa0a1f0ca0893c7ce8c55ba0608c1d80f" - integrity sha512-F9OczOcZMFtazYVZ6LfRIe65/eOfQbiAedIKS0li4npuMz0jKYRbxrjd/U7oLiNQkPAp4/BujU4m1ZIwq6a+tg== - dependencies: - "@sentry/types" "6.10.0" - tslib "^1.9.3" - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@sqltools/formatter@1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz#9390a8127c0dcba61ebd7fdcc748655e191bdd68" - integrity sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.1": - version "1.0.2" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - -"@types/ioredis@4.26.6": - version "4.26.6" - resolved "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.26.6.tgz#7e332d6d24f12d79a1099834ccfa0c169ef667ed" - integrity sha512-Q9ydXL/5Mot751i7WLCm9OGTj5jlW3XBdkdEW21SkXZ8Y03srbkluFGbM3q8c+vzPW30JOLJ+NsZWHoly0+13A== - dependencies: - "@types/node" "*" - -"@types/js-yaml@4.0.2": - version "4.0.2" - resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.2.tgz#4117a7a378593a218e9d6f0ef44ce6d5d9edf7fa" - integrity sha512-KbeHS/Y4R+k+5sWXEYzAZKuB1yQlZtEghuhRxrVRLaqhtoG5+26JwQsa4HyS3AWX8v1Uwukma5HheduUDskasA== - -"@types/json-schema@^7.0.7": - version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/luxon@1.27.1": - version "1.27.1" - resolved "https://registry.npmjs.org/@types/luxon/-/luxon-1.27.1.tgz#aceeb2d5be8fccf541237e184e37ecff5faa9096" - integrity sha512-cPiXpOvPFDr2edMnOXlz3UBDApwUfR+cpizvxCy0n3vp9bz/qe8BWzHPIEFcy+ogUOyjKuCISgyq77ELZPmkkg== - -"@types/ms@0.7.31": - version "0.7.31" - resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" - integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== - -"@types/node@*": - version "17.0.23" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== - -"@types/node@15.3.1": - version "15.3.1" - resolved "https://registry.npmjs.org/@types/node/-/node-15.3.1.tgz#23a06b87eedb524016616e886b116b8fdcb180af" - integrity sha512-weaeiP4UF4XgF++3rpQhpIJWsCTS4QJw5gvBhQu6cFIxTwyxWIe3xbnrY/o2lTCQ0lsdb8YIUDUvLR4Vuz5rbw== - -"@types/ws@7.4.7": - version "7.4.7" - resolved "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== - dependencies: - "@types/node" "*" - -"@typescript-eslint/eslint-plugin@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.0.tgz#b866c9cd193bfaba5e89bade0015629ebeb27996" - integrity sha512-eiREtqWRZ8aVJcNru7cT/AMVnYd9a2UHsfZT8MR1dW3UUEg6jDv9EQ9Cq4CUPZesyQ58YUpoAADGv71jY8RwgA== - dependencies: - "@typescript-eslint/experimental-utils" "4.29.0" - "@typescript-eslint/scope-manager" "4.29.0" - debug "^4.3.1" - functional-red-black-tree "^1.0.1" - regexpp "^3.1.0" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/experimental-utils@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.0.tgz#19b1417602d0e1ef325b3312ee95f61220542df5" - integrity sha512-FpNVKykfeaIxlArLUP/yQfv/5/3rhl1ov6RWgud4OgbqWLkEq7lqgQU9iiavZRzpzCRQV4XddyFz3wFXdkiX9w== - dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.29.0" - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/typescript-estree" "4.29.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.0.tgz#e5367ca3c63636bb5d8e0748fcbab7a4f4a04289" - integrity sha512-+92YRNHFdXgq+GhWQPT2bmjX09X7EH36JfgN2/4wmhtwV/HPxozpCNst8jrWcngLtEVd/4zAwA6BKojAlf+YqA== - dependencies: - "@typescript-eslint/scope-manager" "4.29.0" - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/typescript-estree" "4.29.0" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.0.tgz#cf5474f87321bedf416ef65839b693bddd838599" - integrity sha512-HPq7XAaDMM3DpmuijxLV9Io8/6pQnliiXMQUcAdjpJJSR+fdmbD/zHCd7hMkjJn04UQtCQBtshgxClzg6NIS2w== - dependencies: - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/visitor-keys" "4.29.0" - -"@typescript-eslint/types@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.0.tgz#c8f1a1e4441ea4aca9b3109241adbc145f7f8a4e" - integrity sha512-2YJM6XfWfi8pgU2HRhTp7WgRw78TCRO3dOmSpAvIQ8MOv4B46JD2chnhpNT7Jq8j0APlIbzO1Bach734xxUl4A== - -"@typescript-eslint/typescript-estree@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.0.tgz#af7ab547757b86c91bfdbc54ff86845410856256" - integrity sha512-8ZpNHDIOyqzzgZrQW9+xQ4k5hM62Xy2R4RPO3DQxMc5Rq5QkCdSpk/drka+DL9w6sXNzV5nrdlBmf8+x495QXQ== - dependencies: - "@typescript-eslint/types" "4.29.0" - "@typescript-eslint/visitor-keys" "4.29.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/visitor-keys@4.29.0": - version "4.29.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.0.tgz#1ff60f240def4d85ea68d4fd2e4e9759b7850c04" - integrity sha512-LoaofO1C/jAJYs0uEpYMXfHboGXzOJeV118X4OsZu9f7rG7Pr9B3+4HTU8+err81rADa4xfQmAxnRnPAI2jp+Q== - dependencies: - "@typescript-eslint/types" "4.29.0" - eslint-visitor-keys "^2.0.0" - -abbrev@1: - version "1.1.1" - resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -abstract-logging@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" - integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== - -acorn-jsx@^5.3.1: - version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -agent-base@6: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ajv@^6.10.0, ajv@^6.11.0, ajv@^6.12.4, ajv@^6.12.6: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.1, ajv@^8.1.0: - version "8.11.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ansi-align@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" - integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== - dependencies: - string-width "^4.1.0" - -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -app-root-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz#210b6f43873227e18a4b810a032283311555d5ad" - integrity sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw== - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -avvio@^7.1.2: - version "7.2.5" - resolved "https://registry.npmjs.org/avvio/-/avvio-7.2.5.tgz#65ba255f10b0bea7ac6eded71a5344cd88f5de19" - integrity sha512-AOhBxyLVdpOad3TujtC9kL/9r3HnTkxwQ5ggOsYrvvZP1cCFvzHWJd5XxZDFuTn+IN8vkKSG5SEJrd27vCSbeA== - dependencies: - archy "^1.0.0" - debug "^4.0.0" - fastq "^1.6.1" - queue-microtask "^1.1.2" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bintrees@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" - integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-writer@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" - integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -centra@^2.4.2: - version "2.5.0" - resolved "https://registry.npmjs.org/centra/-/centra-2.5.0.tgz#854c30f9a3ff50da49fa69a8cb441aa25aa1e8e8" - integrity sha512-CnSF1HD8vOOgNbE4P2fZEhdhfAohvpcF3DSdSvEcSHDAZvr+Xfw73isT8SXJJc3VMBqSwjXhr29/ikHUgFcypg== - -chalk@^1.1.1: - version "1.1.3" - resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.2.2: - version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cli-boxes@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-highlight@^2.1.10: - version "2.1.11" - resolved "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" - integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== - dependencies: - chalk "^4.0.0" - highlight.js "^10.7.1" - mz "^2.4.0" - parse5 "^5.1.1" - parse5-htmlparser2-tree-adapter "^6.0.0" - yargs "^16.0.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -cluster-key-slot@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" - integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -cookie@^0.4.0, cookie@^0.4.1: - version "0.4.2" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^3.2.6: - version "3.2.7" - resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -denque@^1.1.0: - version "1.5.1" - resolved "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" - integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -discord-api-types@0.22.0: - version "0.22.0" - resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz#34dc57fe8e016e5eaac5e393646cd42a7e1ccc2a" - integrity sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg== - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -dotenv@^8.2.0: - version "8.6.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" - integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - -"eris@github:abalabahaha/eris#dev": - version "0.16.2-dev" - resolved "https://codeload.github.com/abalabahaha/eris/tar.gz/52db15366f14e31071375e4eb6756b8530a9d677" - dependencies: - ws "^8.2.3" - optionalDependencies: - opusscript "^0.0.8" - tweetnacl "^1.0.3" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-prettier@8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" - integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== - -eslint-plugin-prettier@3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz#cdbad3bf1dbd2b177e9825737fe63b476a08f0c7" - integrity sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint@7.32.0: - version "7.32.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== - dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -fast-decode-uri-component@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" - integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-json-stringify@^2.5.2: - version "2.7.13" - resolved "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz#277aa86c2acba4d9851bd6108ed657aa327ed8c0" - integrity sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA== - dependencies: - ajv "^6.11.0" - deepmerge "^4.2.2" - rfdc "^1.2.0" - string-similarity "^4.0.1" - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fast-redact@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.1.tgz#790fcff8f808c2e12fabbfb2be5cb2deda448fa0" - integrity sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A== - -fast-safe-stringify@^2.0.8: - version "2.1.1" - resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fastify-error@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.1.tgz#8eb993e15e3cf57f0357fc452af9290f1c1278d2" - integrity sha512-oCfpcsDndgnDVgiI7bwFKAun2dO+4h84vBlkWsWnz/OUK9Reff5UFoFl241xTiLeHWX/vU9zkDVXqYUxjOwHcQ== - -fastify-no-icon@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/fastify-no-icon/-/fastify-no-icon-4.0.0.tgz#d37973a345ed3171d17a326fefb1af2058225876" - integrity sha512-4LzH9zgICq1+vKhSmfAqmYZUYpWU/yvonH7NSN8cb0xYt9VE0MR92kXPuS3+4XMavBXtfMFAoUEZqpQemJRuzQ== - dependencies: - fastify-plugin "^2.3.0" - -fastify-plugin@^2.3.0: - version "2.3.4" - resolved "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-2.3.4.tgz#b17abdc36a97877d88101fb86ad8a07f2c07de87" - integrity sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ== - dependencies: - semver "^7.3.2" - -fastify-warning@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz#e717776026a4493dc9a2befa44db6d17f618008f" - integrity sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw== - -fastify@3.20.1: - version "3.20.1" - resolved "https://registry.npmjs.org/fastify/-/fastify-3.20.1.tgz#8ca19b1da6e5e09b552b412168b1923a0eea9f9f" - integrity sha512-AzIpPuHdPaRBMWCg+LbnfGvhmBVpF1tRihGOfpxnUphL1eh8ZrN1GbY3cXY07yn4fUNzYsByTkc9/IjwXH7DAQ== - dependencies: - "@fastify/ajv-compiler" "^1.0.0" - abstract-logging "^2.0.0" - avvio "^7.1.2" - fast-json-stringify "^2.5.2" - fastify-error "^0.3.0" - fastify-warning "^0.2.0" - find-my-way "^4.1.0" - flatstr "^1.0.12" - light-my-request "^4.2.0" - pino "^6.13.0" - proxy-addr "^2.0.7" - readable-stream "^3.4.0" - rfdc "^1.1.4" - secure-json-parse "^2.0.0" - semver "^7.3.2" - tiny-lru "^7.0.0" - -fastq@^1.6.0, fastq@^1.6.1: - version "1.13.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -figlet@^1.1.1: - version "1.5.2" - resolved "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz#dda34ff233c9a48e36fcff6741aeb5bafe49b634" - integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ== - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-my-way@^4.1.0: - version "4.5.1" - resolved "https://registry.npmjs.org/find-my-way/-/find-my-way-4.5.1.tgz#758e959194b90aea0270db18fff75e2fceb2239f" - integrity sha512-kE0u7sGoUFbMXcOG/xpkmz4sRLCklERnBcg7Ftuu1iAxsfEt2S46RLJ3Sq7vshsEy2wJT2hZxE58XZK27qa8kg== - dependencies: - fast-decode-uri-component "^1.0.1" - fast-deep-equal "^3.1.3" - safe-regex2 "^2.0.0" - semver-store "^0.3.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatstr@^1.0.12: - version "1.0.12" - resolved "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" - integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== - -flatted@^3.1.0: - version "3.2.5" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" - integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== - -form-data@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== - dependencies: - ini "1.3.7" - -globals@^13.6.0, globals@^13.9.0: - version "13.13.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b" - integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A== - dependencies: - type-fest "^0.20.2" - -globby@^11.0.3: - version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.2: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -highlight.js@^10.7.1: - version "10.7.3" - resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" - integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== - -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - -husky@7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/husky/-/husky-7.0.1.tgz#579f4180b5da4520263e8713cc832942b48e1f1c" - integrity sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA== - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= - -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@1.3.7: - version "1.3.7" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ioredis@4.27.6: - version "4.27.6" - resolved "https://registry.npmjs.org/ioredis/-/ioredis-4.27.6.tgz#a53d427d3fe75fbd10ed7ad150ce00559df8dcf8" - integrity sha512-6W3ZHMbpCa8ByMyC1LJGOi7P2WiOKP9B3resoZOVLDhi+6dDBOW+KNsRq3yI36Hmnb2sifCxHX+YSarTeXh48A== - dependencies: - cluster-key-slot "^1.1.0" - debug "^4.3.1" - denque "^1.1.0" - lodash.defaults "^4.2.0" - lodash.flatten "^4.4.0" - p-map "^2.1.0" - redis-commands "1.7.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.3" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js-yaml@^3.13.1, js-yaml@^3.14.0: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -light-my-request@^4.2.0: - version "4.9.0" - resolved "https://registry.npmjs.org/light-my-request/-/light-my-request-4.9.0.tgz#83559c7ce7e503466113e36f40a1d596a1886626" - integrity sha512-b1U3z4OVPoO/KanT14NRkXMr9rRtXAiq0ORqNrqhDyb5bGkZjAdEc6GRN1GWCfgaLBG+aq73qkCLDNeB3c2sLw== - dependencies: - ajv "^8.1.0" - cookie "^0.4.0" - process-warning "^1.0.0" - set-cookie-parser "^2.4.1" - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^7.4.0: - version "7.8.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz#649aaeb294a56297b5cbc5d70f198dcc5ebe5747" - integrity sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg== - -lru_map@^0.3.3: - version "0.3.3" - resolved "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" - integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= - -luxon@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/luxon/-/luxon-2.0.1.tgz#b41ca2f1f5ad8099c18603ae6c36a7794039daf0" - integrity sha512-8Eawf81c9ZlQj62W3eq4mp+C7SAIAnmaS7ZuEAiX503YMcn+0C1JnMQRtfaQj6B5qTZLgHv0F4H5WabBCvi1fw== - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0: - version "1.2.6" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -mz@^2.4.0: - version "2.7.0" - resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -nodemon@2.0.12: - version "2.0.12" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5" - integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA== - dependencies: - chokidar "^3.2.2" - debug "^3.2.6" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.7" - semver "^5.7.1" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.3" - update-notifier "^4.1.0" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= - dependencies: - abbrev "1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^4.1.0: - version "4.5.1" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" - integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -opusscript@^0.0.8: - version "0.0.8" - resolved "https://registry.npmjs.org/opusscript/-/opusscript-0.0.8.tgz#00b49e81281b4d99092d013b1812af8654bd0a87" - integrity sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ== - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-map@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -packet-reader@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" - integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parent-require@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz#746a167638083a860b0eef6732cb27ed46c32977" - integrity sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc= - -parse5-htmlparser2-tree-adapter@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" - -parse5@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pg-connection-string@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-pool@^3.4.1: - version "3.5.1" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz#f499ce76f9bf5097488b3b83b19861f28e4ed905" - integrity sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ== - -pg-protocol@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" - integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== - -pg-types@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@8.7.1: - version "8.7.1" - resolved "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" - integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.5.0" - pg-pool "^3.4.1" - pg-protocol "^1.5.0" - pg-types "^2.1.0" - pgpass "1.x" - -pgpass@1.x: - version "1.0.5" - resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" - integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== - dependencies: - split2 "^4.1.0" - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pino-std-serializers@^3.1.0: - version "3.2.0" - resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz#b56487c402d882eb96cd67c257868016b61ad671" - integrity sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg== - -pino@^6.13.0: - version "6.14.0" - resolved "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz#b745ea87a99a6c4c9b374e4f29ca7910d4c69f78" - integrity sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg== - dependencies: - fast-redact "^3.0.0" - fast-safe-stringify "^2.0.8" - flatstr "^1.0.12" - pino-std-serializers "^3.1.0" - process-warning "^1.0.0" - quick-format-unescaped "^4.0.3" - sonic-boom "^1.0.2" - -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= - -postgres-date@~1.0.4: - version "1.0.7" - resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" - integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" - integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== - -process-warning@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" - integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -prom-client@13.1.0: - version "13.1.0" - resolved "https://registry.npmjs.org/prom-client/-/prom-client-13.1.0.tgz#1185caffd8691e28d32e373972e662964e3dba45" - integrity sha512-jT9VccZCWrJWXdyEtQddCDszYsiuWj5T0ekrPszi/WEegj3IZy6Mm09iOOVM86A4IKMWq8hZkT2dD9MaSe+sng== - dependencies: - tdigest "^0.1.1" - -proxy-addr@^2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pstree.remy@^1.1.7: - version "1.1.8" - resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -pupa@^2.0.1: - version "2.1.1" - resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - -queue-microtask@^1.1.2, queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^3.4.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -redis-commands@1.7.0: - version "1.7.0" - resolved "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" - integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= - dependencies: - redis-errors "^1.0.0" - -reflect-metadata@0.1.13, reflect-metadata@^0.1.13: - version "0.1.13" - resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - -regexpp@^3.1.0: - version "3.2.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - -ret@~0.2.0: - version "0.2.2" - resolved "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" - integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rfdc@^1.1.4, rfdc@^1.2.0: - version "1.3.0" - resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@^5.0.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex2@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" - integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== - dependencies: - ret "~0.2.0" - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -secure-json-parse@^2.0.0: - version "2.4.0" - resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.4.0.tgz#5aaeaaef85c7a417f76271a4f5b0cc3315ddca85" - integrity sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg== - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver-store@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9" - integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg== - -semver@^5.7.1: - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: - version "7.3.6" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" - integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== - dependencies: - lru-cache "^7.4.0" - -set-cookie-parser@^2.4.1: - version "2.4.8" - resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" - integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== - -sha.js@^2.4.11: - version "2.4.11" - resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -slash-commands@1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/slash-commands/-/slash-commands-1.5.0.tgz#674723f367d1d89b8f4068d31617d6bfe5391546" - integrity sha512-9tCwqqFRyBUyVD9J0n+CmsBpr550Bpi191LN4jKJh1ble4+vsppApBxrOVZMRhomPLy7wk8tUeIdmA9yF0a2uA== - dependencies: - centra "^2.4.2" - tweetnacl "^1.0.3" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -sonic-boom@^1.0.2: - version "1.4.1" - resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz#d35d6a74076624f12e6f917ade7b9d75e918f53e" - integrity sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg== - dependencies: - atomic-sleep "^1.0.0" - flatstr "^1.0.12" - -source-map-support@0.5.19: - version "0.5.19" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@^0.5.17, source-map-support@^0.5.19: - version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split2@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" - integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - -string-similarity@^4.0.1: - version "4.0.4" - resolved "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" - integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -table@^6.0.9: - version "6.8.0" - resolved "https://registry.npmjs.org/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" - integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== - dependencies: - ajv "^8.0.1" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - -tdigest@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" - integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE= - dependencies: - bintrees "1.0.1" - -term-size@^2.1.0: - version "2.2.1" - resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" - integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - -tiny-lru@^7.0.0: - version "7.0.6" - resolved "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz#b0c3cdede1e5882aa2d1ae21cb2ceccf2a331f24" - integrity sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow== - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - -ts-node@10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz#e656d8ad3b61106938a867f69c39a8ba6efc966e" - integrity sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA== - dependencies: - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.3: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslog@3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/tslog/-/tslog-3.2.0.tgz#4982c289a8948670d6a1c49c29977ae9f861adfa" - integrity sha512-xOCghepl5w+wcI4qXI7vJy6c53loF8OoC/EuKz1ktAPMtltEDz00yo1poKuyBYIQaq4ZDYKYFPD9PfqVrFXh0A== - dependencies: - source-map-support "^0.5.19" - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -tweetnacl@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" - integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typeorm@0.2.31: - version "0.2.31" - resolved "https://registry.npmjs.org/typeorm/-/typeorm-0.2.31.tgz#82b8a1b233224f81c738f53b0380386ccf360917" - integrity sha512-dVvCEVHH48DG0QPXAKfo0l6ecQrl3A8ucGP4Yw4myz4YEDMProebTQo8as83uyES+nrwCbu3qdkL4ncC2+qcMA== - dependencies: - "@sqltools/formatter" "1.2.2" - app-root-path "^3.0.0" - buffer "^5.5.0" - chalk "^4.1.0" - cli-highlight "^2.1.10" - debug "^4.1.1" - dotenv "^8.2.0" - glob "^7.1.6" - js-yaml "^3.14.0" - mkdirp "^1.0.4" - reflect-metadata "^0.1.13" - sha.js "^2.4.11" - tslib "^1.13.0" - xml2js "^0.4.23" - yargonaut "^1.1.2" - yargs "^16.0.3" - -typescript@4.3.5: - version "4.3.5" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" - integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== - -undefsafe@^2.0.3: - version "2.0.5" - resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - -undici@3.3.6: - version "3.3.6" - resolved "https://registry.npmjs.org/undici/-/undici-3.3.6.tgz#06d3b97b7eeff46bce6f8a71079c09f64dd59dc1" - integrity sha512-/j3YTZ5AobMB4ZrTY72mzM54uFUX32v0R/JRW9G2vOyF1uSKYAx+WT8dMsAcRS13TOFISv094TxIyWYk+WEPsA== - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -update-notifier@^4.1.0: - version "4.1.3" - resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" - integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.0.0.tgz#550605d13dfc1437c9ec1396975709c6d7ffc57d" - integrity sha512-6AcSIXpBlS0QvCVKk+3cWnWElLsA6SzC0lkQ43ciEglgXJXiCWK3/CGFEJ+Ybgp006CMibamAsqOlxE9s4AvYA== - -ws@^8.2.3: - version "8.5.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml2js@^0.4.23: - version "0.4.23" - resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargonaut@^1.1.2: - version "1.1.4" - resolved "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz#c64f56432c7465271221f53f5cc517890c3d6e0c" - integrity sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA== - dependencies: - chalk "^1.1.1" - figlet "^1.1.1" - parent-require "^1.0.0" - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^16.0.0, yargs@^16.0.3: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==