mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-11 13:56:15 +00:00
chore: merge dev to main
This commit is contained in:
3
.commitlintrc.js
Executable file
3
.commitlintrc.js
Executable file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
}
|
||||
15
.editorconfig
Executable file
15
.editorconfig
Executable file
@@ -0,0 +1,15 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
18
.eslintrc.js
18
.eslintrc.js
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es2021: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'eslint:recommended',
|
||||
overrides: [],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
indent: ['error', 'tab'],
|
||||
'linebreak-style': ['error', 'windows'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always']
|
||||
}
|
||||
};
|
||||
3
.gitattributes
vendored
Executable file
3
.gitattributes
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
*.pbxproj -text
|
||||
# specific for windows script files
|
||||
*.bat text eol=lf
|
||||
64
.github/workflows/build.yml
vendored
64
.github/workflows/build.yml
vendored
@@ -1,64 +0,0 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Dockerfile
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: all
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
id: ghcr
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/heads/main') }}
|
||||
suffix=-${{ github.sha }}
|
||||
|
||||
- name: Build and push main Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
cache-to: type=gha,mode=max,ignore-error=true
|
||||
cache-from: type=gha
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
78
.github/workflows/release.yml
vendored
Normal file
78
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||
restore-keys: ${{ runner.os }}-bun-
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build packages
|
||||
run: bun run build:packages
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: amd64, arm64
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and release
|
||||
env:
|
||||
RELEASE_WORKFLOW_STEP: release
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
DOCKER_REGISTRY_USER: ${{ github.repository_owner }}
|
||||
DOCKER_REGISTRY_PASSWORD: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
DEBUG: semantic-release:*
|
||||
run: bunx multi-semantic-release --debug
|
||||
|
||||
# We call multi-semantic-release twice to publish in a different step
|
||||
# An environment variable determines which plugins in the config to run
|
||||
- name: Trigger Portainer webhooks
|
||||
if: github.ref == 'refs/heads/main'
|
||||
env:
|
||||
RELEASE_WORKFLOW_STEP: publish
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
WEBSOCKET_API_PORTAINER_WEBHOOK_URL: ${{ secrets.WEBSOCKET_API_PORTAINER_WEBHOOK_URL }}
|
||||
DISCORD_BOT_PORTAINER_WEBHOOK_URL: ${{ secrets.DISCORD_BOT_PORTAINER_WEBHOOK_URL }}
|
||||
run: bunx multi-semantic-release
|
||||
|
||||
- name: Purge outdated images
|
||||
uses: snok/container-retention-policy@v3.0.0
|
||||
with:
|
||||
account: ${{ github.repository_owner }}
|
||||
token: ${{ secrets.DELETE_PACKAGES_TOKEN }}
|
||||
image-names: "revanced-bot-*"
|
||||
keep-n-most-recent: 5
|
||||
cut-off: 3M
|
||||
67
.gitignore
vendored
Normal file → Executable file
67
.gitignore
vendored
Normal file → Executable file
@@ -1,7 +1,60 @@
|
||||
node_modules
|
||||
model
|
||||
test.js
|
||||
eng.traineddata
|
||||
bots/*
|
||||
server/**/**
|
||||
test/*
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Turborepo
|
||||
.turbo/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Tesseract
|
||||
*.traineddata
|
||||
|
||||
# ESLint
|
||||
.eslintcache
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
4
.multi-releaserc
Normal file
4
.multi-releaserc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"ignorePrivate": false,
|
||||
"ignorePackages": ["packages/**"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
25
Dockerfile
25
Dockerfile
@@ -1,25 +0,0 @@
|
||||
FROM node:latest
|
||||
|
||||
ENV WIT_AI_TOKEN $WIT_AI_TOKEN
|
||||
ENV MONGODB_URI $MONGODB_URI
|
||||
ENV DISCORD_TOKEN $DISCORD_TOKEN
|
||||
|
||||
# Create app directory and install dependencies
|
||||
WORKDIR /usr/src/revanced-helper
|
||||
COPY . .
|
||||
RUN npm i
|
||||
|
||||
# Install the server
|
||||
WORKDIR /usr/src/revanced-helper/apps/server/src
|
||||
RUN npm i
|
||||
|
||||
# Install the client for the server
|
||||
WORKDIR /usr/src/revanced-helper/packages/client
|
||||
RUN npm i
|
||||
|
||||
# Install the bot
|
||||
WORKDIR /usr/src/revanced-helper/apps/bot-discord/src
|
||||
RUN npm i
|
||||
|
||||
WORKDIR /usr/src/revanced-helper
|
||||
CMD ["npm", "run", "start"]
|
||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="./assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="./assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="./assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<img height="24px" src="./assets/revanced-logo/revanced-logo-round.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 🤖 ReVanced Helper
|
||||
|
||||
Bots assisting ReVanced on multiple platforms.
|
||||
|
||||
## 👷🏻 Contributing
|
||||
|
||||
Thank you for considering! This project is made in TypeScript and we use the [Bun](https://bun.sh) JavaScript runtime.
|
||||
|
||||
Please refer to the [documentation](./docs) for more information.
|
||||
You can find projects under the [`apis`](./apis/), [`bots`](./bots), and [`packages`](./packages/) directories.
|
||||
2
apis/websocket/.env.example
Executable file
2
apis/websocket/.env.example
Executable file
@@ -0,0 +1,2 @@
|
||||
# Your Wit.ai token
|
||||
WIT_AI_TOKEN="YOUR_TOKEN_HERE"
|
||||
32
apis/websocket/.releaserc.js
Normal file
32
apis/websocket/.releaserc.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import defineSubprojectReleaseConfig from '../../semantic-release-config.js'
|
||||
|
||||
export default defineSubprojectReleaseConfig({
|
||||
plugins:
|
||||
process.env.RELEASE_WORKFLOW_STEP === 'publish'
|
||||
? [
|
||||
[
|
||||
'@semantic-release/exec',
|
||||
{
|
||||
publishCmd: 'bun run scripts/trigger-portainer-webhook.ts',
|
||||
},
|
||||
],
|
||||
]
|
||||
: [
|
||||
[
|
||||
'@codedependant/semantic-release-docker',
|
||||
{
|
||||
dockerImage: 'revanced-bot-websocket-api',
|
||||
dockerRegistry: 'ghcr.io',
|
||||
dockerProject: 'revanced',
|
||||
dockerContext: '../..',
|
||||
dockerPlatform: ['linux/amd64', 'linux/arm64'],
|
||||
dockerBuildQuiet: false,
|
||||
dockerTags: [
|
||||
'{{#if prerelease.[0]}}dev{{else}}main{{/if}}',
|
||||
'{{#unless prerelease.[0]}}latest{{/unless}}',
|
||||
'{{version}}',
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
})
|
||||
92
apis/websocket/CHANGELOG.md
Normal file
92
apis/websocket/CHANGELOG.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.11](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.10...@revanced/bot-websocket-api@1.0.0-dev.11) (2025-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apis/websocket:** attempt to fix missing remote address ([9b2888b](https://github.com/revanced/revanced-bots/commit/9b2888b944ea1d61d31aa5df3536768e9a2dadf8))
|
||||
* run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.10](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0-dev.9...@revanced/bot-websocket-api@1.0.0-dev.10) (2025-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413))
|
||||
* update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apis/websocket:** return `true` for data on a `TrainedMessage` packet ([65add4d](https://github.com/revanced/revanced-helper/commit/65add4dfeed2fa067c2c8e2377f7d01d505ade54))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.7...@revanced/bot-websocket-api@1.0.0-dev.8) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.6...@revanced/bot-websocket-api@1.0.0-dev.7) (2024-07-30)
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.5...@revanced/bot-websocket-api@1.0.0-dev.6) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23)
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apis/websocket:** hardcoded paths in tesseract worker builds ([38e00eb](https://github.com/revanced/revanced-helper/commit/38e00eb4e59c763bd74d27b9b9b482ea66e4dcf4))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.2...@revanced/bot-websocket-api@1.0.0-dev.3) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apis/websocket:** build and runtime issues ([89d8ab1](https://github.com/revanced/revanced-helper/commit/89d8ab1ee58278a9a96cdc31c679d0a0a0d865af))
|
||||
|
||||
# @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apis/websocket:** also include tesseract core files in build ([7dfbf6c](https://github.com/revanced/revanced-helper/commit/7dfbf6c92c49100954fa4aca471dce4ab9fd9565))
|
||||
|
||||
# @revanced/bot-websocket-api 1.0.0-dev.1 (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apis/websocket:** builds not working due to dynamic import requirement ([fc7be22](https://github.com/revanced/revanced-helper/commit/fc7be22c6c15974c7394790e93de2a23a6627153))
|
||||
* **apis/websocket:** don't bundle `tesseract.js` ([51a6fb6](https://github.com/revanced/revanced-helper/commit/51a6fb65f0df3409eacffb297430840a0e326989))
|
||||
* **apis/websocket:** fix forever stuck Promise ([168f40d](https://github.com/revanced/revanced-helper/commit/168f40def64ca213cd2b549f4bafed4c0e1e3695))
|
||||
* **apis/websocket:** fix undefined error ([2f03800](https://github.com/revanced/revanced-helper/commit/2f03800c61c00e59e512567d273a195e605d6736))
|
||||
* **apis/websocket:** improve logging and error handling ([b6cbe9d](https://github.com/revanced/revanced-helper/commit/b6cbe9d64c01ff11feab8351fb801bc1aee48325))
|
||||
* remove error cb handling for `socket.send()` calls ([29544d4](https://github.com/revanced/revanced-helper/commit/29544d4e0127173465796b7e3c62161f4db59c8b))
|
||||
* **types:** fix issues with typings ([669e24c](https://github.com/revanced/revanced-helper/commit/669e24ca8103ea051b4e61160dd0f978e36707ea))
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apis/websocket:** clear old client sessions and instances ([43bd0a0](https://github.com/revanced/revanced-helper/commit/43bd0a021cd885a3d74a1f307ec2935e81d17458))
|
||||
* **packages/shared:** add logger factory ([17c6be7](https://github.com/revanced/revanced-helper/commit/17c6be7bee5b5c24fd4a5279e73374b0bb7a6229))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* In `@revanced/discord-bot`, its environment variable
|
||||
`DATABASE_URL` has been renamed to `DATABASE_PATH`
|
||||
and the `file:` prefix is no longer needed
|
||||
17
apis/websocket/Dockerfile
Normal file
17
apis/websocket/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# This file should be triggered from the monorepo root
|
||||
FROM oven/bun:latest AS base
|
||||
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN cd apis/websocket && bun run build
|
||||
|
||||
FROM base AS release
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /build/apis/websocket/dist /app
|
||||
USER 1000:1000
|
||||
|
||||
ENTRYPOINT [ "bun", "--bun", "run", "index.js" ]
|
||||
0
LICENSE → apis/websocket/LICENSE
Normal file → Executable file
0
LICENSE → apis/websocket/LICENSE
Normal file → Executable file
73
apis/websocket/README.md
Executable file
73
apis/websocket/README.md
Executable file
@@ -0,0 +1,73 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="../../assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="../../assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="../../assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<img height="24px" src="../../assets/revanced-logo/revanced-logo-round.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 🚙 ReVanced Bot WebSocket API
|
||||
|
||||

|
||||
|
||||
The WebSocket API for ReVanced bots utilizing BSON for packet transmission.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Documentation are provided [here](./docs/README.md).
|
||||
|
||||
## 📄 License
|
||||
|
||||
**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions.
|
||||
8
apis/websocket/config.json
Executable file
8
apis/websocket/config.json
Executable file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "./config.schema.json",
|
||||
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"ocrConcurrentQueues": 1,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
27
apis/websocket/config.schema.json
Executable file
27
apis/websocket/config.schema.json
Executable file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "Address to listen on",
|
||||
"type": "string",
|
||||
"default": "127.0.0.1"
|
||||
},
|
||||
"port": {
|
||||
"description": "Port to listen on",
|
||||
"type": "integer",
|
||||
"default": 80
|
||||
},
|
||||
"ocrConcurrentQueues": {
|
||||
"description": "Number of concurrent queues for OCR",
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
},
|
||||
"logLevel": {
|
||||
"description": "The log level to print to console",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn", "info", "log", "debug", "trace", "none"],
|
||||
"default": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
apis/websocket/docker-compose.example.yml
Normal file
11
apis/websocket/docker-compose.example.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
websocket-api:
|
||||
container_name: revanced-bot-websocket-api
|
||||
image: ghcr.io/revanced/revanced-bot-websocket-api:latest
|
||||
environment:
|
||||
- WIT_AI_TOKEN=
|
||||
volumes:
|
||||
- /data/revanced-bot-websocket-api:/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
restart: unless-stopped
|
||||
45
apis/websocket/docs/1_configuration.md
Normal file
45
apis/websocket/docs/1_configuration.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ⚙️ Configuration
|
||||
|
||||
This is the default configuration (provided in [config.json](../config.json)):
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 3000,
|
||||
"ocrConcurrentQueues": 1,
|
||||
"consoleLogLevel": "log"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `config.address` & `config.port`
|
||||
|
||||
The address and port for the server to listen on.
|
||||
|
||||
### `config.ocrConcurrentQueues`
|
||||
|
||||
Amount of concurrent queues that can be run at a time.
|
||||
|
||||
> [!WARNING]
|
||||
> Setting this too high may cause performance issues.
|
||||
|
||||
### `config.logLevel`
|
||||
|
||||
The level of logs to print to console. If the level is more important or equally important to set level, it will be forwarded to the console.
|
||||
|
||||
The possible levels (sorted by their importance descendingly) are:
|
||||
|
||||
- `none` (no messages)
|
||||
- `fatal`
|
||||
- `error`
|
||||
- `warn`
|
||||
- `info`
|
||||
- `log`
|
||||
- `debug`
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will tell you how to run and bundle the server.
|
||||
|
||||
Continue: [🏃🏻♂️ Running the server](./2_running_and_deploying.md)
|
||||
58
apis/websocket/docs/2_running_and_deploying.md
Normal file
58
apis/websocket/docs/2_running_and_deploying.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 🏃🏻♂️ Running and deploying the server
|
||||
|
||||
There are many methods to run the server. Choose one that suits best for the situation.
|
||||
|
||||
## 👷🏻 Development mode
|
||||
|
||||
There will be no compilation step, and Bun will automatically watch changes and restart the server for you.
|
||||
|
||||
You can quickly start the server by running:
|
||||
|
||||
```sh
|
||||
bun dev
|
||||
```
|
||||
|
||||
## 📦 Building
|
||||
|
||||
If you're looking to build and host the server somewhere else, you can run:
|
||||
|
||||
```sh
|
||||
bun run build
|
||||
```
|
||||
|
||||
The distribution files will be placed inside the `dist` directory. Inside will include:
|
||||
|
||||
- The default configuration for the API
|
||||
- Compiled source files of the API
|
||||
|
||||
## ✈️ Deploying
|
||||
|
||||
To deploy the API, you'll need to:
|
||||
|
||||
1. [Build the API as seen in the previous step](#-building)
|
||||
|
||||
2. Copy contents of the `dist` directory
|
||||
|
||||
```sh
|
||||
# For instance, we'll copy them both to /usr/src/api
|
||||
cp -R ./dist/* /usr/src/api
|
||||
```
|
||||
|
||||
3. Replace the default configuration *(optional)*
|
||||
|
||||
4. Configure environment variables
|
||||
As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**.
|
||||
It is recommended to set `NODE_ENV` to `production` when deploying production builds.
|
||||
|
||||
5. Finally, you can run the API using these commands
|
||||
|
||||
```sh
|
||||
cd /usr/src/api
|
||||
bun run index.js
|
||||
```
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will tell you about packets.
|
||||
|
||||
Continue: [📨 Packets](./3_packets.md)
|
||||
31
apis/websocket/docs/3_packets.md
Normal file
31
apis/websocket/docs/3_packets.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 📨 Packets
|
||||
|
||||
Packets are BSON messages sent to the server. They're structured like the following when decoded:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 12345,
|
||||
"d": {
|
||||
"some_field": "some data"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `packet.op`
|
||||
|
||||
Operation codes are numbers that communicate an action.
|
||||
|
||||
### `packet.d`
|
||||
|
||||
Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**.
|
||||
|
||||
### `packet.s` (server packets)
|
||||
|
||||
A sequence number, exclusively for server packets. The WebSocket server contacts other APIs and they may not be reliable at all times, this makes race conditions. A sequence number cleanly solves this issue by letting the client know what the next packet sequence number would be by giving the current number.
|
||||
|
||||
#### 📦 Schemas and constants
|
||||
|
||||
Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well.
|
||||
|
||||
[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../../packages/shared/src/schemas/Packet.ts)
|
||||
[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../../packages/shared/src/constants/Operation.ts)
|
||||
16
apis/websocket/docs/README.md
Executable file
16
apis/websocket/docs/README.md
Executable file
@@ -0,0 +1,16 @@
|
||||
# 🚙 ReVanced Bot WebSocket API
|
||||
|
||||
This documentation explains how the server works, how to start developing, and how to configure the server.
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
0. [🏗️ Set up the development environment (if you haven't already)](../../../docs/0_development_environment.md)
|
||||
1. [⚙️ Configuration](./1_configuration.md)
|
||||
2. [🏃🏻♂️ Running the server](./2_running.md)
|
||||
3. [📨 Packets](./3_packets.md)
|
||||
|
||||
## ⏭️ Start here
|
||||
|
||||
The next page will tell you how to configure the server.
|
||||
|
||||
Continue: [⚙️ Configuration](./1_configuration.md)
|
||||
40
apis/websocket/package.json
Executable file
40
apis/websocket/package.json
Executable file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@revanced/bot-websocket-api",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "1.0.0-dev.11",
|
||||
"description": "🧦 WebSocket API server for bots assisting ReVanced",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"bundle": "bun run scripts/build.ts",
|
||||
"dev": "bun run src/index.ts --watch",
|
||||
"build": "bun bundle",
|
||||
"watch": "bun dev"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/revanced/revanced-bots.git",
|
||||
"directory": "apis/websocket"
|
||||
},
|
||||
"author": "Palm <contact@palmdevs.me> (https://palmdevs.me)",
|
||||
"contributors": [
|
||||
"Palm <contact@palmdevs.me> (https://palmdevs.me)",
|
||||
"ReVanced <nosupport@revanced.app> (https://revanced.app)"
|
||||
],
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/revanced/revanced-bots/issues"
|
||||
},
|
||||
"homepage": "https://github.com/revanced/revanced-bots#readme",
|
||||
"dependencies": {
|
||||
"@revanced/bot-shared": "workspace:*",
|
||||
"@sapphire/async-queue": "^1.5.5",
|
||||
"chalk": "^5.4.1",
|
||||
"tesseract.js": "^5.1.1",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
"typed-emitter": "^2.1.0"
|
||||
}
|
||||
}
|
||||
32
apis/websocket/scripts/build.ts
Normal file
32
apis/websocket/scripts/build.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createLogger } from '@revanced/bot-shared'
|
||||
import { cp, exists, rm } from 'fs/promises'
|
||||
|
||||
const logger = createLogger()
|
||||
|
||||
logger.info('Cleaning previous build...')
|
||||
if (await exists('./dist')) await rm('./dist', { recursive: true })
|
||||
|
||||
logger.info('Building WebSocket API...')
|
||||
await Bun.build({
|
||||
entrypoints: ['./src/index.ts'],
|
||||
outdir: './dist',
|
||||
target: 'bun',
|
||||
sourcemap: 'external',
|
||||
})
|
||||
|
||||
logger.info('Building Tesseract.js worker...')
|
||||
await Bun.build({
|
||||
entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'],
|
||||
external: ['tesseract.js-core/*'],
|
||||
target: 'bun',
|
||||
outdir: './dist/worker',
|
||||
sourcemap: 'external',
|
||||
})
|
||||
|
||||
// Tesseract.js is really bad for minification
|
||||
// It forcefully requires this core module to be present which contains the WASM files
|
||||
logger.info('Copying Tesseract.js Core...')
|
||||
await cp('../../node_modules/tesseract.js-core', './dist/node_modules/tesseract.js-core', { recursive: true })
|
||||
|
||||
logger.info('Copying config...')
|
||||
await cp('config.json', 'dist/config.json')
|
||||
4
apis/websocket/scripts/trigger-portainer-webhook.ts
Normal file
4
apis/websocket/scripts/trigger-portainer-webhook.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { $ } from 'bun'
|
||||
|
||||
const URLEnvironmentVariableName = 'WEBSOCKET_API_PORTAINER_WEBHOOK_URL'
|
||||
await $`INPUT_WEBHOOK_URL=${process.env[URLEnvironmentVariableName]} bun run ../../node_modules/portainer-service-webhook/dist`
|
||||
137
apis/websocket/src/classes/Client.ts
Executable file
137
apis/websocket/src/classes/Client.ts
Executable file
@@ -0,0 +1,137 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import {
|
||||
ClientOperation,
|
||||
DisconnectReason,
|
||||
type Packet,
|
||||
ServerOperation,
|
||||
deserializePacket,
|
||||
isClientPacket,
|
||||
serializePacket,
|
||||
uncapitalize,
|
||||
} from '@revanced/bot-shared'
|
||||
|
||||
import type TypedEmitter from 'typed-emitter'
|
||||
import type { RawData, WebSocket } from 'ws'
|
||||
|
||||
export default class Client {
|
||||
id: string
|
||||
disconnected: DisconnectReason | false = false
|
||||
ready = false
|
||||
currentSequence = 0
|
||||
|
||||
#emitter = new EventEmitter() as TypedEmitter<ClientEventHandlers>
|
||||
#socket: WebSocket
|
||||
|
||||
constructor(options: ClientOptions) {
|
||||
this.#socket = options.socket
|
||||
this.id = options.id
|
||||
|
||||
this.#socket.on('error', () => this.disconnect(DisconnectReason.ServerError))
|
||||
this.#socket.on('close', code => this._handleDisconnect(code))
|
||||
this.#socket.on('unexpected-response', () => this.disconnect(DisconnectReason.InvalidPacket))
|
||||
|
||||
this.send({
|
||||
op: ServerOperation.Hello,
|
||||
d: null,
|
||||
})
|
||||
|
||||
this._listen()
|
||||
this.ready = true
|
||||
this.#emitter.emit('ready')
|
||||
}
|
||||
|
||||
on<T extends keyof ClientEventHandlers>(name: T, handler: ClientEventHandlers[typeof name]) {
|
||||
this.#emitter.on(name, handler)
|
||||
}
|
||||
|
||||
once<T extends keyof ClientEventHandlers>(name: T, handler: ClientEventHandlers[typeof name]) {
|
||||
this.#emitter.once(name, handler)
|
||||
}
|
||||
|
||||
off<T extends keyof ClientEventHandlers>(name: T, handler: ClientEventHandlers[typeof name]) {
|
||||
this.#emitter.off(name, handler)
|
||||
}
|
||||
|
||||
send<T extends ServerOperation>(packet: Omit<Packet<T>, 's'>, sequence?: number) {
|
||||
this.#throwIfDisconnected('Cannot send packet to client that has already disconnected')
|
||||
this.#socket.send(serializePacket({ ...packet, s: sequence ?? this.currentSequence++ } as Packet<T>), err => {
|
||||
if (err) throw err
|
||||
})
|
||||
}
|
||||
|
||||
async disconnect(reason: DisconnectReason | number = DisconnectReason.Generic) {
|
||||
this.#throwIfDisconnected('Cannot disconnect client that has already disconnected')
|
||||
|
||||
this.#socket.close(reason)
|
||||
this._handleDisconnect(reason)
|
||||
}
|
||||
|
||||
#throwIfDisconnected(errorMessage: string) {
|
||||
if (this.disconnected !== false) throw new Error(errorMessage)
|
||||
|
||||
if (this.#socket.readyState !== this.#socket.OPEN) {
|
||||
this.#socket.close(DisconnectReason.NoOpenSocket)
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
protected _handleDisconnect(code: number) {
|
||||
this.disconnected = code
|
||||
this.ready = false
|
||||
|
||||
this.#emitter.emit('disconnect', code)
|
||||
}
|
||||
|
||||
protected _listen() {
|
||||
this.#socket.on('message', data => {
|
||||
this.#emitter.emit('message', data)
|
||||
try {
|
||||
const rawPacket = deserializePacket(this._toBuffer(data))
|
||||
if (!isClientPacket(rawPacket)) throw null
|
||||
|
||||
const packet: ClientPacketObject<ClientOperation> = {
|
||||
...rawPacket,
|
||||
client: this,
|
||||
}
|
||||
|
||||
this.#emitter.emit('packet', packet)
|
||||
this.#emitter.emit(
|
||||
uncapitalize(ClientOperation[packet.op] as ClientEventName),
|
||||
// @ts-expect-error TypeScript doesn't know that the above line will negate the type enough
|
||||
packet,
|
||||
)
|
||||
} catch (e) {
|
||||
// TODO: add error fields to sent packet so we can log what went wrong
|
||||
this.disconnect(DisconnectReason.InvalidPacket)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected _toBuffer(data: RawData) {
|
||||
if (data instanceof Buffer) return data
|
||||
if (data instanceof ArrayBuffer) return Buffer.from(data)
|
||||
return Buffer.concat(data as Uint8Array[])
|
||||
}
|
||||
}
|
||||
|
||||
export interface ClientOptions {
|
||||
id: string
|
||||
socket: WebSocket
|
||||
}
|
||||
|
||||
export type ClientPacketObject<TOp extends ClientOperation> = Packet<TOp> & {
|
||||
client: Client
|
||||
}
|
||||
|
||||
export type ClientEventName = keyof typeof ClientOperation
|
||||
|
||||
export type ClientEventHandlers = {
|
||||
[K in Uncapitalize<ClientEventName>]: (
|
||||
packet: ClientPacketObject<(typeof ClientOperation)[Capitalize<K>]>,
|
||||
) => Promise<unknown> | unknown
|
||||
} & {
|
||||
ready: () => Promise<unknown> | unknown
|
||||
packet: (packet: ClientPacketObject<ClientOperation>) => Promise<unknown> | unknown
|
||||
disconnect: (reason: DisconnectReason) => Promise<unknown> | unknown
|
||||
message: (data: RawData) => Promise<unknown> | unknown
|
||||
}
|
||||
63
apis/websocket/src/context.ts
Normal file
63
apis/websocket/src/context.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { OEM, createWorker as createTesseractWorker } from 'tesseract.js'
|
||||
|
||||
import { join as joinPath } from 'path'
|
||||
import { createLogger } from '@revanced/bot-shared'
|
||||
import { exists as pathExists } from 'fs/promises'
|
||||
import { getConfig } from './utils/config'
|
||||
|
||||
export const config = getConfig()
|
||||
|
||||
export const logger = createLogger({
|
||||
level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel,
|
||||
})
|
||||
|
||||
export const wit = {
|
||||
token: process.env['WIT_AI_TOKEN']!,
|
||||
async fetch(route: string, options?: RequestInit) {
|
||||
const res = await fetch(`https://api.wit.ai${route}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
...options,
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`)
|
||||
|
||||
return await res.json()
|
||||
},
|
||||
message(text: string) {
|
||||
return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise<WitMessageResponse>
|
||||
},
|
||||
async train(text: string, label: string) {
|
||||
await this.fetch('/utterances', {
|
||||
body: JSON.stringify([
|
||||
{
|
||||
text,
|
||||
intent: label,
|
||||
entities: [],
|
||||
traits: [],
|
||||
},
|
||||
]),
|
||||
method: 'POST',
|
||||
})
|
||||
},
|
||||
} as const
|
||||
|
||||
export interface WitMessageResponse {
|
||||
text: string
|
||||
intents: Array<{
|
||||
id: string
|
||||
name: string
|
||||
confidence: number
|
||||
}>
|
||||
}
|
||||
|
||||
const TesseractWorkerDirPath = joinPath(import.meta.dir, 'worker')
|
||||
const TesseractWorkerPath = joinPath(TesseractWorkerDirPath, 'index.js')
|
||||
|
||||
export const tesseract = await createTesseractWorker(
|
||||
'eng',
|
||||
OEM.DEFAULT,
|
||||
(await pathExists(TesseractWorkerDirPath)) ? { workerPath: TesseractWorkerPath } : undefined,
|
||||
)
|
||||
25
apis/websocket/src/events/index.ts
Executable file
25
apis/websocket/src/events/index.ts
Executable file
@@ -0,0 +1,25 @@
|
||||
import type { ClientOperation } from '@revanced/bot-shared'
|
||||
import type { Logger } from '@revanced/bot-shared'
|
||||
import type { Worker as TesseractWorker } from 'tesseract.js'
|
||||
import type { ClientPacketObject } from '../classes/Client'
|
||||
import type { WitMessageResponse } from '../context'
|
||||
import type { Config } from '../utils/config'
|
||||
|
||||
export { default as parseTextEventHandler } from './parseText'
|
||||
export { default as parseImageEventHandler } from './parseImage'
|
||||
export { default as trainMessageEventHandler } from './trainMessage'
|
||||
|
||||
export type EventHandler<POp extends ClientOperation> = (
|
||||
packet: ClientPacketObject<POp>,
|
||||
context: EventContext,
|
||||
) => void | Promise<void>
|
||||
|
||||
export type EventContext = {
|
||||
wit: {
|
||||
train(text: string, label: string): Promise<void>
|
||||
message(text: string): Promise<WitMessageResponse>
|
||||
}
|
||||
tesseract: TesseractWorker
|
||||
logger: Logger
|
||||
config: Config
|
||||
}
|
||||
56
apis/websocket/src/events/parseImage.ts
Executable file
56
apis/websocket/src/events/parseImage.ts
Executable file
@@ -0,0 +1,56 @@
|
||||
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
|
||||
import { AsyncQueue } from '@sapphire/async-queue'
|
||||
|
||||
import type { EventHandler } from '.'
|
||||
|
||||
const queue = new AsyncQueue()
|
||||
|
||||
const parseImageEventHandler: EventHandler<ClientOperation.ParseImage> = async (
|
||||
packet,
|
||||
{ tesseract, logger, config },
|
||||
) => {
|
||||
const {
|
||||
client,
|
||||
d: { image_url: imageUrl },
|
||||
} = packet
|
||||
|
||||
const nextSeq = client.currentSequence++
|
||||
|
||||
logger.debug(`Client ${client.id} requested to parse image from URL (${nextSeq})`, imageUrl)
|
||||
|
||||
if (queue.remaining < config.ocrConcurrentQueues) queue.shift()
|
||||
await queue.wait()
|
||||
|
||||
logger.debug(`Process queued (${nextSeq}), queue has ${queue.remaining} items`)
|
||||
|
||||
try {
|
||||
const { data, jobId } = await tesseract.recognize(imageUrl)
|
||||
|
||||
logger.debug(`Image parsed (job ${jobId}) (${nextSeq}):`, data.text)
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.ParsedImage,
|
||||
d: {
|
||||
text: data.text,
|
||||
},
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
} catch (e) {
|
||||
if (!client.disconnected)
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.ParseImageFailed,
|
||||
d: null,
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
else logger.warn(`Client disconnected before the failed packet could be sent (${nextSeq})`)
|
||||
logger.error(`Failed to parse image (${nextSeq}):`, e)
|
||||
} finally {
|
||||
queue.shift()
|
||||
logger.debug(`Finished parsing image (${nextSeq}), queue has ${queue.remaining} items`)
|
||||
}
|
||||
}
|
||||
|
||||
export default parseImageEventHandler
|
||||
43
apis/websocket/src/events/parseText.ts
Executable file
43
apis/websocket/src/events/parseText.ts
Executable file
@@ -0,0 +1,43 @@
|
||||
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
|
||||
|
||||
import type { EventHandler } from '.'
|
||||
|
||||
const parseTextEventHandler: EventHandler<ClientOperation.ParseText> = async (packet, { wit, logger }) => {
|
||||
const {
|
||||
client,
|
||||
d: { text },
|
||||
} = packet
|
||||
|
||||
const nextSeq = client.currentSequence++
|
||||
const actualText = text.slice(0, 279)
|
||||
|
||||
logger.debug(`Client ${client.id} requested to parse text:`, actualText)
|
||||
|
||||
try {
|
||||
const { intents } = await wit.message(actualText)
|
||||
const intentsWithoutIds = intents.map(({ id, ...rest }) => rest)
|
||||
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.ParsedText,
|
||||
d: {
|
||||
labels: intentsWithoutIds,
|
||||
},
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
} catch (e) {
|
||||
if (!client.disconnected)
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.ParseTextFailed,
|
||||
d: null,
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
else logger.warn(`Client disconnected before the failed packet could be sent (${nextSeq})`)
|
||||
logger.error(`Failed to parse text (${nextSeq}):`, e)
|
||||
}
|
||||
}
|
||||
|
||||
export default parseTextEventHandler
|
||||
41
apis/websocket/src/events/trainMessage.ts
Normal file
41
apis/websocket/src/events/trainMessage.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
|
||||
|
||||
import type { EventHandler } from '.'
|
||||
|
||||
const trainMessageEventHandler: EventHandler<ClientOperation.TrainMessage> = async (packet, { wit, logger }) => {
|
||||
const {
|
||||
client,
|
||||
d: { text, label },
|
||||
} = packet
|
||||
|
||||
const nextSeq = client.currentSequence++
|
||||
const actualText = text.slice(0, 279)
|
||||
|
||||
logger.debug(`${client.id} requested to train label ${label} (${nextSeq}) with:`, actualText)
|
||||
|
||||
try {
|
||||
await wit.train(actualText, label)
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.TrainedMessage,
|
||||
d: true,
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
|
||||
logger.debug(`Trained label (${nextSeq})`)
|
||||
} catch (e) {
|
||||
if (!client.disconnected)
|
||||
client.send(
|
||||
{
|
||||
op: ServerOperation.TrainMessageFailed,
|
||||
d: null,
|
||||
},
|
||||
nextSeq,
|
||||
)
|
||||
else logger.warn(`Client ${client.id} disconnected before the failed packet could be sent`)
|
||||
logger.error(`Failed to train (${nextSeq})`, e)
|
||||
}
|
||||
}
|
||||
|
||||
export default trainMessageEventHandler
|
||||
132
apis/websocket/src/index.ts
Executable file
132
apis/websocket/src/index.ts
Executable file
@@ -0,0 +1,132 @@
|
||||
import { inspect as inspectObject } from 'util'
|
||||
|
||||
import Client from './classes/Client'
|
||||
|
||||
import { type EventContext, parseImageEventHandler, parseTextEventHandler, trainMessageEventHandler } from './events'
|
||||
|
||||
import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared'
|
||||
|
||||
import { createServer } from 'http'
|
||||
import { type WebSocket, WebSocketServer } from 'ws'
|
||||
import { config, logger, tesseract, wit } from './context'
|
||||
|
||||
// Load config, init logger, check environment
|
||||
|
||||
if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`')
|
||||
const environment = (process.env['NODE_ENV'] ?? 'development') as NodeEnvironment
|
||||
|
||||
if (!['development', 'production'].includes(environment)) {
|
||||
logger.error('NODE_ENV is neither `development` nor `production`, unable to determine environment')
|
||||
logger.info('Set NODE_ENV to blank to use `development` mode')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
logger.info(`Running in ${environment} mode...`)
|
||||
|
||||
if (!process.env['WIT_AI_TOKEN']) {
|
||||
logger.error('WIT_AI_TOKEN is not defined in the environment variables')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Handle uncaught exceptions
|
||||
|
||||
process.on('uncaughtException', e => logger.error('Uncaught exception:', e))
|
||||
process.on('unhandledRejection', e => logger.error('Unhandled rejection:', e))
|
||||
|
||||
// Server logic
|
||||
|
||||
const clientIds = new Set<string>()
|
||||
const clientToSocket = new WeakMap<Client, WebSocket>()
|
||||
const socketToClient = new WeakMap<WebSocket, Client>()
|
||||
const eventContext: EventContext = {
|
||||
tesseract,
|
||||
logger,
|
||||
wit,
|
||||
config,
|
||||
}
|
||||
|
||||
const server = createServer()
|
||||
const wss = new WebSocketServer({
|
||||
// 16 KiB max payload
|
||||
// A Discord message can not be longer than 4000 characters
|
||||
// OCR should not be longer than 16000 characters
|
||||
maxPayload: 16 * 1024,
|
||||
server,
|
||||
})
|
||||
|
||||
wss.on('connection', async (socket, request) => {
|
||||
try {
|
||||
const addrInfo = request.socket.address()
|
||||
if (!('address' in addrInfo)) {
|
||||
socket.close()
|
||||
return logger.warn('Connection failed because client is missing remote address. addrInfo =', addrInfo)
|
||||
}
|
||||
|
||||
const id = `${addrInfo.address}:${addrInfo.port}`
|
||||
|
||||
if (clientIds.has(id)) {
|
||||
logger.warn(`Client ${id} already connected, disconnecting old session`)
|
||||
const oldClient = socketToClient.get(socket)
|
||||
await oldClient?.disconnect(DisconnectReason.NewConnection)
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
socket,
|
||||
id,
|
||||
})
|
||||
|
||||
socketToClient.set(socket, client)
|
||||
clientToSocket.set(client, socket)
|
||||
|
||||
logger.info(`New client connected with ID: ${id}`)
|
||||
|
||||
client.on('disconnect', reason => {
|
||||
clientIds.delete(client.id)
|
||||
clientToSocket.delete(client)
|
||||
socketToClient.delete(socket)
|
||||
|
||||
logger.info(
|
||||
`Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]} (${reason})`,
|
||||
)
|
||||
})
|
||||
|
||||
client.on('parseText', packet => parseTextEventHandler(packet, eventContext))
|
||||
client.on('parseImage', packet => parseImageEventHandler(packet, eventContext))
|
||||
client.on('trainMessage', packet => trainMessageEventHandler(packet, eventContext))
|
||||
|
||||
if (['debug', 'trace'].includes(config.logLevel)) {
|
||||
logger.debug('Debug logs enabled, attaching debug events...')
|
||||
|
||||
client.on('message', d => logger.debug(`Message from client ${client.id}:`, d))
|
||||
client.on('packet', ({ client, ...rawPacket }) =>
|
||||
logger.debug(`Packet received from client ${client.id}: ${inspectObject(rawPacket)}`),
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) logger.error(e.stack ?? e.message)
|
||||
else logger.error(inspectObject(e))
|
||||
|
||||
const client = socketToClient.get(socket)
|
||||
|
||||
if (!client) {
|
||||
logger.error(
|
||||
'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!',
|
||||
)
|
||||
return socket.terminate()
|
||||
}
|
||||
|
||||
if (client.disconnected === false) client.disconnect(DisconnectReason.ServerError)
|
||||
|
||||
logger.debug(`Client ${client.id} disconnected because of an internal error`)
|
||||
}
|
||||
})
|
||||
|
||||
// Start the server
|
||||
|
||||
server.listen(config.port, config.address)
|
||||
logger.debug(`Starting with these configurations: ${inspectObject(config)}`)
|
||||
|
||||
const addressInfo = wss.address()
|
||||
if (!addressInfo || typeof addressInfo !== 'object')
|
||||
logger.debug('Server started, but cannot determine address information')
|
||||
else logger.info(`Server started at: ${addressInfo.address}:${addressInfo.port}`)
|
||||
9
apis/websocket/src/types.d.ts
vendored
Executable file
9
apis/websocket/src/types.d.ts
vendored
Executable file
@@ -0,0 +1,9 @@
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
WIT_AI_TOKEN?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare type NodeEnvironment = 'development' | 'production'
|
||||
36
apis/websocket/src/utils/config.ts
Executable file
36
apis/websocket/src/utils/config.ts
Executable file
@@ -0,0 +1,36 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve as resolvePath } from 'path'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
const configPath = resolvePath(process.cwd(), 'config.json')
|
||||
|
||||
const userConfig: Partial<Config> = existsSync(configPath)
|
||||
? (
|
||||
await import(pathToFileURL(configPath).href, {
|
||||
with: {
|
||||
type: 'json',
|
||||
},
|
||||
})
|
||||
).default
|
||||
: {}
|
||||
|
||||
type BaseTypeOf<T> = T extends (infer U)[]
|
||||
? U[]
|
||||
: T extends (...args: unknown[]) => infer U
|
||||
? (...args: unknown[]) => U
|
||||
: T extends object
|
||||
? { [K in keyof T]: T[K] }
|
||||
: T
|
||||
|
||||
export type Config = Omit<BaseTypeOf<typeof import('../../config.json')>, '$schema'>
|
||||
|
||||
export const defaultConfig: Config = {
|
||||
address: '127.0.0.1',
|
||||
port: 8080,
|
||||
ocrConcurrentQueues: 1,
|
||||
logLevel: 'info',
|
||||
}
|
||||
|
||||
export function getConfig() {
|
||||
return Object.assign(defaultConfig, userConfig) satisfies Config
|
||||
}
|
||||
15
apis/websocket/tsconfig.json
Executable file
15
apis/websocket/tsconfig.json
Executable file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"composite": false,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["./*.json", "src/**/*.ts", "scripts/**/*.ts"]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkModPerms.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ban')
|
||||
.setDescription('Ban a member.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to ban')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption(option =>
|
||||
option
|
||||
.setName('dmd')
|
||||
.setDescription('Amount of days to delete messages')
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('Reason for the ban')
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
interaction.guild.members.ban(interaction.options.getUser('user'), {
|
||||
reason: interaction.options.getString('reason'),
|
||||
deleteMessageSeconds: interaction.options.getString('dmd') ?
|
||||
interaction.options.getString('dmd') * 86_400 : 0
|
||||
});
|
||||
|
||||
reportToLogs(config, interaction.client, 'banned', null, {
|
||||
reason: interaction.options.getString('reason'),
|
||||
actionTo: interaction.options.getUser('user'),
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js'
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
import muteMember from '../utils/muteMember.js';
|
||||
import exileMemberToChannel from '../utils/exileMemberToChannel.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('exile')
|
||||
.setDescription('Exile a member to support.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to exile')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason of the exile')
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const user = interaction.options.getUser('user');
|
||||
|
||||
const member = await interaction.guild.members.fetch(user);
|
||||
const reason = interaction.options.getString('reason');
|
||||
const parsedDuration = await muteMember(config, member, {
|
||||
reason,
|
||||
supportMute: true,
|
||||
guild: interaction.guild
|
||||
});
|
||||
|
||||
exileMemberToChannel(member, interaction.channel, null, reason, config);
|
||||
|
||||
reportToLogs(config, interaction.client, 'exiled', null, {
|
||||
reason,
|
||||
actionTo: user,
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel,
|
||||
expire: parsedDuration
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ContextMenuCommandBuilder, ApplicationCommandType } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js'
|
||||
import muteMember from '../utils/muteMember.js';
|
||||
import exileMemberToChannel from '../utils/exileMemberToChannel.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
|
||||
export default {
|
||||
data: new ContextMenuCommandBuilder()
|
||||
.setName('Exile Member')
|
||||
.setType(ApplicationCommandType.Message),
|
||||
async execute(helper, config, interaction) {
|
||||
if (
|
||||
!checkForPerms(config, interaction.member)
|
||||
)
|
||||
return interaction.reply({
|
||||
content: 'You don\'t have the permission to do this.',
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
await interaction.deferReply();
|
||||
const targetMsg = interaction.targetMessage;
|
||||
|
||||
const member = await interaction.guild.members.fetch(targetMsg.author.id);
|
||||
const parsedDuration = await muteMember(config, member, {
|
||||
channel: interaction.channel,
|
||||
reason: null,
|
||||
supportMute: true,
|
||||
guild: interaction.guild
|
||||
});
|
||||
|
||||
exileMemberToChannel(targetMsg.author, interaction.channel, targetMsg.content, null, config);
|
||||
|
||||
reportToLogs(config, interaction.client, 'exiled', null, {
|
||||
reason: null,
|
||||
actionTo: targetMsg.author,
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel,
|
||||
expire: parsedDuration
|
||||
}, interaction);
|
||||
|
||||
await targetMsg.delete();
|
||||
}
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js';
|
||||
import trainAISelectMenu from '../utils/trainAISelectMenu.js';
|
||||
|
||||
export default {
|
||||
data: {
|
||||
name: 'fb-dislike'
|
||||
},
|
||||
async execute(helper, config, interaction) {
|
||||
if (
|
||||
!checkForPerms(config, interaction.member)
|
||||
)
|
||||
return interaction.reply({
|
||||
content: 'You don\'t have the permission to do this.',
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
trainAISelectMenu(interaction, config, helper);
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js';
|
||||
|
||||
export default {
|
||||
data: {
|
||||
name: 'fb-like'
|
||||
},
|
||||
async execute(helper, config, interaction) {
|
||||
if (
|
||||
!checkForPerms(config, interaction.member)
|
||||
)
|
||||
return interaction.reply({
|
||||
content: 'You don\'t have the permission to do this.',
|
||||
ephemeral: true
|
||||
});
|
||||
// FIXME: somehow get the intent?
|
||||
// maybe storing in a collection and fetching the msg id with its label?
|
||||
/*
|
||||
helper.sendTrainData(interactedMessage, i.values[0]);
|
||||
|
||||
i.reply({ content: 'Sent training data to server.', ephemeral: true });
|
||||
|
||||
interaction.message.edit({ components: [] });
|
||||
*/
|
||||
interaction.reply({
|
||||
content: 'Feature currently not available. Please use the dislike button.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkModPerms.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
import muteMember from '../utils/muteMember.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('mute')
|
||||
.setDescription('Mute a member.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to mute')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('duration')
|
||||
.setDescription('The duration of mute')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('reason')
|
||||
.setDescription('The reason of the mute')
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const user = interaction.options.getUser('user');
|
||||
|
||||
const member = await interaction.guild.members.fetch(user);
|
||||
const reason = interaction.options.getString('reason');
|
||||
const parsedDuration = await muteMember(config, member, {
|
||||
duration: interaction.options.getString('duration'),
|
||||
reason,
|
||||
supportMute: false,
|
||||
guild: interaction.guild
|
||||
});
|
||||
|
||||
reportToLogs(config, interaction.client, 'muted', null, {
|
||||
reason,
|
||||
actionTo: user,
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel,
|
||||
expire: parsedDuration
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ContextMenuCommandBuilder, ApplicationCommandType } from 'discord.js';
|
||||
import trainAISelectMenu from '../utils/trainAISelectMenu.js';
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js'
|
||||
|
||||
export default {
|
||||
data: new ContextMenuCommandBuilder()
|
||||
.setName('Train Message')
|
||||
.setType(ApplicationCommandType.Message),
|
||||
async execute(helper, config, interaction) {
|
||||
if (
|
||||
!checkForPerms(config, interaction.member)
|
||||
)
|
||||
return interaction.reply({
|
||||
content: 'You don\'t have the permission to do this.',
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
trainAISelectMenu(interaction, config, helper);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkModPerms.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unban')
|
||||
.setDescription('Unban a member.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to ban')
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
interaction.guild.members.unban(interaction.options.getUser('user'));
|
||||
|
||||
reportToLogs(config, interaction.client, 'unbanned', null, {
|
||||
reason: null,
|
||||
actionTo: await client.users.fetch(interaction.options.getString('user')),
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
import unmuteMember from '../utils/unmuteMember.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unexile')
|
||||
.setDescription('Get the member back from an exilation.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to unexile')
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const user = interaction.options.getUser('user');
|
||||
|
||||
const member = await interaction.guild.members.fetch(user);
|
||||
const isExiled = await unmuteMember(config, member, true);
|
||||
|
||||
if (!isExiled) {
|
||||
await interaction.editReply({
|
||||
content: 'Member was not exiled.'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
reportToLogs(config, interaction.client, 'unexiled', null, {
|
||||
reason: null,
|
||||
actionTo: user,
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel,
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { checkForPerms } from '../utils/checkModPerms.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
import unmuteMember from '../utils/unmuteMember.js';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('unmute')
|
||||
.setDescription('Unmute a member.')
|
||||
.setDMPermission(false)
|
||||
.addUserOption(option =>
|
||||
option
|
||||
.setName('user')
|
||||
.setDescription('The member to unmute')
|
||||
.setRequired(true)
|
||||
),
|
||||
async execute(_, config, interaction) {
|
||||
if (!checkForPerms(config, interaction.member)) return interaction.reply({
|
||||
epheremal: true,
|
||||
content: 'You don\'t have the required permissions.'
|
||||
});
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
const user = interaction.options.getUser('user');
|
||||
|
||||
const member = await interaction.guild.members.fetch(user);
|
||||
const isMuted = await unmuteMember(config, member, false);
|
||||
|
||||
if (!isMuted) {
|
||||
await interaction.editReply({
|
||||
content: 'Member was not muted.'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
reportToLogs(config, interaction.client, 'unmuted', null, {
|
||||
reason: null,
|
||||
actionTo: user,
|
||||
actionBy: interaction.member,
|
||||
channel: interaction.channel,
|
||||
}, interaction);
|
||||
}
|
||||
};
|
||||
@@ -1,358 +0,0 @@
|
||||
{
|
||||
"discord": {
|
||||
"trainRoles": [
|
||||
"1019903194941362198",
|
||||
"955220417969262612",
|
||||
"973886585294704640"
|
||||
],
|
||||
"modRoles": [
|
||||
"955220417969262612",
|
||||
"973886585294704640"
|
||||
],
|
||||
"botId": "1038762591805247518",
|
||||
"ignoreRole": "1027874293192863765",
|
||||
"ignoreChannels": [
|
||||
"953965039105232906",
|
||||
"953964264400515092",
|
||||
"952987428786941952"
|
||||
],
|
||||
"supportChannel": "1135563848586379264",
|
||||
"mute": {
|
||||
"takeRoles": [
|
||||
"996121272897519687",
|
||||
"965267139902705744",
|
||||
"995126555867086938"
|
||||
],
|
||||
"giveRoles": [
|
||||
"953984696491061289"
|
||||
],
|
||||
"supportTakeRoles": [],
|
||||
"supportGiveRoles": [
|
||||
"1140310515730632814"
|
||||
],
|
||||
"supportMuteDuration": 600000
|
||||
}
|
||||
},
|
||||
"logs": {
|
||||
"channelId": "952987428786941952",
|
||||
"threadId": "1027892160797872179"
|
||||
},
|
||||
"sticky": {
|
||||
"channelId": "1135563848586379264",
|
||||
"stickyMessage": {
|
||||
"title": "Sticky notice",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Before you ask",
|
||||
"value": "Check the pinned messages and <#953993848374325269> for existing answers."
|
||||
},
|
||||
{
|
||||
"name": "🔸 How to ask",
|
||||
"value": "Include the following information:\n* Version of ReVanced Manager\n* Name of the app you are patching\n* Version of the app you are patching"
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
},
|
||||
"timeout": 180000
|
||||
},
|
||||
"server": {
|
||||
"port": 3000,
|
||||
"host": "127.0.0.1"
|
||||
},
|
||||
"responses": [
|
||||
{
|
||||
"label": "suggested_version",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Which version is suggested ❓",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "The suggested version can be seen in ReVanced Manager in the app selector screen. Refer to the ReVanced Manager documentation in <#953993848374325269> `3`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_crash",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why am I experiencing crashes ❓",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "You may have patched an unsuggested version of the app, changed the selection of patches or used a faulty APK. Refer to the documentation in <#953993848374325269> `3` in order to correctly patch your app correctly using ReVanced CLI or ReVanced Manager."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "rvmanager_abort",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why is ReVanced Manager aborting ❓",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "Your device may be unsupported by ReVanced Manager. Refer to the documentation in <#953993848374325269> `3` in order to use ReVanced CLI or check if your device is supported by ReVanced Manager."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_download",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Where or how to get ReVanced ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "You can use ReVanced CLI or ReVanced Manager to get ReVanced. Refer to the documentation in <#953993848374325269> `3`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "androidtv_support",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Does ReVanced support YouTube for Android TVs ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "Please refer to <#953993848374325269> `5`. Alternative, there is [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube)."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_nodownloader",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "How do I download videos on YouTube ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_casting",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why can I not cast videos on YouTube ❓",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "You may have patched YouTube with the `GmsCore support` patch which makes YouTube use Vanced MicroG instead of Google Services, but Vanced MicroG does not reliably support casting. In order to be able to cast videos on the patched app, you should not patch the app with the `GmsCore support` patch, but then you are forced to mount the patched app with root permissions, because you will not be able to install the app in normal circumstances and Google Services will reject the patched app."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "microg_download",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Where can I get Vanced MicroG ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "If you patched YouTube using the `GmsCore support` patch, the patched app will redirect you to the download link of Vanced MicroG if you open it. In case it does not, please refer to <#953993848374325269> `17`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "microg_nointernet",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why does YouTube say, I am offline ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "Please refer to <#953993848374325269> `15`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "rvdownload_unofficial",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "What are the official links of ReVanced ❓",
|
||||
"description": "A list of official links can be found in <#954066838856273960>.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "ReVanced is always available at [revanced.app](https://revanced.app)."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "yt_buffering",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why do videos fail to play❓",
|
||||
"description": "You might have asked a question that has been answered in the <#953993848374325269> channel already. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "Please refer to <#953993848374325269> `32`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "false",
|
||||
"threshold": 0,
|
||||
"reply": null
|
||||
}
|
||||
],
|
||||
"ocrResponses": [
|
||||
{
|
||||
"regex": "is not installed",
|
||||
"reply": {
|
||||
"title": "How do I download videos on YouTube ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "In order to be able to download videos on YouTube without YouTube Premium, you can patch YouTube with the `External downloads` patch. You can configure the downloader in the settings of the patched app. NewPipe is the default downloader. Please refer to <#953993848374325269> `24`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"regex": "You're offline|Please check your",
|
||||
"reply": {
|
||||
"title": "Why does YouTube say, I am offline ❓",
|
||||
"description": "You might have asked a question that has already been answered in <#953993848374325269>. Make sure to read it as it will answer a lot of your questions, guaranteed.",
|
||||
"color": 5150960,
|
||||
"fields": [
|
||||
{
|
||||
"name": "🔸 Regarding your question",
|
||||
"value": "Please refer to <#953993848374325269> `15`."
|
||||
}
|
||||
],
|
||||
"footer": {
|
||||
"text": "ReVanced",
|
||||
"icon_url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "https://cdn.discordapp.com/attachments/1095487869923119144/1115436493050224660/revanced-logo.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
import cureUsername from '../utils/cureUsername.js';
|
||||
|
||||
export default {
|
||||
name: Events.GuildMemberAdd,
|
||||
once: false,
|
||||
async execute(_, config, member) {
|
||||
cureUsername(member);
|
||||
const mute = await client.db.collection('muted').findOne({
|
||||
guild_id: member.guild.id,
|
||||
user_id: member.id
|
||||
});
|
||||
|
||||
if (mute) {
|
||||
// Add the roles given.
|
||||
member.roles.add(mute.support_mute ?
|
||||
config.mute.supportGiveRoles :
|
||||
config.mute.giveRoles
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
import cureUsername from '../utils/cureUsername.js';
|
||||
|
||||
export default {
|
||||
name: Events.GuildMemberUpdate,
|
||||
once: false,
|
||||
async execute(_, config, oldMember, newMember) {
|
||||
cureUsername(newMember);
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
|
||||
export default {
|
||||
name: Events.InteractionCreate,
|
||||
once: false,
|
||||
async execute(helper, config, interaction) {
|
||||
const command = interaction.client.commands.get(interaction.commandName || interaction.customId);
|
||||
|
||||
// It's the select menu interaction (hopefully), ignore.
|
||||
if (!command) return;
|
||||
|
||||
try {
|
||||
await command.execute(helper, config, interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
await interaction.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
|
||||
export default {
|
||||
name: Events.MessageCreate,
|
||||
once: false,
|
||||
async execute(helper, config, msg) {
|
||||
if (!msg.guild || msg.system || msg.webhookId || msg.author.bot) return;
|
||||
if (msg.content.startsWith('?')) {
|
||||
const [cmd, args] = msg.content.replace('?', '').split(/\s/g);
|
||||
const command = msg.client.msgCommands.get(cmd);
|
||||
if (command) {
|
||||
await command.execute(msg, args, config);
|
||||
}
|
||||
}
|
||||
const hasImmunity = msg.member.roles.cache.some(role => role.id === config.discord.ignoreRole);
|
||||
if (config.discord.ignoreChannels.includes(msg.channelId)) return;
|
||||
if (msg.attachments.first()?.contentType?.startsWith('image') && !hasImmunity) {
|
||||
helper.scanImage(msg.attachments.first().url, `${msg.channelId}/${msg.id}`);
|
||||
}
|
||||
|
||||
if (msg.content && !hasImmunity) {
|
||||
helper.scanText(
|
||||
msg.content.toLowerCase().replace(/<.*?>/g, ''),
|
||||
`${msg.channelId}/${msg.id}`
|
||||
);
|
||||
}
|
||||
|
||||
// Sticky message
|
||||
if (msg.channel.id !== config.sticky.channelId) return;
|
||||
if (msg.client.stickiedMessageTimeout) clearInterval(msg.client.stickiedMessageTimeout);
|
||||
|
||||
msg.client.stickiedMessageTimeout = setTimeout(async () => {
|
||||
const channel = await msg.client.channels.fetch(config.sticky.channelId);
|
||||
|
||||
const message = await channel.send({ embeds: [config.sticky.stickyMessage] });
|
||||
|
||||
if (msg.client.stickiedMessage && channel.messages.cache.get(msg.client.stickiedMessage)?.deletable)
|
||||
channel.messages.delete(msg.client.stickiedMessage).catch(() => {});
|
||||
|
||||
msg.client.stickiedMessage = message.id;
|
||||
}, config.sticky.timeout);
|
||||
}
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
import setMuteTimeout from '../utils/setMuteTimeout.js';
|
||||
|
||||
export default {
|
||||
name: Events.ClientReady,
|
||||
once: false,
|
||||
async execute(_, config, client) {
|
||||
console.log('Client is ready. Reloading mutes.');
|
||||
|
||||
const mutes = await client.db.collection('muted').find().toArray();
|
||||
|
||||
for (const mute of mutes) {
|
||||
await setMuteTimeout(mute, client, config);
|
||||
}
|
||||
|
||||
console.log(`Loaded ${mutes.length} mutes.`);
|
||||
}
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Events } from 'discord.js';
|
||||
|
||||
export default {
|
||||
name: Events.ThreadCreate,
|
||||
once: false,
|
||||
async execute(helper, _, thread) {
|
||||
helper.scanText(thread.name.toLowerCase(), thread.id);
|
||||
}
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
import {
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle
|
||||
} from 'discord.js';
|
||||
|
||||
export default {
|
||||
name: 'aiResponse',
|
||||
once: false,
|
||||
async execute(client, config, helper, aiRes) {
|
||||
if (!aiRes.response) return;
|
||||
if (!aiRes.response[0]) return;
|
||||
|
||||
try {
|
||||
const ids = aiRes.id.split('/');
|
||||
|
||||
const intent = aiRes.response.reduce((a, b) =>
|
||||
a.confidence > b.confidence ? a : b
|
||||
);
|
||||
|
||||
const response = config.responses.find(
|
||||
(res) => res.label === intent.name
|
||||
);
|
||||
|
||||
if (response.threshold > intent.confidence) return;
|
||||
if (!response.reply) return;
|
||||
|
||||
const embed = response.reply;
|
||||
const feedbackRow = new ActionRowBuilder().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId('fb-like')
|
||||
.setEmoji('👍')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
new ButtonBuilder()
|
||||
.setCustomId('fb-dislike')
|
||||
.setEmoji('👎')
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
);
|
||||
|
||||
let channel = client.channels.cache.get(ids[0]);
|
||||
|
||||
if (!channel) {
|
||||
await client.channels.fetch(ids[0]);
|
||||
channel = client.channels.cache.get(ids[0]);
|
||||
}
|
||||
|
||||
if (!ids[1]) {
|
||||
// This means that it's a thread/forum.
|
||||
const threadEditOption = { locked: false, archived: false };
|
||||
if (response.closeThread) {
|
||||
threadEditOption.archived = true;
|
||||
}
|
||||
|
||||
if (response.lockThread) {
|
||||
threadEditOption.locked = true;
|
||||
}
|
||||
|
||||
await channel.send({
|
||||
embeds: [embed],
|
||||
components: [feedbackRow]
|
||||
});
|
||||
|
||||
channel.edit(threadEditOption);
|
||||
} else {
|
||||
let message = channel.messages.cache.get(ids[1]);
|
||||
|
||||
if (!message) {
|
||||
await channel.messages.fetch(ids[1]);
|
||||
message = channel.messages.cache.get(ids[1]);
|
||||
}
|
||||
|
||||
const firstMsg = (await channel.messages.fetch()).first();
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
components: [feedbackRow]
|
||||
});
|
||||
|
||||
if (firstMsg == message) {
|
||||
const threadEditOption = { locked: false, archived: false };
|
||||
if (response.closeThread) {
|
||||
threadEditOption.archived = true;
|
||||
}
|
||||
|
||||
if (response.lockThread) {
|
||||
threadEditOption.locked = true;
|
||||
}
|
||||
|
||||
channel.edit(threadEditOption);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
export default {
|
||||
name: 'ocrResponse',
|
||||
once: false,
|
||||
async execute(client, config, helper, ocrRes) {
|
||||
try {
|
||||
const ids = ocrRes.id.split('/');
|
||||
let channel = client.channels.cache.get(ids[0]);
|
||||
|
||||
if (!channel) {
|
||||
await client.channels.fetch(ids[0]);
|
||||
channel = client.channels.cache.get(ids[0]);
|
||||
}
|
||||
|
||||
let message = channel.messages.cache.get(ids[1]);
|
||||
|
||||
if (!message) {
|
||||
await channel.messages.fetch(ids[1]);
|
||||
message = channel.messages.cache.get(ids[1]);
|
||||
}
|
||||
|
||||
for (const ocrReply of config.ocrResponses) {
|
||||
if (ocrRes.ocrText.match(ocrReply.regex)) {
|
||||
message.reply({ embeds: [ocrReply.reply] });
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Client, GatewayIntentBits, Collection } from 'discord.js';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
// Fix __dirname not being defined in ES modules. (https://stackoverflow.com/a/64383997)
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
import HelperClient from '@revanced-helper/helper-client';
|
||||
import config from './config.json' assert { type: 'json' };
|
||||
import { MongoClient } from 'mongodb';
|
||||
const helper = new HelperClient(config);
|
||||
const mongoDBClient = new MongoClient(process.env.MONGODB_URI);
|
||||
|
||||
await mongoDBClient.connect();
|
||||
helper.connect();
|
||||
|
||||
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent
|
||||
]
|
||||
});
|
||||
|
||||
client.commands = new Collection();
|
||||
client.msgCommands = new Collection();
|
||||
client.trainingVotes = new Collection();
|
||||
client.stickiedMessage = null;
|
||||
client.stickiedMessageTimeout = null;
|
||||
client.db = mongoDBClient.db('revanced_discord_bot');
|
||||
client.mutes = new Collection();
|
||||
|
||||
const commandsPath = join(__dirname, 'commands');
|
||||
const commandFiles = readdirSync(commandsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = join(commandsPath, file);
|
||||
const command = (await import(`file://${filePath}`)).default;
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(
|
||||
`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const msgCommandsPath = join(__dirname, 'msgCommands');
|
||||
const msgCommandFiles = readdirSync(msgCommandsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of msgCommandFiles) {
|
||||
const filePath = join(msgCommandsPath, file);
|
||||
const command = (await import(`file://${filePath}`)).default;
|
||||
if ('name' in command && 'execute' in command) {
|
||||
client.msgCommands.set(command.name, command);
|
||||
} else {
|
||||
console.log(
|
||||
`[WARNING] The command at ${filePath} is missing a required "name" or "execute" property.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const discordEventsPath = join(__dirname, 'events');
|
||||
const discordEventFiles = readdirSync(discordEventsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of discordEventFiles) {
|
||||
const filePath = join(discordEventsPath, file);
|
||||
const event = (await import(`file://${filePath}`)).default;
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) =>
|
||||
event.execute(helper, config, ...args)
|
||||
);
|
||||
} else {
|
||||
client.on(event.name, (...args) =>
|
||||
event.execute(helper, config, ...args)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The ReVanced Helper events.
|
||||
|
||||
const helperEventsPath = join(__dirname, 'helperEvents');
|
||||
const helperEventFiles = readdirSync(helperEventsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of helperEventFiles) {
|
||||
const filePath = join(helperEventsPath, file);
|
||||
const event = (await import(`file://${filePath}`)).default;
|
||||
if (event.once) {
|
||||
helper.once(event.name, (...args) =>
|
||||
event.execute(client, config, helper, ...args)
|
||||
);
|
||||
} else {
|
||||
helper.on(event.name, (...args) =>
|
||||
event.execute(client, config, helper, ...args)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
client.login(process.env.DISCORD_TOKEN);
|
||||
@@ -1,40 +0,0 @@
|
||||
import exileMemberToChannel from '../utils/exileMemberToChannel.js';
|
||||
import { checkForPerms } from '../utils/checkSupporterPerms.js'
|
||||
import muteMember from '../utils/muteMember.js';
|
||||
import reportToLogs from '../utils/reportToLogs.js';
|
||||
|
||||
export default {
|
||||
name: 'exile',
|
||||
async execute(msg, args, config) {
|
||||
if (!checkForPerms(config, msg.member)) return msg.reply('You don\'t have the permission to do this.');
|
||||
|
||||
if (!msg.reference) return msg.reply('You did not reply to anyone!');
|
||||
const referencedMsg = await msg.channel.messages.fetch(msg.reference.messageId);
|
||||
let message = referencedMsg.content;
|
||||
if (args && args[0]) {
|
||||
if (isNaN(args[0])) return msg.reply('The argument you entered is not a number!');
|
||||
|
||||
const msgsByAuthor = (await msg.channel.messages.fetch({ limit: 50 })).filter(
|
||||
m => m.author.id === referencedMsg.author.id
|
||||
).map(m => m.content);
|
||||
message = msgsByAuthor.slice(Number(`-${args[0]}`));
|
||||
}
|
||||
|
||||
const parsedDuration = await muteMember(config, referencedMsg.member, {
|
||||
supportMute: true,
|
||||
guild: msg.guild
|
||||
});
|
||||
|
||||
exileMemberToChannel(referencedMsg.author, msg.channel, message, null, config, false);
|
||||
|
||||
reportToLogs(config, msg.client, 'exiled', null, {
|
||||
reason: null,
|
||||
actionTo: referencedMsg.author,
|
||||
actionBy: msg.member,
|
||||
channel: msg.channel,
|
||||
expire: parsedDuration
|
||||
}, null, msg);
|
||||
|
||||
await referencedMsg.delete();
|
||||
}
|
||||
}
|
||||
1335
apps/bot-discord/src/package-lock.json
generated
1335
apps/bot-discord/src/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "bot-discord",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
"author": "Reis Can",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@revanced-helper/helper-client": "file:../../../packages/client",
|
||||
"decancer": "^1.6.5",
|
||||
"discord.js": "^14.11.0",
|
||||
"mongodb": "^5.7.0",
|
||||
"parse-duration": "^1.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { REST, Routes } from 'discord.js';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
const configJSON = readFileSync('./config.json', 'utf-8');
|
||||
const config = JSON.parse(configJSON);
|
||||
|
||||
const commands = [];
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandFiles = readdirSync('./commands').filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const command = await import(`./commands/${file}`);
|
||||
if (!command.default.data.toJSON) continue;
|
||||
commands.push(command.default.data.toJSON());
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`Started refreshing ${commands.length} application (/) commands.`
|
||||
);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(Routes.applicationCommands(config.discord.botId), {
|
||||
body: commands
|
||||
});
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function checkForPerms(config, member) {
|
||||
for (const role of config.discord.modRoles) {
|
||||
if (member.roles.cache.has(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
export function checkForPerms(config, member) {
|
||||
for (const role of config.discord.trainRoles) {
|
||||
if (member.roles.cache.has(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import decancer from 'decancer';
|
||||
|
||||
export default async function cureUsername(member) {
|
||||
let toCure = member.user.displayName;
|
||||
if (member.nickname) toCure = member.nickname;
|
||||
|
||||
if (!/^[\x20-\x7D]+$/.test(toCure)) {
|
||||
// Cure username/nickname if there's non-ascii characters.
|
||||
|
||||
let curedName = decancer(toCure).toString();
|
||||
|
||||
// If the output is empty, rename them to "ReVanced member".
|
||||
if (/^\s*$/.test(curedName)) curedName = 'ReVanced member';
|
||||
|
||||
member.setNickname(curedName);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
export default async function exileMemberToChannel(member, channel, message, reason, config) {
|
||||
const redirectChannel = await channel.client.channels.fetch(config.discord.supportChannel);
|
||||
|
||||
let messageContent = '';
|
||||
if (Array.isArray(message)) {
|
||||
for (const msg of message) {
|
||||
messageContent += `${msg}\n`;
|
||||
}
|
||||
} else if (!message) {
|
||||
message = 'No message provided'
|
||||
} else messageContent = message;
|
||||
|
||||
const embedFields = [
|
||||
{
|
||||
name: 'Orginal message',
|
||||
value: messageContent
|
||||
}
|
||||
];
|
||||
|
||||
if (reason) {
|
||||
embedFields.push(
|
||||
{
|
||||
name: 'Reason',
|
||||
value: reason
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
await redirectChannel.send({
|
||||
content: `<@${member.id}>`,
|
||||
embeds: [
|
||||
{
|
||||
title: `Restricted to <#${redirectChannel.id}>`,
|
||||
fields: embedFields,
|
||||
thumbnail: {
|
||||
url: member.avatarURL()
|
||||
},
|
||||
footer: {
|
||||
text: 'ReVanced',
|
||||
icon_url: channel.client.user.avatarURL()
|
||||
},
|
||||
color: 5150960
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import parse from 'parse-duration'
|
||||
import setMuteTimeout from './setMuteTimeout.js';
|
||||
|
||||
parse['mo'] = parse['month']
|
||||
|
||||
export default async function muteMember(config, member, { duration, reason, supportMute, guild }) {
|
||||
let expires;
|
||||
|
||||
if (supportMute) {
|
||||
expires = Math.floor((Date.now() + config.discord.mute.supportMuteDuration) / 1000);
|
||||
} else {
|
||||
const parsedDuration = parse(duration);
|
||||
expires = Math.floor((Date.now() + parsedDuration) / 1000);
|
||||
}
|
||||
|
||||
const takenRoles = [];
|
||||
for (const takeRole of supportMute ?
|
||||
config.discord.mute.supportTakeRoles :
|
||||
config.discord.mute.takeRoles
|
||||
) {
|
||||
if (member.roles.cache.get(takeRole)) {
|
||||
takenRoles.push(takeRole);
|
||||
}
|
||||
}
|
||||
|
||||
const existingMute = await guild.client.db.collection('muted').findOne({
|
||||
guild_id: guild.id,
|
||||
user_id: member.id
|
||||
});
|
||||
|
||||
if (existingMute) {
|
||||
// Update existing mute
|
||||
|
||||
await guild.client.db.collection('muted').updateOne({
|
||||
guild_id: guild.id,
|
||||
user_id: member.id
|
||||
}, {
|
||||
$set: {
|
||||
reason,
|
||||
expires,
|
||||
support_mute: supportMute
|
||||
}
|
||||
});
|
||||
|
||||
if (guild.client.mutes.has(member.id)) {
|
||||
clearTimeout(guild.client.mutes.get(member.id))
|
||||
guild.client.mutes.delete(member.id);
|
||||
}
|
||||
} else {
|
||||
await guild.client.db.collection('muted').insertOne({
|
||||
guild_id: guild.id,
|
||||
user_id: member.id,
|
||||
taken_roles: takenRoles,
|
||||
expires,
|
||||
reason,
|
||||
support_mute: supportMute
|
||||
});
|
||||
}
|
||||
|
||||
// Remove the roles, give defined roles.
|
||||
if (!existingMute) {
|
||||
const currentRoles = member.roles.cache.map((role) => role.id);
|
||||
let setRoles = [];
|
||||
for (const role of currentRoles) {
|
||||
if (takenRoles.includes(role)) continue;
|
||||
setRoles.push(role);
|
||||
}
|
||||
|
||||
setRoles = setRoles.concat(supportMute ?
|
||||
config.discord.mute.supportGiveRoles :
|
||||
config.discord.mute.giveRoles)
|
||||
await member.roles.set(setRoles);
|
||||
}
|
||||
|
||||
|
||||
// Start a timeout.
|
||||
setMuteTimeout({
|
||||
guild_id: guild.id,
|
||||
user_id: member.id,
|
||||
taken_roles: takenRoles,
|
||||
expires,
|
||||
support_mute: supportMute
|
||||
}, guild.client, config);
|
||||
|
||||
// Return parsed time for the mute command to resolve.
|
||||
return expires;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { EmbedBuilder, messageLink } from 'discord.js';
|
||||
|
||||
export default async function reportToLogs(config, client, action, message, { reason, expire, actionTo, actionBy }, interaction, commandMsg) {
|
||||
const actionUpper = action.charAt(0).toUpperCase() + action.slice(1);
|
||||
const actionTitle = `${actionUpper} ${actionTo.tag}`;
|
||||
const actionEmbed = new EmbedBuilder()
|
||||
.setThumbnail(actionTo.avatarURL())
|
||||
.setTitle(actionTitle)
|
||||
.setColor(5150960)
|
||||
.setFooter({ text: 'ReVanced', iconURL: client.user.avatarURL() });
|
||||
|
||||
const fields = [
|
||||
{ name: 'Action', value: `${actionTo.toString()} was ${action} by ${actionBy.toString()}` }
|
||||
];
|
||||
|
||||
if (action === 'banned' || action === 'muted' || action === 'exiled') fields.push({
|
||||
name: 'Reason',
|
||||
value: reason ? reason : 'No reason provided',
|
||||
inline: true
|
||||
});
|
||||
|
||||
if (expire) fields.push({ name: 'Expires', value: `<t:${expire}:F>`, inline: true });
|
||||
|
||||
if (message) fields.push({ name: 'Reference', value: `[Jump to message](${messageLink(
|
||||
message.channelId,
|
||||
message.id,
|
||||
message.guild.id)})`,
|
||||
inline: true
|
||||
});
|
||||
|
||||
actionEmbed.setFields(fields);
|
||||
|
||||
if (interaction) {
|
||||
await interaction.editReply({ embeds: [actionEmbed] });
|
||||
const msg = await interaction.fetchReply();
|
||||
reportToLogs(config, client, action, msg, { reason, expire, actionTo, actionBy });
|
||||
} else if (commandMsg) {
|
||||
const msg = await commandMsg.reply({ embeds: [actionEmbed] });
|
||||
reportToLogs(config, client, action, msg, { reason, expire, actionTo, actionBy });
|
||||
} else {
|
||||
const channel = await client.channels.fetch(config.logs.channelId);
|
||||
const thread = await channel.threads.fetch(config.logs.threadId);
|
||||
thread.send({ embeds: [actionEmbed] });
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export default async function setMuteTimeout(mute, client, config) {
|
||||
const duration = (mute.expires - Math.floor(new Date() / 1000)) * 1000;
|
||||
client.mutes.set(mute.user_id, setTimeout(async() => {
|
||||
const guild = await client.guilds.fetch(mute.guild_id);
|
||||
let member;
|
||||
try {
|
||||
member = await guild.members.fetch(mute.user_id);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
member.roles.add(mute.taken_roles);
|
||||
member.roles.remove(
|
||||
mute.support_mute ?
|
||||
config.discord.mute.supportGiveRoles :
|
||||
config.discord.mute.giveRoles
|
||||
);
|
||||
|
||||
client.db.collection('muted').deleteOne({
|
||||
user_id: mute.user_id,
|
||||
guild_id: mute.guild_id
|
||||
});
|
||||
}, duration));
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
StringSelectMenuBuilder,
|
||||
ComponentType
|
||||
} from 'discord.js';
|
||||
|
||||
export default async function trainAISelectMenu(
|
||||
interaction,
|
||||
config,
|
||||
helper
|
||||
) {
|
||||
const options = [];
|
||||
|
||||
for (const { label } of config.responses) {
|
||||
options.push({
|
||||
label: label,
|
||||
description: `The ${label} label.`,
|
||||
value: label.toLowerCase()
|
||||
});
|
||||
}
|
||||
|
||||
const row = new ActionRowBuilder().addComponents(
|
||||
new StringSelectMenuBuilder()
|
||||
.setCustomId('select')
|
||||
.setPlaceholder('Nothing selected')
|
||||
.addOptions(options)
|
||||
);
|
||||
|
||||
let interactedMessage;
|
||||
|
||||
if (!interaction.isMessageContextMenuCommand()) {
|
||||
try {
|
||||
const channel = await interaction.client.channels.fetch(interaction.message.reference.channelId);
|
||||
const message = await channel.messages.fetch(interaction.message.reference.messageId);
|
||||
interactedMessage = message.content.toLowerCase();
|
||||
} catch (e) {
|
||||
interaction.reply({
|
||||
content: 'The message that you wanted to train the bot with was deleted.',
|
||||
ephemeral: true
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
interactedMessage = interaction.targetMessage.content.toLowerCase()
|
||||
}
|
||||
|
||||
const reply = await interaction.reply({
|
||||
content: 'Please select the corresponding label to train the bot.',
|
||||
components: [row],
|
||||
ephemeral: true
|
||||
});
|
||||
|
||||
const collector = reply.createMessageComponentCollector({
|
||||
componentType: ComponentType.StringSelect,
|
||||
time: 30000
|
||||
});
|
||||
|
||||
const voteId = interaction.targetMessage ? interaction.targetMessage.id :
|
||||
interaction.message.reference.messageId;
|
||||
collector.on('collect', (i) => {
|
||||
interaction.editReply({ content: 'Sent training data to server.', components: [] });
|
||||
|
||||
const existingVote = interaction.client.trainingVotes.get(voteId);
|
||||
if (existingVote) clearTimeout(existingVote);
|
||||
interaction.client.trainingVotes.set(voteId,
|
||||
setTimeout(() => {
|
||||
helper.sendTrainData(interactedMessage, i.values[0]);
|
||||
|
||||
if (!interaction.isMessageContextMenuCommand()) {
|
||||
interaction.message.edit({ components: [] });
|
||||
}
|
||||
|
||||
interaction.client.trainingVotes.delete(voteId);
|
||||
}, 10_000)
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export default async function unmuteMember(config, member, supportMute) {
|
||||
const mute = await member.client.db.collection('muted').findOne({
|
||||
guild_id: member.guild.id,
|
||||
user_id: member.id
|
||||
});
|
||||
|
||||
if (!mute) return false;
|
||||
if (supportMute) {
|
||||
if (!mute.support_mute) return false;
|
||||
}
|
||||
|
||||
member.roles.remove(mute.support_mute ?
|
||||
config.discord.mute.supportGiveRoles :
|
||||
config.discord.mute.giveRoles
|
||||
);
|
||||
|
||||
member.roles.add(mute.taken_roles);
|
||||
|
||||
await member.client.db.collection('muted').deleteOne({
|
||||
user_id: member.id
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
export default {
|
||||
command: /\/train/,
|
||||
async execute(bot, config, msg) {
|
||||
const admins = await bot.getChatAdministrators(msg.chat.id);
|
||||
const isAdmin = admins.find((admin) => admin.user.id === msg.from.id);
|
||||
|
||||
if (!isAdmin)
|
||||
return bot.sendMessage(msg.chat.id, 'You\'re not an admin.', {
|
||||
message_thread_id: msg.message_thread_id,
|
||||
reply_to_message_id: msg.message_id
|
||||
});
|
||||
|
||||
if (msg.reply_to_message.message_id === msg.message_thread_id)
|
||||
return bot.sendMessage(msg.chat.id, 'Please reply to a message!', {
|
||||
message_thread_id: msg.message_thread_id,
|
||||
reply_to_message_id: msg.message_id
|
||||
});
|
||||
|
||||
const options = [];
|
||||
let arrI = 0;
|
||||
let i = 0;
|
||||
for (const { label } of config.responses) {
|
||||
if (arrI === 0 && i === 0) {
|
||||
options.push([
|
||||
{
|
||||
text: label,
|
||||
callback_data: `label_${label.toLowerCase()}`
|
||||
}
|
||||
]);
|
||||
i++;
|
||||
} else if (i === 2) {
|
||||
options.push([
|
||||
{
|
||||
text: label,
|
||||
callback_data: `label_${label.toLowerCase()}`
|
||||
}
|
||||
]);
|
||||
i = 0;
|
||||
arrI++;
|
||||
} else {
|
||||
options[arrI].push({
|
||||
text: label,
|
||||
callback_data: `label_${label.toLowerCase()}`
|
||||
});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'Please select the corresponding label to train the bot.',
|
||||
{
|
||||
message_thread_id: msg.message_thread_id,
|
||||
reply_to_message_id: msg.reply_to_message.message_id,
|
||||
reply_markup: {
|
||||
inline_keyboard: options
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
{
|
||||
"server": {
|
||||
"port": 3000,
|
||||
"host": "127.0.0.1"
|
||||
},
|
||||
"responses": [
|
||||
{
|
||||
"label": "revanced_crash",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why is my patched app crashing?",
|
||||
"description": "It seems like your patched application is crashing, these might be the reason why:\n * You patched a non-suggested version.\n * Patches failed to apply.\n * Non-default patches were used.\n * Split APK was used."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "rvmanager_abort",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why is ReVanced Manager aborting?",
|
||||
"description": "It seems like your Manager is aborting. If you see the text \"exit code = 135\" or \"exit code = 139\", then your device's architecture is not supported. Please patch on another device or on your PC."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_download",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "How to download ReVanced?",
|
||||
"description": "ReVanced is a patcher. You can use the [ReVanced Manager](https://github.com/revanced/revanced-manager/releases/latest) or the [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app you want to modify.\n* [ReVanced Manager docs](https://github.com/revanced/revanced-manager/tree/main/docs#-revanced-manager)\n* [ReVanced CLI docs](https://github.com/revanced/revanced-cli/tree/main/docs#-documentation-and-guides-of-revanced-cli)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "androidtv_support",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Will YT ReVanced support Android TVs?",
|
||||
"description": "YouTube ReVanced for Android/Google TVs won't be made. There are alternatives, such as [SmartTubeNext](https://github.com/yuliskov/SmartTubeNext#smarttube)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_nodownloader",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why can't I download videos?",
|
||||
"description": "You need to install a downloader to download videos. NewPipe is the default downloader, and it can be downloaded from [here](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "revanced_casting",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why can't I cast?",
|
||||
"description": "Because of devices like Chromecast, Android/Google TVs using the Cast v2 API, casting to those devices won't work."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "microg_download",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "From where can I download Vanced MicroG?",
|
||||
"description": "You can download Vanced MicroG from [here](https://github.com/TeamVanced/VancedMicroG/releases/latest)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "microg_nointernet",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why does YT ReVanced say I'm offline?",
|
||||
"description": "If your YT ReVanced appears to not have internet and you recently changed your Google password, remove the Vanced MicroG account from within your device settings and then log back in."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "rvdownload_unofficial",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "What are the official ReVanced sites?",
|
||||
"description": "The official sites for ReVanced are [revanced.app](https://revanced.app) and [github.com/revanced](https://github.com/revanced)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "yt_buffering",
|
||||
"threshold": 0.85,
|
||||
"reply": {
|
||||
"title": "Why do videos fail to play?",
|
||||
"description": "Check [this announcement](https://t.me/app_revanced/19)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "false",
|
||||
"threshold": 0,
|
||||
"reply": null
|
||||
}
|
||||
],
|
||||
"ocrResponses": [
|
||||
{
|
||||
"regex": "is not installed",
|
||||
"reply": {
|
||||
"title": "Why can't I download videos?",
|
||||
"description": "You need to install a downloader to download videos. NewPipe is the default downloader, and it can be downloaded from [here](https://github.com/TeamNewPipe/NewPipe/releases/latest)."
|
||||
}
|
||||
},
|
||||
{
|
||||
"regex": "You're offline|Please check your",
|
||||
"reply": {
|
||||
"title": "Why does YT ReVanced say I'm offline?",
|
||||
"description": "If your YT ReVanced appears to not have internet and you recently changed your Google password, remove the Vanced MicroG account from within your device settings and then log back in."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
export default {
|
||||
name: 'callback_query',
|
||||
once: false,
|
||||
async execute(bot, helper, cb) {
|
||||
const admins = await bot.getChatAdministrators(cb.message.chat.id);
|
||||
const isAdmin = admins.find((admin) => admin.user.id === cb.from.id);
|
||||
|
||||
if (!isAdmin)
|
||||
return bot.sendMessage(cb.message.chat.id, 'You\'re not an admin.', {
|
||||
message_thread_id: cb.message.message_thread_id,
|
||||
reply_to_message_id: cb.message.message_id
|
||||
});
|
||||
|
||||
helper.sendTrainData(
|
||||
cb.message.reply_to_message.text.toLowerCase(),
|
||||
cb.data.replace('label_', '').toUpperCase()
|
||||
);
|
||||
|
||||
bot.sendMessage(cb.message.chat.id, 'Sent training data to server.', {
|
||||
message_thread_id: cb.message.message_thread_id,
|
||||
reply_to_message_id: cb.message.message_id
|
||||
});
|
||||
|
||||
bot.deleteMessage(cb.message.chat.id, cb.message.message_id);
|
||||
}
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
export default {
|
||||
name: 'message',
|
||||
once: false,
|
||||
async execute(bot, helper, msg) {
|
||||
if (msg.photo) {
|
||||
const fileLink = await bot.getFileLink(msg.photo.at(-1).file_id);
|
||||
helper.scanImage(fileLink, `${msg.chat.id}/${msg.message_thread_id}/${msg.message_id}`)
|
||||
}
|
||||
if (!msg.text) return;
|
||||
helper.scanText(
|
||||
msg.text.toLowerCase(),
|
||||
`${msg.chat.id}/${msg.message_thread_id}/${msg.message_id}`
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
export default {
|
||||
name: 'aiResponse',
|
||||
once: false,
|
||||
async execute(bot, config, aiRes) {
|
||||
if (!aiRes.response) return;
|
||||
if (!aiRes.response[0]) return;
|
||||
const ids = aiRes.id.split('/');
|
||||
const intent = aiRes.response.reduce((a, b) =>
|
||||
a.confidence > b.confidence ? a : b
|
||||
);
|
||||
const response = config.responses.find((res) => res.label === intent.name);
|
||||
if (!response) return;
|
||||
if (response.threshold > intent.confidence) return;
|
||||
if (!response.reply) return;
|
||||
|
||||
bot.sendMessage(
|
||||
ids[0],
|
||||
`**${response.reply.title}**\n\n${response.reply.description}\n`,
|
||||
{
|
||||
message_thread_id: ids[1],
|
||||
reply_to_message_id: ids[2],
|
||||
parse_mode: 'markdown'
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
export default {
|
||||
name: 'ocrResponse',
|
||||
once: false,
|
||||
async execute(bot, config, ocrRes) {
|
||||
const ids = ocrRes.id.split('/');
|
||||
|
||||
for (const ocrReply of config.ocrResponses) {
|
||||
if (ocrRes.ocrText.match(ocrReply.regex)) {
|
||||
|
||||
bot.sendMessage(
|
||||
ids[0],
|
||||
`## ${ocrReply.reply.title}\n\n${ocrReply.reply.description}`,
|
||||
{
|
||||
message_thread_id: ids[1],
|
||||
reply_to_message_id: ids[2],
|
||||
parse_mode: 'markdown'
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import TelegramBot from 'node-telegram-bot-api';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
// Fix __dirname not being defined in ES modules. (https://stackoverflow.com/a/64383997)
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
import HelperClient from '@revanced-helper/helper-client';
|
||||
import config from './config.json' assert { type: 'json' };
|
||||
|
||||
const helper = new HelperClient(config);
|
||||
helper.connect();
|
||||
|
||||
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, { polling: true });
|
||||
|
||||
const commandsPath = join(__dirname, 'commands');
|
||||
const commandFiles = readdirSync(commandsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = join(commandsPath, file);
|
||||
const command = (await import(`file://${filePath}`)).default;
|
||||
if ('command' in command && 'execute' in command) {
|
||||
bot.onText(command.command, (...args) =>
|
||||
command.execute(bot, config, ...args)
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const tgEventsPath = join(__dirname, 'events');
|
||||
const tgEventFiles = readdirSync(tgEventsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of tgEventFiles) {
|
||||
const filePath = join(tgEventsPath, file);
|
||||
const event = (await import(`file://${filePath}`)).default;
|
||||
if (event.once) {
|
||||
bot.once(event.name, (...args) => event.execute(bot, helper, ...args));
|
||||
} else {
|
||||
bot.on(event.name, (...args) => event.execute(bot, helper, ...args));
|
||||
}
|
||||
}
|
||||
|
||||
// The ReVanced Helper events.
|
||||
|
||||
const helperEventsPath = join(__dirname, 'helperEvents');
|
||||
const helperEventFiles = readdirSync(helperEventsPath).filter((file) =>
|
||||
file.endsWith('.js')
|
||||
);
|
||||
|
||||
for (const file of helperEventFiles) {
|
||||
const filePath = join(helperEventsPath, file);
|
||||
const event = (await import(`file://${filePath}`)).default;
|
||||
if (event.once) {
|
||||
helper.once(event.name, (...args) => event.execute(bot, config, ...args));
|
||||
} else {
|
||||
helper.on(event.name, (...args) => event.execute(bot, config, ...args));
|
||||
}
|
||||
}
|
||||
2302
apps/bot-telegram/src/package-lock.json
generated
2302
apps/bot-telegram/src/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "bot-telegram",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Reis Can",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@revanced-helper/helper-client": "file:../../../packages/client",
|
||||
"node-telegram-bot-api": "^0.61.0"
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
# Server Protocol
|
||||
|
||||
The server uses TCP for connection and BSON for messages, so you need to serialize and deserialize the messages.
|
||||
|
||||
## AI
|
||||
|
||||
Sending the server this JSON (BSON) will send you back the AI predictions.
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 1,
|
||||
"id": "String",
|
||||
"text": "How do i download ReVanced?"
|
||||
}
|
||||
```
|
||||
|
||||
And the server would return something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 2,
|
||||
"id": "String",
|
||||
"response": [
|
||||
{
|
||||
"confidence": 0.99,
|
||||
"name": "revanced_download"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Training the AI
|
||||
|
||||
To train the AI, send the server a JSON (BSON) like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 3,
|
||||
"label": "revanced_download",
|
||||
"text": "how to download revanced"
|
||||
}
|
||||
```
|
||||
|
||||
## OCR
|
||||
|
||||
Sending the server this JSON (BSON) will send you back the read text.
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 5,
|
||||
"id": "String",
|
||||
"url": "https://cdn.discordapp.com/attachments/1033338556493606963/1033338557231796224/Screenshot_20221022-121318.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
And the server would return something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"op": 6,
|
||||
"id": "String",
|
||||
"ocrText": "..."
|
||||
}
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"regexSupport": false,
|
||||
"regexResponses": [
|
||||
{
|
||||
"label": "videobuffer",
|
||||
"regex": "insert regex here"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import runAI from './runAI.js';
|
||||
import runOCR from './runOCR.js';
|
||||
import trainAI from './trainAI.js';
|
||||
|
||||
export { runAI, runOCR, trainAI };
|
||||
@@ -1,43 +0,0 @@
|
||||
import { serialize } from 'bson';
|
||||
|
||||
export default async function runAI(client, data, config) {
|
||||
if (config.regexSupport) {
|
||||
for (const reply of config.regexResponses) {
|
||||
if (new RegExp(reply.regex).test(data.text)) {
|
||||
client.write(
|
||||
serialize({
|
||||
op: 2,
|
||||
id: data.id,
|
||||
response: [
|
||||
{
|
||||
confidence: 1,
|
||||
name: reply.label
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const witAIReq = await fetch(
|
||||
`https://api.wit.ai/message?v=20230215&q=${encodeURI(data.text)}`,
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${process.env.WIT_AI_TOKEN}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const response = await witAIReq.json();
|
||||
|
||||
client.write(
|
||||
serialize({
|
||||
op: 2,
|
||||
id: data.id,
|
||||
response: response.intents
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { createWorker } from 'tesseract.js';
|
||||
import { serialize } from 'bson';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
const worker = await createWorker();
|
||||
await worker.loadLanguage('eng');
|
||||
await worker.initialize('eng');
|
||||
|
||||
async function recognize({ client, data }) {
|
||||
const { data: { text } } = await worker.recognize(data.url);
|
||||
|
||||
client.write(
|
||||
serialize({
|
||||
op: 6,
|
||||
id: data.id,
|
||||
ocrText: text
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
class Queue extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.isRunning = false;
|
||||
this.items = []
|
||||
}
|
||||
|
||||
push(item) {
|
||||
this.items.push(item);
|
||||
this.emit('item', item)
|
||||
}
|
||||
|
||||
shift() {
|
||||
return this.items.shift();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const queue = new Queue();
|
||||
|
||||
queue.on('item', async ({ client, data }) => {
|
||||
if (!queue.isRunning) {
|
||||
queue.isRunning = true;
|
||||
await recognize(queue.items.shift());
|
||||
queue.isRunning = false;
|
||||
queue.emit('finished');
|
||||
}
|
||||
});
|
||||
|
||||
queue.on('finished', async () => {
|
||||
if (queue.items.length !== 0) {
|
||||
queue.isRunning = true;
|
||||
await recognize(queue.items.shift());
|
||||
queue.isRunning = false;
|
||||
queue.emit('finished');
|
||||
}
|
||||
});
|
||||
|
||||
export default async function runOCR(client, data) {
|
||||
queue.push({ client, data });
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
export default async function trainAI(data) {
|
||||
fetch('https://api.wit.ai/utterances', {
|
||||
headers: {
|
||||
authorization: `Bearer ${process.env.WIT_AI_TOKEN}`
|
||||
},
|
||||
body: JSON.stringify([
|
||||
{
|
||||
text: data.text,
|
||||
intent: data.label,
|
||||
entities: [],
|
||||
traits: []
|
||||
}
|
||||
]),
|
||||
method: 'POST'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { createServer } from 'node:net';
|
||||
import { deserialize } from 'bson';
|
||||
import { runAI, runOCR, trainAI } from './events/index.js';
|
||||
import Config from './config.json' assert { type: 'json' };
|
||||
|
||||
const server = createServer(async (client) => {
|
||||
client.on('data', async (data) => {
|
||||
const eventData = deserialize(data, {
|
||||
allowObjectSmallerThanBufferSize: true
|
||||
});
|
||||
|
||||
switch (eventData.op) {
|
||||
case 1: {
|
||||
runAI(client, eventData, Config);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
trainAI(eventData);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
await runOCR(client, eventData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(process.env.PORT || 3000);
|
||||
228
apps/server/src/package-lock.json
generated
228
apps/server/src/package-lock.json
generated
@@ -1,228 +0,0 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"bson": "^5.3.0",
|
||||
"tesseract.js": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="
|
||||
},
|
||||
"node_modules/bson": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz",
|
||||
"integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==",
|
||||
"engines": {
|
||||
"node": ">=14.20.1"
|
||||
}
|
||||
},
|
||||
"node_modules/idb-keyval": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
||||
"integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="
|
||||
},
|
||||
"node_modules/is-electron": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="
|
||||
},
|
||||
"node_modules/is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/opencollective-postinstall": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
|
||||
"bin": {
|
||||
"opencollective-postinstall": "index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/tesseract.js": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-4.1.0.tgz",
|
||||
"integrity": "sha512-sCfWJtei6Ykc7uLiWs5H4IKDAhwsMauEVDwUiSkEpzMo4kH+7n8QDBPPNRtGJoZ4NJBf1WSlcbU+Puf64GjOfw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bmp-js": "^0.1.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"is-electron": "^2.2.2",
|
||||
"is-url": "^1.2.4",
|
||||
"node-fetch": "^2.6.9",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"tesseract.js-core": "^4.0.4",
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"zlibjs": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/tesseract.js-core": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-4.0.4.tgz",
|
||||
"integrity": "sha512-MJ+vtktjAaT0681uPl6TDUPhbRbpD/S9emko5rtorgHRZpQo7R3BG7h+3pVHgn1KjfNf1bvnx4B7KxEK8YKqpg=="
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.5.1.tgz",
|
||||
"integrity": "sha512-GHr23qmuehNXHY4902/hJ6EV5sUANIJC3R/yMfQ7hWDg3nfhlcJfnIL96R2ohpIwa62araN6aN4bLzzzq5GXkg=="
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zlibjs": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
|
||||
"integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
"integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="
|
||||
},
|
||||
"bson": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-5.3.0.tgz",
|
||||
"integrity": "sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag=="
|
||||
},
|
||||
"idb-keyval": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
|
||||
"integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="
|
||||
},
|
||||
"is-electron": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="
|
||||
},
|
||||
"is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"opencollective-postinstall": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"tesseract.js": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-4.1.0.tgz",
|
||||
"integrity": "sha512-sCfWJtei6Ykc7uLiWs5H4IKDAhwsMauEVDwUiSkEpzMo4kH+7n8QDBPPNRtGJoZ4NJBf1WSlcbU+Puf64GjOfw==",
|
||||
"requires": {
|
||||
"bmp-js": "^0.1.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"is-electron": "^2.2.2",
|
||||
"is-url": "^1.2.4",
|
||||
"node-fetch": "^2.6.9",
|
||||
"opencollective-postinstall": "^2.0.3",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"tesseract.js-core": "^4.0.4",
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"zlibjs": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"tesseract.js-core": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-4.0.4.tgz",
|
||||
"integrity": "sha512-MJ+vtktjAaT0681uPl6TDUPhbRbpD/S9emko5rtorgHRZpQo7R3BG7h+3pVHgn1KjfNf1bvnx4B7KxEK8YKqpg=="
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"wasm-feature-detect": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.5.1.tgz",
|
||||
"integrity": "sha512-GHr23qmuehNXHY4902/hJ6EV5sUANIJC3R/yMfQ7hWDg3nfhlcJfnIL96R2ohpIwa62araN6aN4bLzzzq5GXkg=="
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"zlibjs": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
|
||||
"integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"keywords": [],
|
||||
"author": "Reis Can",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"bson": "^5.3.0",
|
||||
"tesseract.js": "^4.1.0"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
1
assets/revanced-logo/revanced-logo-round.svg
Normal file
1
assets/revanced-logo/revanced-logo-round.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="Logo"><g id="Ring"><circle id="Ring-Background" serif:id="Ring Background" cx="400" cy="400" r="400" style="fill:#1b1b1b;"/><path id="Ring1" serif:id="Ring" d="M400,0c220.766,0 400,179.234 400,400c-0,220.766 -179.234,400 -400,400c-220.766,-0 -400,-179.234 -400,-400c0,-220.766 179.234,-400 400,-400Zm-0,36c200.897,-0 364,163.103 364,364c0,200.897 -163.103,364 -364,364c-200.897,0 -364,-163.103 -364,-364c-0,-200.897 163.103,-364 364,-364Z" style="fill:url(#_Linear1);"/></g><g id="Shape"><path id="V-Shape" serif:id="V Shape" d="M538.74,269.872c1.481,-3.382 1.157,-7.283 -0.863,-10.373c-2.021,-3.091 -5.464,-4.954 -9.156,-4.954c-5.148,0 -10.435,0 -14.165,0c-3.1,0 -5.907,1.834 -7.153,4.672c-12.468,28.396 -78.273,178.273 -100.25,228.328c-1.246,2.838 -4.053,4.671 -7.154,4.671c-3.1,0 -5.907,-1.833 -7.153,-4.671c-21.977,-50.055 -87.782,-199.932 -100.25,-228.328c-1.246,-2.838 -4.053,-4.672 -7.153,-4.672c-3.73,0 -9.017,0 -14.164,0c-3.693,0 -7.135,1.863 -9.156,4.954c-2.02,3.09 -2.344,6.991 -0.863,10.373c23.557,53.766 101.872,232.519 117.871,269.034c1.743,3.979 5.674,6.549 10.018,6.549c6.293,-0 15.408,-0 21.701,-0c4.344,-0 8.275,-2.57 10.018,-6.549c15.999,-36.515 94.315,-215.268 117.872,-269.034Z" style="fill:#fff;"/><path id="Diamond" d="M408.119,395.312c-1.675,2.901 -4.77,4.688 -8.119,4.688c-3.349,-0 -6.444,-1.787 -8.119,-4.688c-16.997,-29.44 -56.156,-97.264 -73.153,-126.704c-1.675,-2.901 -1.675,-6.474 0,-9.375c1.675,-2.901 4.77,-4.688 8.119,-4.688c33.995,0 112.311,0 146.306,0c3.349,0 6.444,1.787 8.119,4.688c1.675,2.901 1.675,6.474 -0,9.375c-16.997,29.44 -56.156,97.264 -73.153,126.704Z" style="fill:url(#_Linear2);"/></g></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.89859e-14,800,-800,4.89859e-14,400.001,3.31681e-10)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.77155e-14,289.317,-282.535,1.73003e-14,400,254.545)"><stop offset="0" style="stop-color:#f04e98;stop-opacity:1"/><stop offset="0.5" style="stop-color:#5f65d4;stop-opacity:1"/><stop offset="1" style="stop-color:#4e98f0;stop-opacity:1"/></linearGradient></defs><script xmlns=""/></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
60
biome.json
Normal file
60
biome.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"complexity": {
|
||||
"useLiteralKeys": {
|
||||
"level": "off"
|
||||
}
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": {
|
||||
"level": "off"
|
||||
},
|
||||
"useEnumInitializers": {
|
||||
"level": "off"
|
||||
},
|
||||
"useNodejsImportProtocol": {
|
||||
"level": "off"
|
||||
},
|
||||
"useNumberNamespace": {
|
||||
"level": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2
|
||||
},
|
||||
"parser": {
|
||||
"allowComments": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"lineEnding": "crlf",
|
||||
"arrowParentheses": "asNeeded",
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120,
|
||||
"quoteProperties": "asNeeded",
|
||||
"quoteStyle": "single",
|
||||
"semicolons": "asNeeded",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"include": ["*.js", "*.json", "*.ts"],
|
||||
"ignore": ["dist/**/*", "node_modules/**/*"]
|
||||
}
|
||||
}
|
||||
2
bots/discord/.env.example
Normal file
2
bots/discord/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
DISCORD_TOKEN="YOUR-TOKEN-HERE"
|
||||
DATABASE_PATH=./db.sqlite3
|
||||
185
bots/discord/.gitignore
vendored
Normal file
185
bots/discord/.gitignore
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
# DB
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
.drizzle
|
||||
|
||||
# Auto-generated files
|
||||
src/commands/index.ts
|
||||
src/events/*/index.ts
|
||||
32
bots/discord/.releaserc.js
Normal file
32
bots/discord/.releaserc.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import defineSubprojectReleaseConfig from '../../semantic-release-config.js'
|
||||
|
||||
export default defineSubprojectReleaseConfig({
|
||||
plugins:
|
||||
process.env.RELEASE_WORKFLOW_STEP === 'publish'
|
||||
? [
|
||||
[
|
||||
'@semantic-release/exec',
|
||||
{
|
||||
publishCmd: 'bun run scripts/trigger-portainer-webhook.ts',
|
||||
},
|
||||
],
|
||||
]
|
||||
: [
|
||||
[
|
||||
'@codedependant/semantic-release-docker',
|
||||
{
|
||||
dockerImage: 'revanced-bot-discord',
|
||||
dockerRegistry: 'ghcr.io',
|
||||
dockerProject: 'revanced',
|
||||
dockerContext: '../..',
|
||||
dockerPlatform: ['linux/amd64', 'linux/arm64'],
|
||||
dockerBuildQuiet: false,
|
||||
dockerTags: [
|
||||
'{{#if prerelease.[0]}}dev{{else}}main{{/if}}',
|
||||
'{{#unless prerelease.[0]}}latest{{/unless}}',
|
||||
'{{version}}',
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
})
|
||||
424
bots/discord/CHANGELOG.md
Normal file
424
bots/discord/CHANGELOG.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# @revanced/discord-bot [1.0.0-dev.38](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.37...@revanced/discord-bot@1.0.0-dev.38) (2025-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* run projects with `--bun` ([bb2182e](https://github.com/revanced/revanced-bots/commit/bb2182e707fa40c555d56138972eeea28f1b3cf9))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.37](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.36...@revanced/discord-bot@1.0.0-dev.37) (2025-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord/utils/duration:** fix specified default unit not working ([d138af4](https://github.com/revanced/revanced-bots/commit/d138af46d1f25a11b6f8ab3790ecaa70b1d716a9))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.36](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.0.0-dev.35...@revanced/discord-bot@1.0.0-dev.36) (2025-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord/scripts/build:** check if dist dir exists before cleaning ([c06033e](https://github.com/revanced/revanced-bots/commit/c06033e5730f82438e8052b9b519a8f8e2d25437))
|
||||
* **bots/discord/utils/duration:** make second the default unit ([5d1af3c](https://github.com/revanced/revanced-bots/commit/5d1af3c31c3379b6a13684dfb07583737908c8aa))
|
||||
* **bots/discord:** add GuildMember partial ([8e3946a](https://github.com/revanced/revanced-bots/commit/8e3946a66602838715787090008c7bfaf72b67e9))
|
||||
* **bots/discord:** decrease length of an option in `ban` command ([22d3eea](https://github.com/revanced/revanced-bots/commit/22d3eea88d532792c1237d1a1ab18bc02e57816a))
|
||||
* **bots/discord:** delete expired appliedPresets entries after unapplying ([14c98e8](https://github.com/revanced/revanced-bots/commit/14c98e87df1ec4fd762bbc48ca4c06470cb110a2))
|
||||
* fix typings and formatting ([479812e](https://github.com/revanced/revanced-bots/commit/479812e199b52cdb295a5746e0767306afab3413))
|
||||
* update repo url ([a21aa34](https://github.com/revanced/revanced-bots/commit/a21aa348d7f32cd0ee65b371e9594520c0a9d3f1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add more month aliases to duration parser ([c2009ca](https://github.com/revanced/revanced-bots/commit/c2009ca6d42e4387bc5f375d76ecf72991b7fe32))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.35](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.34...@revanced/discord-bot@1.0.0-dev.35) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** fix freeze on prod builds ([8efb549](https://github.com/revanced/revanced-helper/commit/8efb549453a04fab1ac6414a7f7f8bf702df3c93))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.34](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.33...@revanced/discord-bot@1.0.0-dev.34) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** attempt to fix stuck sticky message timeouts ([3ed5bd1](https://github.com/revanced/revanced-helper/commit/3ed5bd11acc3b4fbd57b0d632c68eb9f77365b8a))
|
||||
* **bots/discord:** fix reload not working ([11582d5](https://github.com/revanced/revanced-helper/commit/11582d50345cae9fb645a65ca4e621596de6a408))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add default durations for moderation commands ([27d3b39](https://github.com/revanced/revanced-helper/commit/27d3b392092141a1e3b4b0298131ff7817458dc1))
|
||||
* **bots/discord:** cure on every event ([8ff6086](https://github.com/revanced/revanced-helper/commit/8ff6086028132cc4b49ee60846e8d6ef909f5a89))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.33](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.32...@revanced/discord-bot@1.0.0-dev.33) (2024-09-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add trigger to context for eval ([b5f4097](https://github.com/revanced/revanced-helper/commit/b5f40975386677ffff343c42f8ffac21f847a0b7))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.32](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.31...@revanced/discord-bot@1.0.0-dev.32) (2024-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** contextify object before sandboxing ([062735f](https://github.com/revanced/revanced-helper/commit/062735f6d552890404d6192244c51a11b0709580))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.31](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.30...@revanced/discord-bot@1.0.0-dev.31) (2024-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** persist changes in context for eval command ([5b4965d](https://github.com/revanced/revanced-helper/commit/5b4965dcc7285676b2b3b6756c249bd56eaf8485))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.30](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.29...@revanced/discord-bot@1.0.0-dev.30) (2024-09-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** improve admin commands ([0346741](https://github.com/revanced/revanced-helper/commit/03467411882b8598e2c06f389a09ef2e201bb43f))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.29](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.28...@revanced/discord-bot@1.0.0-dev.29) (2024-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** fix get response logic ([3261294](https://github.com/revanced/revanced-helper/commit/3261294822b0a9faec094536ed5be2d3e1d5e17b))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.28](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.27...@revanced/discord-bot@1.0.0-dev.28) (2024-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** don't refresh timer if force timer is active for sticky messages ([4abac0c](https://github.com/revanced/revanced-helper/commit/4abac0c890c0548e14cb56723cae919353a8e726))
|
||||
* **bots/discord:** filter out text triggers correctly from image-only scans ([8c0dd67](https://github.com/revanced/revanced-helper/commit/8c0dd67d03d5a1747993da08a5bf82a39de43789))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** correct permission check logic ([dd8872c](https://github.com/revanced/revanced-helper/commit/dd8872c027c7e7e1a00f38d659b4d6e79274238c))
|
||||
* **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd))
|
||||
* **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.27](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.26...@revanced/discord-bot@1.0.0-dev.27) (2024-08-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** give only removed roles for role presets ([522ad28](https://github.com/revanced/revanced-helper/commit/522ad28fd83565e9ca411dbce86c8447574288fd))
|
||||
* **bots/discord:** replace duration parser with a library ([94c4fed](https://github.com/revanced/revanced-helper/commit/94c4fedc06e20051e4123508e3134b97eb84782a))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.26](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.25...@revanced/discord-bot@1.0.0-dev.26) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** correct timer active condition for sticky messages ([96065ff](https://github.com/revanced/revanced-helper/commit/96065ff17584ff99a56ca5008327863ca5a7852b))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.25](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.24...@revanced/discord-bot@1.0.0-dev.25) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** allow access to `context` in `/eval` and await result ([99f65f0](https://github.com/revanced/revanced-helper/commit/99f65f07f5f8830c6e8ea4ae171e986af4d3f1f6))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.24](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.23...@revanced/discord-bot@1.0.0-dev.24) (2024-08-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** do not remove unrelated reactions ([031fd26](https://github.com/revanced/revanced-helper/commit/031fd26b2619ecafeff3964e50accacb87de6108))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.23](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.22...@revanced/discord-bot@1.0.0-dev.23) (2024-08-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add `train` commands ([ee90ef2](https://github.com/revanced/revanced-helper/commit/ee90ef247b4bf2b3c0698606b947116f2dc1b868))
|
||||
* **bots/discord:** update to newer command definition framework ([97f2795](https://github.com/revanced/revanced-helper/commit/97f2795df4ede4d12a08193dba453c1bc765a4c2))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.22](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.21...@revanced/discord-bot@1.0.0-dev.22) (2024-08-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** parse larger units of durations, fix wrong timestamp in mod embed ([6c8dce0](https://github.com/revanced/revanced-helper/commit/6c8dce059366a6ef85f5b8b1794c056515b9f5b6))
|
||||
* **bots/discord:** provide discord token for `reload` command ([dd21a5a](https://github.com/revanced/revanced-helper/commit/dd21a5abad560f3d00b8c58912786d4b6bd520e9))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add code to actually scan text files correctly ([80aeb19](https://github.com/revanced/revanced-helper/commit/80aeb1902063140a2e78cfaed9424e5101ab03f1))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** correct sticky messages logic ([de8bef6](https://github.com/revanced/revanced-helper/commit/de8bef6520d53a1299f0478458320a7eb75c5e1d))
|
||||
* **bots/discord:** make `/eval` work ([eaa25f2](https://github.com/revanced/revanced-helper/commit/eaa25f2eb58a9e2d25bb98633ad668485e099714))
|
||||
* **bots/discord:** some configuration values not applying after running `/reload` ([a976dd2](https://github.com/revanced/revanced-helper/commit/a976dd2accc4b74914651245acde0979c30c92f5))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** await when putting entries into db ([4da6175](https://github.com/revanced/revanced-helper/commit/4da6175cf58b1fa6144bdc71ec806766d32c1025))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.19](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.18...@revanced/discord-bot@1.0.0-dev.19) (2024-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** correct whitelist logic ([49c29be](https://github.com/revanced/revanced-helper/commit/49c29bebfbe348ae4e2cc1b3a83bfa41eb26ccd1))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.18](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.17...@revanced/discord-bot@1.0.0-dev.18) (2024-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** set the `label` property correctly for message scans ([6d463df](https://github.com/revanced/revanced-helper/commit/6d463df586dee5dd8fe8d6cff1c5316f7809b32a))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.17](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.16...@revanced/discord-bot@1.0.0-dev.17) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord/commands/eval:** evaluate in current context ([5925d90](https://github.com/revanced/revanced-helper/commit/5925d902095acef5f6396ca03583a9cbb0862498))
|
||||
* **bots/discord:** send right response for after regexes ([a7688fa](https://github.com/revanced/revanced-helper/commit/a7688fa9b91919a87f74071b502cd0a87cd1c1fa))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** update example config file ([bc9951c](https://github.com/revanced/revanced-helper/commit/bc9951c9b5e007c3e1b3076aa0966ccf29bb18bc))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.16](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.15...@revanced/discord-bot@1.0.0-dev.16) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** open database as read-write ([c366840](https://github.com/revanced/revanced-helper/commit/c36684091dddf67880505dc459e4334a8a5492f4))
|
||||
* **bots/discord:** remove bad text channel checks ([f5939e2](https://github.com/revanced/revanced-helper/commit/f5939e25288fea2022fdeec9085ecb9ffada6111))
|
||||
* **bots/discord:** remove redundant footer for response embeds ([412e003](https://github.com/revanced/revanced-helper/commit/412e00317d1eaca23e9c1375e16f94a5f2fa8d86))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord/commands:** add `reload` command ([6875b32](https://github.com/revanced/revanced-helper/commit/6875b32fd0c6ce3034da9dc6c704d425afb26f2e))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.15](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.14...@revanced/discord-bot@1.0.0-dev.15) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** import `config` from context ([763ef25](https://github.com/revanced/revanced-helper/commit/763ef253f9d4ff70a8b79969a7f4f41cba7f3c59))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add sticky messages ([bf66155](https://github.com/revanced/revanced-helper/commit/bf661556e131bf0ef24e47f658fbcd701960e312))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.14](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.13...@revanced/discord-bot@1.0.0-dev.14) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** always true check causing no messages to be scanned ([98ec37b](https://github.com/revanced/revanced-helper/commit/98ec37b5d18cade85270ab83b0ed0abe41244dd9))
|
||||
* other small issues ([bc437a5](https://github.com/revanced/revanced-helper/commit/bc437a5ec7ce1d339094d608e2a61ac5f460c163))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add more options for curing, fix default regex ([1a4ec1e](https://github.com/revanced/revanced-helper/commit/1a4ec1ece80becd9156582cc490f6681cb2a1f39))
|
||||
* **bots/discord:** allow admins to bypass permission checks ([620f933](https://github.com/revanced/revanced-helper/commit/620f9339f0737b79d72c66d90ffa42ea3f987710))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.13](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.12...@revanced/discord-bot@1.0.0-dev.13) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** broken regex when prefix set to special characters ([ab62e55](https://github.com/revanced/revanced-helper/commit/ab62e55e76005f5999d7413d1158e54053f28d1f))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.12](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.11...@revanced/discord-bot@1.0.0-dev.12) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** deployment runtime errors due to minification ([a60c60c](https://github.com/revanced/revanced-helper/commit/a60c60c0f994a4c256b7d0582e99a1731209cf49))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.11](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.10...@revanced/discord-bot@1.0.0-dev.11) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** reset counter when reconnected to api, redo message scan filter logic ([d234d79](https://github.com/revanced/revanced-helper/commit/d234d79310caed9c43e14a905f9ef46a110e071d))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.10](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.9...@revanced/discord-bot@1.0.0-dev.10) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** hanging process when disconnecting from API too many times ([d31616e](https://github.com/revanced/revanced-helper/commit/d31616ebcba6f1dcd8bde183bcb8d1adb1501b61))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.8...@revanced/discord-bot@1.0.0-dev.9) (2024-07-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** framework changes and new features ([646ec8d](https://github.com/revanced/revanced-helper/commit/646ec8da87617e6c8f48a89e8054e2cba91da549))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.7...@revanced/discord-bot@1.0.0-dev.8) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** cross-device link build errors ([38c0699](https://github.com/revanced/revanced-helper/commit/38c06997b4d0f7bb3f1e62618a5e3f088c522e30))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** blacklist and whitelist for filters ([cdb6001](https://github.com/revanced/revanced-helper/commit/cdb600195520dba33110c40841629259e317055e))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bot/discord:** start remove preset timeout for `role-preset` command ([cbf9116](https://github.com/revanced/revanced-helper/commit/cbf91162e27dd4c1ecb976927ab708f1d882abca))
|
||||
* **bots/discord:** only check for member permissions when specified while correcting responses ([b79a1c7](https://github.com/revanced/revanced-helper/commit/b79a1c7575e94c3e62654c87775cac497be4a50a))
|
||||
* **bots/discord:** set timeout for eligible mutes to unmute faster ([1f5c5a9](https://github.com/revanced/revanced-helper/commit/1f5c5a92a639973b83a1204355538936e69a4454))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** add `replyToReplied` option in response config ([27662ed](https://github.com/revanced/revanced-helper/commit/27662ed91a79bfac7d3f091834e859a7b57366ce))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** ci issues causing database to not be auto generated ([673aa18](https://github.com/revanced/revanced-helper/commit/673aa189bef1009a3e32ba3b1291a5ee84f2def3))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23)
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** wrong database schema path ([875bd20](https://github.com/revanced/revanced-helper/commit/875bd209b252566414bf89349839cabc01697e1c))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** revert dist denesting, fixes config not found ([0d4898d](https://github.com/revanced/revanced-helper/commit/0d4898dae8b26f8466d3f6b8f62875866f581644))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** don't nest builds in src directory, autogen db when missing ([4834685](https://github.com/revanced/revanced-helper/commit/48346851864c4d4b6276388644dd24ce16222b3e))
|
||||
|
||||
# @revanced/discord-bot 1.0.0-dev.1 (2024-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord/commands/mute:** use existing `parseDuration` util function' ([3e07429](https://github.com/revanced/revanced-helper/commit/3e07429664f7dbb6ce653083e0adb1a232737fde))
|
||||
* **bots/discord/commands/unmute:** fix option description and embeds ([7fdf8c0](https://github.com/revanced/revanced-helper/commit/7fdf8c0dc722e21fe5a3ad6ef8d3a306ef85f532))
|
||||
* **bots/discord/commands:** minor issues ([3b2596e](https://github.com/revanced/revanced-helper/commit/3b2596e748cf2cde1500ef2ded55f0faabc2c272))
|
||||
* **bots/discord/commands:** refactor and add checks ([a2bf3ea](https://github.com/revanced/revanced-helper/commit/a2bf3eade99b46f9ffb55d45b8caf1bcf3d22a9b))
|
||||
* **bots/discord/database:** fix schema for role presets ([4aa138a](https://github.com/revanced/revanced-helper/commit/4aa138a9af8db7093ef637470fcfdea1f5341236))
|
||||
* **bots/discord/scripts:** unintentional escaping on windows ([09dc706](https://github.com/revanced/revanced-helper/commit/09dc70632da0597fdb26677acee3f6fccbb2b9b5))
|
||||
* **bots/discord/utils/discord/embeds:** set thumbnail on moderation embeds ([b056291](https://github.com/revanced/revanced-helper/commit/b056291ad0f2e2eac5eec8aa71f15dbc769aa0f9))
|
||||
* **bots/discord/utils/discord/rolePresets:** correct property access for presets ([4c6ad11](https://github.com/revanced/revanced-helper/commit/4c6ad11be30c1d6af97c4ae40fc62d05fa7bdd57))
|
||||
* **bots/discord/utils/discord:** add `moderation` module related functions ([7e8270f](https://github.com/revanced/revanced-helper/commit/7e8270f7d260322e1950e058b221ab088bd595d0))
|
||||
* **bots/discord/utils/duration:** fix empty string returning with duration is 0 ([83c314e](https://github.com/revanced/revanced-helper/commit/83c314ef5f721abc355272db0e4c182dcfe5d943))
|
||||
* **bots/discord:** apply active role presets if members rejoin ([f50b26b](https://github.com/revanced/revanced-helper/commit/f50b26b82d66c88fd1dbb8c07d77c177c0e781df))
|
||||
* **bots/discord:** check token before connecting to bot api ([f3e4408](https://github.com/revanced/revanced-helper/commit/f3e4408aa28fb6a9d21365af8c1bea3d07b481de))
|
||||
* **bots/discord:** clear role presets after they expire ([faa81f4](https://github.com/revanced/revanced-helper/commit/faa81f4d887eaeae809651f5b68187d033a260f2))
|
||||
* **bots/discord:** connect to discord API even if initial bot API connection fails ([6658b58](https://github.com/revanced/revanced-helper/commit/6658b582dbeba7e072a7a04c4efa255e7f634aef))
|
||||
* **bots/discord:** do decancer after resetting nickname ([0303fe3](https://github.com/revanced/revanced-helper/commit/0303fe3e367c07e92f831365d5548ca5b03435b2))
|
||||
* **bots/discord:** follow-up if reply is already sent when error ([f75060b](https://github.com/revanced/revanced-helper/commit/f75060bc9cda44902cf872def73c116a6df039d7))
|
||||
* **bots/discord:** messed up file name for `unmute` command ([399dca7](https://github.com/revanced/revanced-helper/commit/399dca71538fe5c8831977694a97058254a17578))
|
||||
* **bots/discord:** owners cannot bypass checks on some commands ([39cba97](https://github.com/revanced/revanced-helper/commit/39cba973418027ba6ed67e1ae5ab5c6458807562))
|
||||
* **bots/discord:** remove auto-generated files ([fb8af00](https://github.com/revanced/revanced-helper/commit/fb8af008661bf37389e01cba19d64a8b4fc82139))
|
||||
* **bots/discord:** remove usage of macros ([7f27c56](https://github.com/revanced/revanced-helper/commit/7f27c5607ceeeef56d67097e88f68caa1b8791b3))
|
||||
* **bots/discord:** remove useless feature ([d830e48](https://github.com/revanced/revanced-helper/commit/d830e48bc2de7aa457eab3a5f96ae652a93178f9))
|
||||
* **bots/discord:** use `APIEmbed` for response config ([35b9448](https://github.com/revanced/revanced-helper/commit/35b944800a3943c187d5b0e0d3e465ad7d2056fe))
|
||||
* **bots/discord:** use env for initializing database ([af3759c](https://github.com/revanced/revanced-helper/commit/af3759caf428fada3b3f4a51852543d6fb280018))
|
||||
* **bots/discord:** wrong command file path being imported ([fa0159c](https://github.com/revanced/revanced-helper/commit/fa0159c3a8dd4dad8778ccdb75b9e7c02ebbb64f))
|
||||
* config file not being read ([474a8be](https://github.com/revanced/revanced-helper/commit/474a8be4af4eb2bae6e80a893439d846ad4f7503))
|
||||
* **discord-bot:** also execute slash commands ([f0d45b2](https://github.com/revanced/revanced-helper/commit/f0d45b2c926ed753e2d21f2e06e24d7e6c43880a))
|
||||
* **discord-bot:** check for role position ([d332043](https://github.com/revanced/revanced-helper/commit/d332043b1a4bb7ac9698a2fc912832e184130b4b))
|
||||
* **discord-bot:** check if the member has the role ([9bff68c](https://github.com/revanced/revanced-helper/commit/9bff68c8c40c692764e4dec15a058e35059efbc9))
|
||||
* **discord-bot:** not executing slash commands ([aa08087](https://github.com/revanced/revanced-helper/commit/aa0808768b90844c5fbd3e75d9f2d01c723b0151))
|
||||
* **discord-bot:** only send lowercased text ([7803758](https://github.com/revanced/revanced-helper/commit/78037580dc92883f5ca21157e45268850cb5db90))
|
||||
* dislike button not working properly ([85eba55](https://github.com/revanced/revanced-helper/commit/85eba554247738066af72a8efd0de215ec1164dc))
|
||||
* fix deprecation ([4373ede](https://github.com/revanced/revanced-helper/commit/4373ede855333f209676551162a525238656e1f8))
|
||||
* fix the fiter for the interaction collector ([a9ff003](https://github.com/revanced/revanced-helper/commit/a9ff00394a73f68a6793c2b35ff184675ee5a72c))
|
||||
* ignore message if there's no content ([3cbebc2](https://github.com/revanced/revanced-helper/commit/3cbebc284277808495e64cf0fb47c555924ad9c5))
|
||||
* move modules to `/bots` ([cd7156e](https://github.com/revanced/revanced-helper/commit/cd7156e792e65777ad1ab5a6f5d828b9ef6a9754))
|
||||
* trainAI not using the bin location ([bd29943](https://github.com/revanced/revanced-helper/commit/bd2994388bc65f720120ef49edb6ba8163260309))
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* fix more build issues ([77fefb9](https://github.com/revanced/revanced-helper/commit/77fefb9bef286a22f40a4d76b79c64fcc5a2467f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add wit.ai support ([1909e2c](https://github.com/revanced/revanced-helper/commit/1909e2c42148d635dcd045c738d88f65c8be16e3))
|
||||
* **bots/discord/commands/reply:** send stacktrace when failed ([9f1ac37](https://github.com/revanced/revanced-helper/commit/9f1ac379276c11da65235577a9c6717e01cb02eb))
|
||||
* **bots/discord/commands:** add `ban` and `unban` commands ([dc4863d](https://github.com/revanced/revanced-helper/commit/dc4863dc208b3fede4d4def323306ab58daffe04))
|
||||
* **bots/discord/commands:** add `eval` command ([e64d1da](https://github.com/revanced/revanced-helper/commit/e64d1da00cc2ba718da5a4b0da141fe86a0e48d2))
|
||||
* **bots/discord/commands:** add `mute` and `unmute` commands ([c0fa2fe](https://github.com/revanced/revanced-helper/commit/c0fa2fe1c36acdc7c52cde277aa7da867065f55e))
|
||||
* **bots/discord/commands:** add `purge` and `role-preset` commands ([fb01ce5](https://github.com/revanced/revanced-helper/commit/fb01ce57400130c93751a11573eb444c0ba103eb))
|
||||
* **bots/discord/commands:** allow process exception in `exception-test` ([ca47535](https://github.com/revanced/revanced-helper/commit/ca475356ad95fec86e8e8b5bf4bbf17b70add5fe))
|
||||
* **bots/discord/utils/discord/embeds:** expose `applyCommonEmbedStyles` fn ([2d794ed](https://github.com/revanced/revanced-helper/commit/2d794ede7d7a208bd3616c45e8e6d2a2cd83e9ed))
|
||||
* **bots/discord/utils/embeds:** make title parameter nullable ([ee885ca](https://github.com/revanced/revanced-helper/commit/ee885ca7585a55fdc31e137ae29dc13a37ce2fb2))
|
||||
* **bots/discord/utils/fs:** use `recursive` option for listing files ([da21e1a](https://github.com/revanced/revanced-helper/commit/da21e1a6f76deaeb477203b04263bd170863825b))
|
||||
* **bots/discord/utils:** add duration utility ([a9add9e](https://github.com/revanced/revanced-helper/commit/a9add9ea9affb42bdfcb17cf4b268feec5729854))
|
||||
* **bots/discord/utils:** add functions for role presets ([fb32a04](https://github.com/revanced/revanced-helper/commit/fb32a04ad38be8d0836dc99259b6ef05a0825830))
|
||||
* **bots/discord/utils:** allow loading commands from custom dir ([8b690b8](https://github.com/revanced/revanced-helper/commit/8b690b879bb5c6023c8fc863afbd9fd1d02719bb))
|
||||
* **bots/discord:** add `api.disconnectRetryInterval` config ([2f86586](https://github.com/revanced/revanced-helper/commit/2f8658617923c07f6847cbf1fdfc5f5379d95b6c))
|
||||
* **bots/discord:** add `moderation.roles` config to be used in `moderation` commands ([39d5b3a](https://github.com/revanced/revanced-helper/commit/39d5b3a479b4d856aabe12cc31177c24f88ae23e))
|
||||
* **bots/discord:** add `ocrTriggers` resp config, embed footer scan mode ([744a56a](https://github.com/revanced/revanced-helper/commit/744a56a4fdc8844e37959a88bcf81ee39fe726ef))
|
||||
* **bots/discord:** add a better way to manage databases ([a68d726](https://github.com/revanced/revanced-helper/commit/a68d72687584332587455962b0202a306288057d))
|
||||
* **bots/discord:** add more fallbacks for decancering ([2e1e009](https://github.com/revanced/revanced-helper/commit/2e1e009b4272495798313bd3bd61f258875c62e1))
|
||||
* **bots/discord:** add source ([f9d50a0](https://github.com/revanced/revanced-helper/commit/f9d50a0a6bef8beaa428a0a555bfa4f879f685f1))
|
||||
* **bots/discord:** improve logs ([6abb740](https://github.com/revanced/revanced-helper/commit/6abb7409945c10bd3af451fb45ef4b4d4ebe9489))
|
||||
* **bots/discord:** sanitize `BasicDatabase` inputs ([fd76e0a](https://github.com/revanced/revanced-helper/commit/fd76e0af72fe28b414ae3b5e8d3886e58561e57e))
|
||||
* **bots/discord:** support nickname decancering ([1723e8c](https://github.com/revanced/revanced-helper/commit/1723e8cacf96e8c6bdee22cfd30e89524fdcef74))
|
||||
* **bots/discord:** switch to `drizzle-orm` ([e204b7b](https://github.com/revanced/revanced-helper/commit/e204b7b7566fd7fa423baef32977a8575d44a9e0))
|
||||
* **bots/discord:** update config ([197d2ac](https://github.com/revanced/revanced-helper/commit/197d2acea89c38e43858d52736508d449152e804))
|
||||
* discord bot scanning messages ([d1bd3b2](https://github.com/revanced/revanced-helper/commit/d1bd3b2b7e4985a64e9b070ab006cc6f3508c46e))
|
||||
* **discord-bot:** a way to train AI ([355a508](https://github.com/revanced/revanced-helper/commit/355a50803adc85b5579155b55ddbba4fa0449237))
|
||||
* **discord-bot:** command handler and train cmd ([6aee8a4](https://github.com/revanced/revanced-helper/commit/6aee8a4c63eb108800fcb0a23ca61f200d8f1f2a))
|
||||
* **discord-bot:** event handler ([0ad5ece](https://github.com/revanced/revanced-helper/commit/0ad5ece08593c0db111fa4a592b42c6e0348fd1c))
|
||||
* GODEL AI ([0ba525c](https://github.com/revanced/revanced-helper/commit/0ba525c4a5802106d582c75f713728accf2f151a))
|
||||
* initalize discord bot ([bb4a5a7](https://github.com/revanced/revanced-helper/commit/bb4a5a77eefbc7ac88536f73a111df1050b235e7))
|
||||
* initialize helper client ([7f9ca77](https://github.com/revanced/revanced-helper/commit/7f9ca77e0331ec143160ee51ed7c3aa9e4e70b9c))
|
||||
* message buttons for training ([6551ca9](https://github.com/revanced/revanced-helper/commit/6551ca9dadc2e3ddfe98875e80ed61f7d71a1651))
|
||||
* platform specific responses ([18e57b0](https://github.com/revanced/revanced-helper/commit/18e57b0c320732a937bb60db11c5d6794ed11522))
|
||||
* prettier and eslint ([1c27ccb](https://github.com/revanced/revanced-helper/commit/1c27ccb17c85f0f6982db45de426181d2c231d0e))
|
||||
* refactor and new features ([#7](https://github.com/revanced/revanced-helper/issues/7)) ([8b9f45d](https://github.com/revanced/revanced-helper/commit/8b9f45dc22de29dc2ccb1cfab9a026db00457e25))
|
||||
* run bots in one process ([d26d533](https://github.com/revanced/revanced-helper/commit/d26d53317440c64fb775cea609a87d29be6c8b40))
|
||||
* training and replies changed ([715aa91](https://github.com/revanced/revanced-helper/commit/715aa918cf84213c9b19591a398d7532eb3f232a))
|
||||
* **utils/discord/embeds:** allow adding extra fields for moderation embeds ([49ce9a7](https://github.com/revanced/revanced-helper/commit/49ce9a7ca3d8558b73a9b94dfe7a01d809db6fff))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* In `@revanced/discord-bot`, its environment variable
|
||||
`DATABASE_URL` has been renamed to `DATABASE_PATH`
|
||||
and the `file:` prefix is no longer needed
|
||||
18
bots/discord/Dockerfile
Normal file
18
bots/discord/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
# This file should be triggered from the monorepo root
|
||||
FROM oven/bun:latest AS base
|
||||
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN cd bots/discord && bun run build
|
||||
|
||||
FROM base AS release
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /build/bots/discord/dist /app
|
||||
|
||||
USER 1000:1000
|
||||
|
||||
ENTRYPOINT [ "bun", "--bun", "run", "src/index.js" ]
|
||||
73
bots/discord/README.md
Executable file
73
bots/discord/README.md
Executable file
@@ -0,0 +1,73 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="../../assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="../../assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="../../assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<img height="24px" src="../../assets/revanced-logo/revanced-logo-round.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 🤖 ReVanced Discord Bot
|
||||
|
||||

|
||||
|
||||
Discord bot assisting ReVanced's Discord server.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Documentation are provided [here](./docs/README.md).
|
||||
|
||||
## 📄 License
|
||||
|
||||
**ReVanced Discord Bot** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions.
|
||||
122
bots/discord/config.js
Normal file
122
bots/discord/config.js
Normal file
@@ -0,0 +1,122 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('./config.schema').Config}
|
||||
*/
|
||||
export default {
|
||||
prefix: '!',
|
||||
admin: {
|
||||
users: ['USER_ID_HERE'],
|
||||
roles: {
|
||||
GUILD_ID_HERE: ['ROLE_ID_HERE'],
|
||||
},
|
||||
},
|
||||
stickyMessages: {
|
||||
GUILD_ID_HERE: {
|
||||
CHANNEL_ID_HERE: {
|
||||
message: {
|
||||
content: 'This is a sticky message!',
|
||||
},
|
||||
timeout: 60000,
|
||||
forceSendTimeout: 300000,
|
||||
},
|
||||
},
|
||||
},
|
||||
moderation: {
|
||||
cure: {
|
||||
minimumNameLength: 3,
|
||||
removeCharactersRegex: /[^a-zA-Z0-9 \-_]/g,
|
||||
defaultName: 'Server member',
|
||||
},
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
log: {
|
||||
channel: 'CHANNEL_ID_HERE',
|
||||
// Optional
|
||||
thread: 'THREAD_ID_HERE',
|
||||
},
|
||||
},
|
||||
rolePresets: {
|
||||
guilds: {
|
||||
GUILD_ID_HERE: {
|
||||
preset: {
|
||||
give: ['ROLE_ID_HERE'],
|
||||
take: ['ROLE_ID_HERE'],
|
||||
},
|
||||
anotherPreset: {
|
||||
give: ['ROLE_ID_HERE'],
|
||||
take: ['ROLE_ID_HERE'],
|
||||
},
|
||||
},
|
||||
},
|
||||
checkExpiredEvery: 3600,
|
||||
},
|
||||
messageScan: {
|
||||
scanBots: false,
|
||||
scanOutsideGuilds: false,
|
||||
filter: {
|
||||
whitelist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
blacklist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
},
|
||||
humanCorrections: {
|
||||
falsePositiveLabel: 'false_positive',
|
||||
allow: {
|
||||
members: {
|
||||
permissions: 8n,
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
},
|
||||
},
|
||||
},
|
||||
attachments: {
|
||||
scanAttachments: true,
|
||||
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'text/plain'],
|
||||
maxTextFileSize: 512000,
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
filterOverride: {
|
||||
whitelist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
blacklist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
},
|
||||
triggers: {
|
||||
text: [/^regexp?$/, { label: 'label', threshold: 0.85 }],
|
||||
},
|
||||
response: {
|
||||
embeds: [
|
||||
{
|
||||
title: 'Embed title',
|
||||
description: 'Embed description',
|
||||
fields: [
|
||||
{
|
||||
name: 'Field name',
|
||||
value: 'Field value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
logLevel: 'log',
|
||||
api: {
|
||||
url: 'ws://127.0.0.1:3000',
|
||||
disconnectLimit: 3,
|
||||
disconnectRetryInterval: 10000,
|
||||
},
|
||||
}
|
||||
96
bots/discord/config.schema.ts
Normal file
96
bots/discord/config.schema.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { BaseMessageOptions } from 'discord.js'
|
||||
|
||||
export type Config = {
|
||||
prefix?: string
|
||||
admin?: {
|
||||
users?: string[]
|
||||
roles?: Record<string, string[]>
|
||||
}
|
||||
stickyMessages?: Record<string, Record<string, StickyMessageConfig>>
|
||||
moderation?: {
|
||||
roles: string[]
|
||||
cure?: {
|
||||
minimumNameLength?: number
|
||||
removeCharactersRegex?: RegExp
|
||||
defaultName: string
|
||||
}
|
||||
log?: {
|
||||
channel: string
|
||||
thread?: string
|
||||
}
|
||||
}
|
||||
rolePresets?: {
|
||||
checkExpiredEvery: number
|
||||
guilds: Record<string, Record<string, RolePresetConfig>>
|
||||
}
|
||||
messageScan?: {
|
||||
scanBots?: boolean
|
||||
scanOutsideGuilds?: boolean
|
||||
attachments?: {
|
||||
scanAttachments?: boolean
|
||||
allowedMimeTypes?: string[]
|
||||
maxTextFileSize?: number
|
||||
}
|
||||
filter?: {
|
||||
whitelist?: Filter
|
||||
blacklist?: Filter
|
||||
}
|
||||
humanCorrections: {
|
||||
falsePositiveLabel: string
|
||||
allow?: {
|
||||
users?: string[]
|
||||
members?: {
|
||||
permissions?: bigint
|
||||
roles?: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
responses: ConfigMessageScanResponse[]
|
||||
}
|
||||
logLevel: 'none' | 'error' | 'warn' | 'info' | 'log' | 'trace' | 'debug'
|
||||
api: {
|
||||
url: string
|
||||
disconnectLimit?: number
|
||||
disconnectRetryInterval?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type StickyMessageConfig = {
|
||||
timeout: number
|
||||
forceSendTimeout?: number
|
||||
message: BaseMessageOptions
|
||||
}
|
||||
|
||||
export type RolePresetConfig = {
|
||||
give: string[]
|
||||
take: string[]
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponse = {
|
||||
triggers: {
|
||||
text?: Array<RegExp | ConfigMessageScanResponseLabelConfig>
|
||||
image?: Array<RegExp>
|
||||
}
|
||||
filterOverride?: NonNullable<Config['messageScan']>['filter']
|
||||
response: ConfigMessageScanResponseMessage | null
|
||||
respondToReply?: boolean
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseLabelConfig = {
|
||||
/**
|
||||
* Label name
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Confidence threshold
|
||||
*/
|
||||
threshold: number
|
||||
}
|
||||
|
||||
export type Filter = {
|
||||
roles?: string[]
|
||||
users?: string[]
|
||||
channels?: string[]
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseMessage = BaseMessageOptions
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user