Compare commits
50 commits
1.0-pre-re
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
e2c3da732b | ||
|
d13a814bb6 | ||
|
3bea94bee0 | ||
|
5f6cb95f52 | ||
|
1253de3bb4 | ||
|
a7d4a91936 | ||
|
33d71ae1a6 | ||
|
fd27047cbb | ||
|
53fec894f7 | ||
|
a7f52bb195 | ||
|
a87e1c92d5 | ||
|
5dd4476f6a | ||
|
44ff217949 | ||
|
41e80eebf9 | ||
|
ef7d13cba1 | ||
|
8a6d714ca1 | ||
|
5bd45cc801 | ||
|
6f3169d564 | ||
|
d9b52d841a | ||
|
b3943d3cdb | ||
|
a4f4b17198 | ||
|
09b4eeabd6 | ||
|
192e979a9e | ||
|
68d6402ffd | ||
|
32cfeb354e | ||
|
bb3e063987 | ||
|
b7559411c6 | ||
|
1ca300771f | ||
|
5cbb6ad445 | ||
|
65dd44fd43 | ||
|
1c41dff700 | ||
|
2989f40e3b | ||
|
6d585ce3af | ||
|
b61e48c535 | ||
|
2f59ff4147 | ||
|
98ca59cd41 | ||
|
0d2c8243a0 | ||
|
5696df170f | ||
|
2171b25dd6 | ||
|
28b6201a3a | ||
|
373b6e1926 | ||
|
db3cff0aa0 | ||
|
14435d2345 | ||
|
f11f3905ca | ||
|
a6dcfeedb6 | ||
|
f26b8b22de | ||
|
9f56b012b9 | ||
|
5480402a8c | ||
|
1451549928 | ||
|
33d1c8ae9c |
11
.env.example
Normal file
11
.env.example
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
PBX_HOSTNAME=your_pbx_hostname
|
||||||
|
FREEPBX_URL=http://your_freepbx_url
|
||||||
|
FREEPBX_CLIENT_ID=your_freepbx_client_id
|
||||||
|
FREEPBX_CLIENT_SECRET=your_freepbx_client_secret
|
||||||
|
DB_HOST=your_db_host
|
||||||
|
DB_USER=your_db_user
|
||||||
|
DB_PASS=your_db_password
|
||||||
|
DISCORD_TOKEN=your_discord_token
|
||||||
|
OWNER_ID=your_owner_id
|
||||||
|
EXTENSION_ROLE_ID=your_extension_role_id
|
||||||
|
DISCORD_GUILD=your_discord_guild_id
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -136,4 +136,6 @@ config.json.disabled
|
||||||
test.js
|
test.js
|
||||||
embeds.json
|
embeds.json
|
||||||
pageGroups.json
|
pageGroups.json
|
||||||
.ssh/
|
.ssh/
|
||||||
|
|
||||||
|
old/
|
87
README.md
87
README.md
|
@ -24,7 +24,7 @@ To set up the config file, follow the steps below:
|
||||||
|
|
||||||
1. Clone the repository:
|
1. Clone the repository:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/ChrisChrome/discord-freepbx-manager.git
|
git clone https://git.chrischro.me/ChrisChrome/discord-freepbx-manager.git
|
||||||
|
|
||||||
cd discord-freepbx-manager
|
cd discord-freepbx-manager
|
||||||
```
|
```
|
||||||
|
@ -33,63 +33,34 @@ To set up the config file, follow the steps below:
|
||||||
npm install --save
|
npm install --save
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Create a new file named `config.json` in the root directory of the project and fill it with the following content:
|
3. Copy `.env.example` to `.env`
|
||||||
```json
|
|
||||||
{
|
4. Configure the `.env` file with the following options:
|
||||||
"ntfyUrl": "ntfy-url",
|
|
||||||
"freepbx": {
|
PBX_HOSTNAME: This is the hostname or IP address of your PBX (Private Branch Exchange) server. It is used to connect to the PBX system. This value is whats sent to the end users via the bot. Can be whatever and won't affect the functionality of the bot.
|
||||||
"server": "sip-server-ip",
|
|
||||||
"url": "pbx-api-url",
|
FREEPBX_URL: Base URL for the API of your PBX. i.e. `https://pbx.example.com`
|
||||||
"clientid": "gql-client-id",
|
|
||||||
"allowedscopes": "gql",
|
FREEPBX_CLIENT_ID: This is the client ID used for authenticating with the FreePBX API. It is part of the OAuth2 authentication process.
|
||||||
"secret": "gql-secret",
|
|
||||||
"startExt": 1000
|
FREEPBX_CLIENT_SECRET: This is the client secret used along with the client ID for authenticating with the FreePBX API.
|
||||||
},
|
|
||||||
"discord": {
|
DB_HOST: This is the hostname or IP address of your database server. It is used to connect to the database. If running on your PBX, set to `127.0.0.1`
|
||||||
"token": "bot-token",
|
|
||||||
"guildId": "guild-id",
|
DB_USER: This is the username used to authenticate with the database.
|
||||||
"roleId": "user-role",
|
|
||||||
"logId": "log-channel",
|
DB_PASS: This is the password used to authenticate with the database.
|
||||||
"extList": "extension-list-channel",
|
|
||||||
"developers": [
|
DISCORD_TOKEN: This is the token for your Discord bot. It is used to authenticate the bot with the Discord API.
|
||||||
"your-user-id"
|
|
||||||
]
|
OWNER_ID: This is the Discord user ID of the bot owner. It can be used to grant special permissions or access to the bot owner.
|
||||||
},
|
|
||||||
"mariadb": {
|
EXTENSION_ROLE_ID: Discord role ID of the role to give PBX users when they make their extension.
|
||||||
"host": "db-hostname0here",
|
|
||||||
"user": "bot",
|
DISCORD_GUILD: This is the Discord server (guild) ID where the bot will operate. This is used to determine if extensions are orphaned, so make sure it's set correctly.
|
||||||
"password": "bot",
|
|
||||||
"database": "asterisk",
|
These environment variables are used to configure your application and provide necessary credentials and settings for connecting to various services. Make sure to keep this information secure and do not share it publicly.
|
||||||
"connectionLimit": 5
|
|
||||||
},
|
|
||||||
"cdrdb": {
|
|
||||||
"host": "db-hostname-here",
|
|
||||||
"user": "bot",
|
|
||||||
"password": "bot",
|
|
||||||
"database": "asteriskcdrdb",
|
|
||||||
"connectionLimit": 5
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"interval": 60,
|
|
||||||
"url": "uptime-kuma-link"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
4. Replace the placeholders with your own values:
|
|
||||||
- `ntfyUrl`: The URL of the NTFY server.
|
|
||||||
- `sip-server-ip`: The IP address of the SIP server.
|
|
||||||
- `pbx-api-url`: The URL of the FreePBX API.
|
|
||||||
- `gql-client-id`: The client ID for the GraphQL API.
|
|
||||||
- `gql-secret`: The secret for the GraphQL API.
|
|
||||||
- `bot-token`: The token of the Discord bot.
|
|
||||||
- `guild-id`: The ID of the Discord guild.
|
|
||||||
- `user-role`: The ID of the role that users must have to use the bot.
|
|
||||||
- `log-channel`: The ID of the channel where logs will be sent.
|
|
||||||
- `extension-list-channel`: The ID of the channel where the extension list will be sent.
|
|
||||||
- `your-user-id`: Your Discord user ID.
|
|
||||||
- `db-hostname-here`: The hostname of the MariaDB server.
|
|
||||||
- Use `mysql -u root -p -e "CREATE USER 'bot'@'localhost' IDENTIFIED BY 'bot';"` to create a new user. Change `localhost` to the IP of the server running the bot if you aren't running the bot on the PBX server.
|
|
||||||
- `uptime-kuma-link`: The URL of the Uptime Kuma instance.
|
|
||||||
5. Run the bot:
|
5. Run the bot:
|
||||||
```shell
|
```shell
|
||||||
node .
|
node .
|
||||||
|
|
52
TODO.md
Normal file
52
TODO.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Bot Checklist
|
||||||
|
|
||||||
|
## General Commands
|
||||||
|
- [X] **/whoami** - Get your extension info if you have one.
|
||||||
|
- [X] **/new** - Get an extension on the LiteNet Phone System.
|
||||||
|
- [X] **/delete** - Remove your extension from the LiteNet Phone System.
|
||||||
|
- [X] **/list** - List all extensions on the LiteNet Phone System.
|
||||||
|
- [X] **/button** - Send the "Get an extension" button! *(Requires default_member_permissions: 0)*
|
||||||
|
|
||||||
|
## Admin Commands
|
||||||
|
*(Requires default_member_permissions: 0)*
|
||||||
|
- [X] **/admin**
|
||||||
|
- [X] **silence** - Kill all ongoing calls.
|
||||||
|
- [X] **reload** - Run an Asterisk reload.
|
||||||
|
- [X] **reboot** - Reboot the server. *(LAST RESORT)*
|
||||||
|
|
||||||
|
## Developer Commands
|
||||||
|
*(Requires default_member_permissions: 0)*
|
||||||
|
- [X] **/dev**
|
||||||
|
- [X] **fwconsole** - Run an `fwconsole` command.
|
||||||
|
- [X] **command** (required) - The command to run.
|
||||||
|
- [X] **asterisk** - Run an Asterisk CLI command.
|
||||||
|
- [X] **command** (required) - The command to run.
|
||||||
|
- [X] **shell** - Run a shell command.
|
||||||
|
- [X] **command** (required) - The command to run.
|
||||||
|
- [X] **restart** - Restart the bot.
|
||||||
|
|
||||||
|
## Call Detail Records (CDR)
|
||||||
|
- [ ] **/cdr** - Get the call detail records for your extension.
|
||||||
|
- [ ] **start_date** (optional) - The start date for the CDR (mm/dd/yyyy).
|
||||||
|
- [ ] **end_date** (optional) - The end date for the CDR (mm/dd/yyyy).
|
||||||
|
|
||||||
|
## Management Commands (For admins to manage extensions)
|
||||||
|
*(Requires default_member_permissions: 0)*
|
||||||
|
- [X] **/manage**
|
||||||
|
- [X] **create** - Create an extension.
|
||||||
|
- [X] **extension** (required) - The extension number.
|
||||||
|
- [X] **discord user** (required) - The Discord user to assign the extension to.
|
||||||
|
- [X] **delete** - Delete an extension.
|
||||||
|
- [X] **discord user** (required) - The Discord user whose extension is to be deleted.
|
||||||
|
- [X] **lookup** - Lookup an extension. (With creds)
|
||||||
|
- [X] **extension** (required) - The extension number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Buttons (On button message)
|
||||||
|
- [X] **Get an Extension** - Sends the "Get an extension" button.
|
||||||
|
- [X] **See Your Info** - Sends the "See your info" button.
|
||||||
|
- [X] **Delete Your Extension** - Sends the "Delete your extension" button.
|
||||||
|
|
||||||
|
## Other Features
|
||||||
|
- [ ] **Extension List on Discord** - List all extensions on the Discord server. (Will do later)
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"description": "Change your extension's name (Defaults to your Discord name)",
|
|
||||||
"type": 1,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"description": "The new name for your extension",
|
|
||||||
"type": 3,
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
307
commands.js
Normal file
307
commands.js
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
"name": "whoami",
|
||||||
|
"description": "Get your extension info if you have one",
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "new",
|
||||||
|
"description": "Get an extension on the LiteNet Phone System",
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delete",
|
||||||
|
"description": "Remove your extension from the LiteNet Phone System",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "confirm",
|
||||||
|
"description": "Confirm that you want to delete your extension. THIS CANNOT BE UNDONE!",
|
||||||
|
"type": 5,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paging",
|
||||||
|
"description": "Subcommands for managing your paging groups",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "add",
|
||||||
|
"description": "Add yourself to a paging group",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"description": "Page group to join",
|
||||||
|
"type": 3,
|
||||||
|
"required": true,
|
||||||
|
"choices": require("./pageGroups.json")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "remove",
|
||||||
|
"description": "Remove yourself from a paging group",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"description": "Page group to leave",
|
||||||
|
"type": 3,
|
||||||
|
"required": true,
|
||||||
|
"choices": require("./pageGroups.json")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "list",
|
||||||
|
"description": "List all extensions on the LiteNet Phone System",
|
||||||
|
"type": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "button",
|
||||||
|
"description": "Send the get an extension button!",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0
|
||||||
|
},
|
||||||
|
// TODO: Find a way to make the name command work again. Sadge
|
||||||
|
// {
|
||||||
|
// "name": "name",
|
||||||
|
// "description": "Change your extension's name (Defaults to your Discord name)",
|
||||||
|
// "type": 1,
|
||||||
|
// "options": [
|
||||||
|
// {
|
||||||
|
// "name": "name",
|
||||||
|
// "description": "The new name for your extension",
|
||||||
|
// "type": 3,
|
||||||
|
// "required": false
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"description": "Admin only commands",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "silence",
|
||||||
|
"description": "Kill all ongoing calls",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reload",
|
||||||
|
"description": "Run asterisk reload",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reboot",
|
||||||
|
"description": "Reboot the server (LAST RESORT)",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "list-deletions",
|
||||||
|
"description": "List pending deletions",
|
||||||
|
"type": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"description": "Developer only commands",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "fwconsole",
|
||||||
|
"description": "Run an fwconsole command",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "command",
|
||||||
|
"description": "The command to run",
|
||||||
|
"type": 3,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asterisk",
|
||||||
|
"description": "Run an asterisk CLI command",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "command",
|
||||||
|
"description": "The command to run",
|
||||||
|
"type": 3,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shell",
|
||||||
|
"description": "Run a shell command",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "command",
|
||||||
|
"description": "The command to run",
|
||||||
|
"type": 3,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "restart",
|
||||||
|
"description": "Restart the bot",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// "name": "cdr",
|
||||||
|
// "description": "Get the call detail records for your extension",
|
||||||
|
// "type": 1,
|
||||||
|
// "options": [
|
||||||
|
// {
|
||||||
|
// "name": "start_date",
|
||||||
|
// "description": "The start date for the CDR (mm/dd/yyyy)",
|
||||||
|
// "type": 3,
|
||||||
|
// "required": false
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "end_date",
|
||||||
|
// "description": "The end date for the CDR (mm/dd/yyyy)",
|
||||||
|
// "type": 3,
|
||||||
|
// "required": false
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
"name": "lookup",
|
||||||
|
"description": "Find extension by Discord user",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": "The Discord user to lookup",
|
||||||
|
"type": 6,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "whois",
|
||||||
|
"description": "Find Discord user by extension",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "extension",
|
||||||
|
"description": "The extension to lookup",
|
||||||
|
"type": 4,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "manage",
|
||||||
|
"description": "Manage extensions",
|
||||||
|
"type": 1,
|
||||||
|
"default_member_permissions": 0,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "create",
|
||||||
|
"description": "Create an extension",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": "The Discord user to assign the extension to",
|
||||||
|
"type": 6,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "extension",
|
||||||
|
"description": "The extension number",
|
||||||
|
"type": 4,
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "delete",
|
||||||
|
"description": "Delete an extension",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": "The Discord user whose extension is to be deleted",
|
||||||
|
"type": 6,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lookup",
|
||||||
|
"description": "Lookup an extension",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": "The Discord user whose extension is to be deleted",
|
||||||
|
"type": 6,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "link",
|
||||||
|
"description": "Link an existing extension to a Discord user",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"description": "The Discord user to link the extension to",
|
||||||
|
"type": 6,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "extension",
|
||||||
|
"description": "The extension number to link",
|
||||||
|
"type": 4,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unlink",
|
||||||
|
"description": "Unlink an extension from a Discord user",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "extension",
|
||||||
|
"description": "The extension number to unlink",
|
||||||
|
"type": 4,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "list-orphans",
|
||||||
|
"description": "List all orphaned extensions (extensions without a Discord user linked)",
|
||||||
|
"type": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
170
commands.json
170
commands.json
|
@ -1,170 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "whoami",
|
|
||||||
"description": "Get your extension info if you have one",
|
|
||||||
"type": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "new",
|
|
||||||
"description": "Get an extension on the LiteNet Phone System",
|
|
||||||
"type": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete",
|
|
||||||
"description": "Remove your extension from the LiteNet Phone System",
|
|
||||||
"type": 1,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "confirm",
|
|
||||||
"description": "Confirm that you want to delete your extension. THIS CANNOT BE UNDONE!",
|
|
||||||
"type": 5,
|
|
||||||
"required": true,
|
|
||||||
"choices": [
|
|
||||||
{
|
|
||||||
"name": "yes",
|
|
||||||
"value": "yes"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "list",
|
|
||||||
"description": "List all extensions on the LiteNet Phone System",
|
|
||||||
"type": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "button",
|
|
||||||
"description": "Send the get an extension button!",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"description": "Change your extension's name (Defaults to your Discord name)",
|
|
||||||
"type": 1,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"description": "The new name for your extension",
|
|
||||||
"type": 3,
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"description": "Admin only commands",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "silence",
|
|
||||||
"description": "Kill all ongoing calls",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reload",
|
|
||||||
"description": "Run asterisk reload",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "reboot",
|
|
||||||
"description": "Reboot the server (LAST RESORT)",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dev",
|
|
||||||
"description": "Developer only commands",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "fwconsole",
|
|
||||||
"description": "Run an fwconsole command",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "command",
|
|
||||||
"description": "The command to run",
|
|
||||||
"type": 3,
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "asterisk",
|
|
||||||
"description": "Run an asterisk CLI command",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "command",
|
|
||||||
"description": "The command to run",
|
|
||||||
"type": 3,
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shell",
|
|
||||||
"description": "Run a shell command",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "command",
|
|
||||||
"description": "The command to run",
|
|
||||||
"type": 3,
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "restart",
|
|
||||||
"description": "Restart the bot",
|
|
||||||
"type": 1,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cdr",
|
|
||||||
"description": "Get the call detail records for your extension",
|
|
||||||
"type": 1,
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "start_date",
|
|
||||||
"description": "The start date for the CDR (mm/dd/yyyy)",
|
|
||||||
"type": 3,
|
|
||||||
"required": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "end_date",
|
|
||||||
"description": "The end date for the CDR (mm/dd/yyyy)",
|
|
||||||
"type": 3,
|
|
||||||
"required": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Lookup Extension",
|
|
||||||
"type": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Create Extension",
|
|
||||||
"type": 2,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Delete Extension",
|
|
||||||
"type": 2,
|
|
||||||
"default_member_permissions": 0
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,39 +0,0 @@
|
||||||
{
|
|
||||||
"ntfyUrl": "ntfy-url",
|
|
||||||
"freepbx": {
|
|
||||||
"server": "sip-server-ip",
|
|
||||||
"url": "pbx-api-url",
|
|
||||||
"clientid": "gql-client-id",
|
|
||||||
"allowedscopes": "gql",
|
|
||||||
"secret": "gql-secret",
|
|
||||||
"startExt": 1000
|
|
||||||
},
|
|
||||||
"discord": {
|
|
||||||
"token": "bot-token",
|
|
||||||
"guildId": "guild-id",
|
|
||||||
"roleId": "user-role",
|
|
||||||
"logId": "log-channel",
|
|
||||||
"extList": "extension-list-channel",
|
|
||||||
"developers": [
|
|
||||||
"your-user-id"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"mariadb": {
|
|
||||||
"host": "db-hostname0here",
|
|
||||||
"user": "bot",
|
|
||||||
"password": "bot",
|
|
||||||
"database": "asterisk",
|
|
||||||
"connectionLimit": 5
|
|
||||||
},
|
|
||||||
"cdrdb": {
|
|
||||||
"host": "db-hostname-here",
|
|
||||||
"user": "bot",
|
|
||||||
"password": "bot",
|
|
||||||
"database": "asteriskcdrdb",
|
|
||||||
"connectionLimit": 5
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"interval": 60,
|
|
||||||
"url": "uptime-kuma-link"
|
|
||||||
}
|
|
||||||
}
|
|
36
debug.js
Normal file
36
debug.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
require("dotenv").config();
|
||||||
|
const cron = require("node-cron")
|
||||||
|
const fs = require('fs');
|
||||||
|
const mariadb = require("mariadb");
|
||||||
|
const pool = mariadb.createPool({
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT || 3306,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASS,
|
||||||
|
database: "asterisk",
|
||||||
|
connectionLimit: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const FreepbxManager = require("./freepbx");
|
||||||
|
const fpbx = new FreepbxManager({
|
||||||
|
url: process.env.FREEPBX_URL,
|
||||||
|
clientId: process.env.FREEPBX_CLIENT_ID,
|
||||||
|
clientSecret: process.env.FREEPBX_CLIENT_SECRET,
|
||||||
|
dbPool: pool,
|
||||||
|
});
|
||||||
|
|
||||||
|
// fpbx.listExtensions()
|
||||||
|
// .then((extensions) => {
|
||||||
|
// console.log(JSON.stringify(extensions, null, 2));
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// console.error(err);
|
||||||
|
// });
|
||||||
|
|
||||||
|
fpbx.getNextAvailableExtension()
|
||||||
|
.then((extension) => {
|
||||||
|
console.log(`Next available extension: ${extension}`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
72
deletions.js
Normal file
72
deletions.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
var error_shown = false;
|
||||||
|
module.exports.handleScheduled = async () => {
|
||||||
|
if (error_shown) return;
|
||||||
|
if (!process.env.DISCORD_GUILD) {
|
||||||
|
error_shown = true;
|
||||||
|
return log.error(`Environment variable DISCORD_GUILD is not set! Cannot handle automatic deletions. Please set this variable and restart to enable this feature.`);
|
||||||
|
}
|
||||||
|
const deletions = await pool.query('SELECT * FROM discord_deletions');
|
||||||
|
if (!deletions) return;
|
||||||
|
|
||||||
|
for (const deletion of deletions) {
|
||||||
|
const guild = client.guilds.cache.get(process.env.DISCORD_GUILD);
|
||||||
|
const member = guild ? await guild.members.fetch(deletion.discordId).catch(() => null) : null;
|
||||||
|
const ext = await fpbx.getExtension(deletion.extension);
|
||||||
|
if (!ext) {
|
||||||
|
log.error(`Failed to get extension for deletion: ${deletion.discordId}. Assuming it's already gone. Deleting from database.`);
|
||||||
|
await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
await pool.query('DELETE FROM discord_users WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (member) {
|
||||||
|
log.info(`User ${deletion.discordId} rejoined, removing deletion`);
|
||||||
|
await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
return;
|
||||||
|
} else if (new Date(deletion.deleteAt) < new Date()) {
|
||||||
|
log.info(`Deleting extension for ${deletion.discordId}`);
|
||||||
|
const deleteResp = await fpbx.deleteExtension(deletion.extension);
|
||||||
|
if (!deleteResp[0].deleteExtension.status) {
|
||||||
|
log.error(`Failed to delete extension for ${deletion.discordId}`);
|
||||||
|
await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.info(`Deleted extension for ${deletion.discordId}`);
|
||||||
|
await pool.query('DELETE FROM discord_users WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
}
|
||||||
|
await fpbx.reload();
|
||||||
|
await pool.query('DELETE FROM discord_deletions WHERE discordId = ?', [deletion.discordId]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.findOrphans = async () => {
|
||||||
|
if (error_shown) return;
|
||||||
|
if (!process.env.DISCORD_GUILD) {
|
||||||
|
error_shown = true;
|
||||||
|
return log.error(`Environment variable DISCORD_GUILD is not set! Cannot handle automatic deletions. Please set this variable and restart to enable this feature.`);
|
||||||
|
}
|
||||||
|
const users = await pool.query('SELECT * FROM discord_users');
|
||||||
|
const deletions = await pool.query('SELECT * FROM discord_deletions');
|
||||||
|
const guild = client.guilds.cache.get(process.env.DISCORD_GUILD);
|
||||||
|
if (!users) return
|
||||||
|
for (const user of users) {
|
||||||
|
const member = guild ? await guild.members.fetch(user.discordId).catch(() => null) : null;
|
||||||
|
if (!member) {
|
||||||
|
log.info(`User ${user.discordId} not found in guild, marking for deletion`);
|
||||||
|
const isMarkedForDeletion = deletions.some(deletion => deletion.discordId === user.discordId);
|
||||||
|
if (isMarkedForDeletion) {
|
||||||
|
log.info(`User ${user.discordId} is already marked for deletion`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const deleteAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour in the future
|
||||||
|
await pool.query('INSERT INTO discord_deletions (discordId, extension, deleteAt) VALUES (?, ?, ?)', [user.discordId, user.extension, deleteAt]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"controls": [
|
|
||||||
{
|
|
||||||
"title": "Phone System Controls",
|
|
||||||
"color": 205442,
|
|
||||||
"description": "Use the buttons below to control your extension!"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
216
freepbx.js
Normal file
216
freepbx.js
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
const { FreepbxGqlClient, gql } = require("freepbx-graphql-client");
|
||||||
|
|
||||||
|
class FreepbxManager {
|
||||||
|
/**
|
||||||
|
* Creates an instance of the FreepbxGqlClient and initializes the connection pool.
|
||||||
|
*
|
||||||
|
* @param {Object} config - Configuration object for the FreepbxGqlClient.
|
||||||
|
* @param {string} config.url - The URL of the FreePBX GraphQL endpoint.
|
||||||
|
* @param {string} config.clientId - The client ID for authentication.
|
||||||
|
* @param {string} config.clientSecret - The client secret for authentication.
|
||||||
|
* @param {Object} config.dbPool - The connection pool for managing database connections.
|
||||||
|
*/
|
||||||
|
constructor(config) {
|
||||||
|
this.client = null;
|
||||||
|
this.renewClient = async () => {
|
||||||
|
this.client = new FreepbxGqlClient(config.url, {
|
||||||
|
client: {
|
||||||
|
id: config.clientId,
|
||||||
|
secret: config.clientSecret,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.renewClient();
|
||||||
|
|
||||||
|
this.pbxCall = async (query, variables) => {
|
||||||
|
try {
|
||||||
|
return await this.client.request(query, variables).catch(err => {
|
||||||
|
throw err;
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response && err.response.error && err.response.error.message === "The resource owner or authorization server denied the request.") {
|
||||||
|
await this.renewClient();
|
||||||
|
console.log("Client renewed");
|
||||||
|
return await this.pbxCall(query, variables);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pool = config.dbPool;
|
||||||
|
|
||||||
|
if (!this.pool) {
|
||||||
|
throw new Error("Connection pool is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async getExtension(ext) {
|
||||||
|
ext = String(ext);
|
||||||
|
|
||||||
|
const query = gql`
|
||||||
|
query fetchExtension($extensionId: ID!) {
|
||||||
|
fetchExtension(extensionId: $extensionId) {
|
||||||
|
user {
|
||||||
|
extension
|
||||||
|
name
|
||||||
|
extPassword
|
||||||
|
voicemail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
extensionId: ext.match(/\d+/)[0],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.pbxCall(query, variables);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch extension:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listExtensions() {
|
||||||
|
const query = gql`
|
||||||
|
query {
|
||||||
|
fetchAllExtensions {
|
||||||
|
extension {
|
||||||
|
user {
|
||||||
|
extension
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return await this.pbxCall(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addExtension(ext, name) {
|
||||||
|
ext = String(ext);
|
||||||
|
name = String(name);
|
||||||
|
name = name.replace(/[^a-zA-Z0-9\s]/g, '');
|
||||||
|
const query = gql`
|
||||||
|
mutation addExtension($ext: ID!, $name: String!, $vmPassword: String!) {
|
||||||
|
addExtension(input: {
|
||||||
|
extensionId: $ext
|
||||||
|
name: $name
|
||||||
|
vmEnable: true
|
||||||
|
vmPassword: $vmPassword
|
||||||
|
email: ""
|
||||||
|
maxContacts: "100"
|
||||||
|
umEnable: false
|
||||||
|
}) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
ext,
|
||||||
|
name,
|
||||||
|
vmPassword: ext,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.pbxCall(query, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteExtension(ext) {
|
||||||
|
ext = String(ext);
|
||||||
|
const query = gql`
|
||||||
|
mutation deleteExtension($ext: ID!) {
|
||||||
|
deleteExtension(input: { extensionId: $ext }) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
ext,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fpbxQuery = this.pbxCall(query, variables);
|
||||||
|
const dbQuery = this.pool.query('DELETE FROM paging_groups WHERE ext = ?', [ext]);
|
||||||
|
return await Promise.all([fpbxQuery, dbQuery]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload() {
|
||||||
|
const query = gql`
|
||||||
|
mutation {
|
||||||
|
doreload(input: { clientMutationId: "${Math.random().toString(36).substring(2, 14)}" }) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return await this.pbxCall(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// async updateName(ext, name) {
|
||||||
|
// const query = gql`
|
||||||
|
// mutation updateName($ext: ID!, $name: String!) {
|
||||||
|
// updateExtension(input: {extensionId: $ext, name: $name}) {
|
||||||
|
// status,
|
||||||
|
// message
|
||||||
|
// }
|
||||||
|
// }`;
|
||||||
|
|
||||||
|
// const variables = {
|
||||||
|
// ext,
|
||||||
|
// name,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// return await this.pbxCall(query, variables);
|
||||||
|
// }
|
||||||
|
// TODO: Implement updateName method, Current implementation resets extension for some reason
|
||||||
|
|
||||||
|
async joinPageGroup(ext, pageGroup) {
|
||||||
|
const [lookup] = await this.pool.query('SELECT * FROM paging_groups WHERE page_number = ? AND ext = ?', [pageGroup, ext]);
|
||||||
|
if (lookup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.pool.query('INSERT INTO paging_groups (page_number, ext) VALUES (?, ?)', [pageGroup, ext]);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async leavePageGroup(ext, pageGroup) {
|
||||||
|
const [lookup] = await this.pool.query('SELECT * FROM paging_groups WHERE page_number = ? AND ext = ?', [pageGroup, ext]);
|
||||||
|
if (!lookup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.pool.query('DELETE FROM paging_groups WHERE page_number = ? AND ext = ?', [pageGroup, ext]);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
async getNextAvailableExtension() {
|
||||||
|
const extList = await this.listExtensions();
|
||||||
|
const exts = extList.fetchAllExtensions.extension;
|
||||||
|
const startExt = process.env.START_EXT ? parseInt(process.env.START_EXT, 10) : 1000;
|
||||||
|
// Remove duplicates by using a Set
|
||||||
|
const existingExtsSet = new Set(exts.map(ext => parseInt(ext.user.extension, 10)));
|
||||||
|
const existingExts = Array.from(existingExtsSet).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
let nextExt = startExt;
|
||||||
|
for (let i = 0; i < existingExts.length; i++) {
|
||||||
|
if (existingExts[i] !== nextExt) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextExt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FreepbxManager;
|
97
funcs.js
97
funcs.js
|
@ -1,97 +0,0 @@
|
||||||
// Some random functions, as to not clutter the main file
|
|
||||||
// Generate GraphQL query
|
|
||||||
const generateQuery = (type, args) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'lookup':
|
|
||||||
return minifyQuery(`query {
|
|
||||||
fetchExtension(extensionId: "${args.ext}") {
|
|
||||||
user {
|
|
||||||
extension
|
|
||||||
name
|
|
||||||
extPassword
|
|
||||||
voicemail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchVoiceMail(extensionId: "${args.ext}") {
|
|
||||||
password
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
return minifyQuery(`query {
|
|
||||||
fetchAllExtensions {
|
|
||||||
extension {
|
|
||||||
user {
|
|
||||||
extension
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
break;
|
|
||||||
case 'add':
|
|
||||||
return minifyQuery(`mutation {
|
|
||||||
addExtension(input: {
|
|
||||||
extensionId: "${args.ext}"
|
|
||||||
name: "${args.name}"
|
|
||||||
email: "${args.uid}"
|
|
||||||
vmEnable: true
|
|
||||||
vmPassword: "${args.ext}"
|
|
||||||
maxContacts: "5"
|
|
||||||
umEnable: false
|
|
||||||
}) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'delete':
|
|
||||||
return minifyQuery(`mutation {
|
|
||||||
deleteExtension(input: {extensionId: ${args.ext}}) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
break;
|
|
||||||
case 'reload':
|
|
||||||
return minifyQuery(`mutation {
|
|
||||||
doreload(input: {clientMutationId: "${args.id}"}) {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
break;
|
|
||||||
case 'update_name':
|
|
||||||
return minifyQuery(`mutation {
|
|
||||||
updateCoreUser (input: {extension: ${args.ext}, name: "${args.name}", noanswer_cid: "", busy_cid: "", chanunavail_cid: "", busy_dest: "", noanswer_dest: "", chanunavail_dest: ""}) {
|
|
||||||
coreuser {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// minify query function
|
|
||||||
const minifyQuery = (query) => {
|
|
||||||
return query.replace(/\s+/g, ' ').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
generateQuery,
|
|
||||||
minifyQuery,
|
|
||||||
// Input validation
|
|
||||||
validateInput: function (input, type) {
|
|
||||||
switch (type) {
|
|
||||||
case 'extention':
|
|
||||||
// Check if input is a 3 digit number
|
|
||||||
if (input.length != 3) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isNaN(input)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
81
interactionHandlers/commands/admin.js
Normal file
81
interactionHandlers/commands/admin.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
|
||||||
|
const runCommand = (command, onData) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const process = exec(command);
|
||||||
|
|
||||||
|
process.stdout.on('data', (data) => {
|
||||||
|
if (onData) {
|
||||||
|
onData(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stderr.on('data', (data) => {
|
||||||
|
reject(`stderr: ${data}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('close', (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject(`process exited with code ${code}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve('Command executed successfully');
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('error', (error) => {
|
||||||
|
reject(`error: ${error.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
|
||||||
|
switch (subcommand) {
|
||||||
|
case 'silence': // Run `asterisk -x "channel request hangup all"
|
||||||
|
runCommand('asterisk -x "channel request hangup all"').then((res) => {
|
||||||
|
interaction.reply({ content: `Silenced`, ephemeral: true });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'reload': // Run `fwconsole reload`
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
runCommand('fwconsole reload', (data) => {
|
||||||
|
interaction.editReply({ content: data, ephemeral: true });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'reboot': // Run `reboot 0`
|
||||||
|
await interaction.reply({ content: "Rebooting...", ephemeral: true });
|
||||||
|
await client.destroy();
|
||||||
|
log.info('Client destroyed.');
|
||||||
|
pool.end((err) => {
|
||||||
|
if (err) {
|
||||||
|
log.error('Error closing database pool:', err);
|
||||||
|
} else {
|
||||||
|
log.info('Database pool closed.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
runCommand('reboot 0');
|
||||||
|
break;
|
||||||
|
case 'list-deletions':
|
||||||
|
const deletions = await pool.query('SELECT * FROM discord_deletions');
|
||||||
|
if (!deletions.length) {
|
||||||
|
await interaction.reply({ content: 'No pending deletions.', ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletionList = deletions.map((deletion) => {
|
||||||
|
return `Member: <@${deletion.discordId}> (${deletion.discordId}), Extension: ${deletion.extension}, Delete At: <t:${deletion.deleteAt / 1000}>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ content: deletionList.join('\n'), ephemeral: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
102
interactionHandlers/commands/button.js
Normal file
102
interactionHandlers/commands/button.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
const Discord = require("discord.js")
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
/* Holding for upcoming sponsorship embed
|
||||||
|
{
|
||||||
|
"title": "The LiteNet Community PBX",
|
||||||
|
"description": "The LiteNet Community PBX is hosted through, and is sponsored by Snakecraft Hosting!\nOffering affordable game hosting, Discord bot hosting, and VPS services since 2020.\nGet started at https://go.litenet.tel/sch-affiliate\nCheck them out on [Discord](https://discord.gg/nZFQTaZWqT)\n\n-# The link above is an affiliate link. We will receive credits from any purchase made via this link.\n-# Snakecraft Hosting has no administrative control over, nor has access to private information stored on LiteNet. Snakecraft Hosting provides hosting for LiteNet free of charge. Specific details regarding their affiliate program can be found [here](https://my.snakecrafthosting.com/index.php?rp=/knowledgebase/4/Affiliate-Program-FAQs.html)",
|
||||||
|
"color": 7955428,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "What's this?",
|
||||||
|
"value": "The community PBX is a public, free to use [FreePBX](https://freepbx.org) based phone system that any server member is welcome to get a number on!\nEveryone on the system has their own 4 digit number, that can be used to call between other members on the system.\nThe PBX runs on a Snakecraft Hosting VPS graciously provided to us at no cost!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "What can it do?",
|
||||||
|
"value": "The LiteNet phone system offers many features, including but not limited to the following:\n- Free inbound/outbound calling via +1 (610) LITENET (548-3638)\n- Private Voicemail\n- Intercom/Paging\n- Conference Rooms\n- Direct dial access to [AstroCom](https://astrocom.tel)\n- [Full extension status page](https://pbx.litenet.tel/status)\n- And more!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Privacy Policy",
|
||||||
|
"value": "LiteNet respects the privacy of all members, and as such, only very few select staff members have access to the system files directly. Voicemails are not tracked nor listened to under any circumstances. Call logs are kept and only reviewed during investigations into violations of community guidelines, or possible illegal activity. Call recordings may be kept at the request of any individual member, and will NOT be reviewed unless prior permission was given from said member.\nAll user data may be deleted by request, or by simply running the `/delete` command.\n\nIf you believe your privacy has been violated in any way, please don't hesistate to reach out to any of our staff members!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Can it do `X`?",
|
||||||
|
"value": "Any specific questions are welcome to be asked in our <#1102782499756724239> chat!\nIf you have a suggestion for something we should add to the PBX or Discord server, feel free to leave it in <#1148099609428762634>!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"footer": {
|
||||||
|
"text": "Made with <3 by Chris Chrome & The LiteNet Team • Sponsored by Snakecraft Hosting",
|
||||||
|
"icon_url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller.png"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"url": "https://f.chrischro.me/assets/litenet-full.png"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller-rounder.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
interaction.channel.send({
|
||||||
|
embeds: [{
|
||||||
|
"title": "The LiteNet Community PBX",
|
||||||
|
"description": "The LiteNet Community PBX is hosted through, and is sponsored by Snakecraft Hosting!\nOffering affordable game hosting, Discord bot hosting, and VPS services since 2020.\nGet started at https://go.litenet.tel/sch-affiliate\nCheck them out on [Discord](https://discord.gg/nZFQTaZWqT)\n\n-# The link above is an affiliate link. We will receive credits from any purchase made via this link.\n-# Snakecraft Hosting has no administrative control over, nor has access to private information stored on LiteNet. Snakecraft Hosting provides hosting for LiteNet free of charge. Specific details regarding their affiliate program can be found [here](https://my.snakecrafthosting.com/index.php?rp=/knowledgebase/4/Affiliate-Program-FAQs.html)",
|
||||||
|
"color": 7955428,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "What's this?",
|
||||||
|
"value": "The community PBX is a public, free to use [FreePBX](https://freepbx.org) based phone system that any server member is welcome to get a number on!\nEveryone on the system has their own 4 digit number, that can be used to call between other members on the system.\nThe PBX runs on a Snakecraft Hosting VPS graciously provided to us at no cost!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "What can it do?",
|
||||||
|
"value": "The LiteNet phone system offers many features, including but not limited to the following:\n- Free inbound/outbound calling via +1 (610) LITENET (548-3638)\n- Private Voicemail\n- Intercom/Paging\n- Conference Rooms\n- Direct dial access to [AstroCom](https://astrocom.tel)\n- [Full extension status page](https://pbx.litenet.tel/status)\n- And more!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Privacy Policy",
|
||||||
|
"value": "LiteNet respects the privacy of all members, and as such, only very few select staff members have access to the system files directly. Voicemails are not tracked nor listened to under any circumstances. Call logs are kept and only reviewed during investigations into violations of community guidelines, or possible illegal activity. Call recordings may be kept at the request of any individual member, and will NOT be reviewed unless prior permission was given from said member.\nAll user data may be deleted by request, or by simply running the `/delete` command.\n\nIf you believe your privacy has been violated in any way, please don't hesistate to reach out to any of our staff members!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Can it do `X`?",
|
||||||
|
"value": "Any specific questions are welcome to be asked in our <#1102782499756724239> chat!\nIf you have a suggestion for something we should add to the PBX or Discord server, feel free to leave it in <#1148099609428762634>!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"footer": {
|
||||||
|
"text": "Made with <3 by Chris Chrome & The LiteNet Team • Sponsored by Snakecraft Hosting",
|
||||||
|
"icon_url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller.png"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"url": "https://f.chrischro.me/assets/litenet-full.png"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"url": "https://f.chrischro.me/assets/Snakecraft-Social-Media-purple-v2-smaller-rounder.png"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: Discord.ComponentType.Button,
|
||||||
|
label: "Get an Extension",
|
||||||
|
emoji: "✅",
|
||||||
|
style: Discord.ButtonStyle.Success,
|
||||||
|
custom_id: "newExtension"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: Discord.ComponentType.Button,
|
||||||
|
label: "Get your extension info",
|
||||||
|
emoji: "ℹ️",
|
||||||
|
style: Discord.ButtonStyle.Primary,
|
||||||
|
custom_id: "getExtensionInfo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
5
interactionHandlers/commands/cdr.js
Normal file
5
interactionHandlers/commands/cdr.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
module.exports = {}
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
interaction.reply({ephemeral: true, content: `To be implemented!`})
|
||||||
|
}
|
30
interactionHandlers/commands/delete.js
Normal file
30
interactionHandlers/commands/delete.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [interaction.user.id]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `We're sorry, It doesn't look like you have an extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (interaction.options.getBoolean("confirm") !== true) {
|
||||||
|
await interaction.reply({ content: `You must confirm you want to delete your extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fpbx.deleteExtension(lookup.extension).then(async (res) => {
|
||||||
|
if (res[0].deleteExtension.status != true) {
|
||||||
|
await interaction.reply({ content: `Something went wrong :(`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('DELETE FROM discord_users WHERE discordId = ?', [interaction.user.id]);
|
||||||
|
await fpbx.reload();
|
||||||
|
await interaction.reply({ content: `Extension ${lookup.extension} deleted!`, ephemeral: true });
|
||||||
|
}).catch(async (error) => {
|
||||||
|
log.error(error);
|
||||||
|
await interaction.reply({ content: 'There was an error while deleting your extension!', ephemeral: true });
|
||||||
|
});
|
||||||
|
}
|
111
interactionHandlers/commands/dev.js
Normal file
111
interactionHandlers/commands/dev.js
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const { on } = require('events');
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
|
||||||
|
const runCommand = (command, onData) => {
|
||||||
|
try {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const process = exec(command);
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
process.kill();
|
||||||
|
resolve({ output: output, code: 1 });
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
process.stdout.on('data', (data) => {
|
||||||
|
output += data;
|
||||||
|
if (onData) {
|
||||||
|
onData(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stderr.on('data', (data) => {
|
||||||
|
onData(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('close', (code) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (code !== 0) {
|
||||||
|
resolve({ output: output, code: code });
|
||||||
|
}
|
||||||
|
resolve({ output: output, code: code });
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('error', (error) => {
|
||||||
|
reject(`error: ${error.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
if (interaction.user.id !== process.env.OWNER_ID) {
|
||||||
|
await interaction.reply({ content: "You do not have permission to run this command.", ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
|
||||||
|
switch (subcommand) {
|
||||||
|
case 'fwconsole': // Run an arbitrary fwconsole command
|
||||||
|
const command = interaction.options.getString('command');
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var output = '';
|
||||||
|
runCommand(`fwconsole ${command}`, (data) => {
|
||||||
|
output += data;
|
||||||
|
if (output.length >= 1500) {
|
||||||
|
output = output.substring(output.length - 1500);
|
||||||
|
}
|
||||||
|
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
|
||||||
|
}).then((fullOutput) => {
|
||||||
|
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
|
||||||
|
const buffer = Buffer.from(fullOutput.output, 'utf-8');
|
||||||
|
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
|
||||||
|
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\`\nProcess returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 'asterisk': // Run arbitrary asterisk command with asterisk -x "command"
|
||||||
|
const asteriskCommand = interaction.options.getString('command');
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var output = '';
|
||||||
|
runCommand(`asterisk -x "${asteriskCommand}"`, (data) => {
|
||||||
|
output += data;
|
||||||
|
if (output.length >= 1500) {
|
||||||
|
output = output.substring(output.length - 1500);
|
||||||
|
}
|
||||||
|
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
|
||||||
|
}).then((fullOutput) => {
|
||||||
|
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
|
||||||
|
const buffer = Buffer.from(fullOutput.output, 'utf-8');
|
||||||
|
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
|
||||||
|
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\`\nProcess returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 'shell': // Run any arbitrary shell command
|
||||||
|
const shellCommand = interaction.options.getString('command');
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var output = '';
|
||||||
|
runCommand(shellCommand, (data) => {
|
||||||
|
output += data;
|
||||||
|
if (output.length >= 1500) {
|
||||||
|
output = output.substring(output.length - 1500);
|
||||||
|
}
|
||||||
|
interaction.editReply({ content: `\`\`\`ansi\n${output}\`\`\``, ephemeral: true });
|
||||||
|
}).then((fullOutput) => {
|
||||||
|
output = output.length > 1500 ? output.substring(output.length - 1500) : output;
|
||||||
|
const buffer = Buffer.from(fullOutput.output, 'utf-8');
|
||||||
|
const attachment = new Discord.AttachmentBuilder(buffer, {name: 'output.txt'});
|
||||||
|
interaction.editReply({ content: `Process returned code ${fullOutput.code}`, files: [attachment], ephemeral: true });
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
36
interactionHandlers/commands/list.js
Normal file
36
interactionHandlers/commands/list.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const lookup = await pool.query('SELECT * FROM discord_users');
|
||||||
|
// lookup: [ { extension: '1001', discord_id: '1234567890' } ]
|
||||||
|
const embeds = [];
|
||||||
|
let description = '';
|
||||||
|
|
||||||
|
lookup.forEach((row, index) => {
|
||||||
|
const line = `${row.extension}: <@${row.discordId}>\n`;
|
||||||
|
if (description.length > 2048) {
|
||||||
|
embeds.push({
|
||||||
|
description
|
||||||
|
});
|
||||||
|
description = '';
|
||||||
|
}
|
||||||
|
description += line;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (description.length > 0) {
|
||||||
|
embeds.push({
|
||||||
|
description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
embeds.forEach(async (embed) => {
|
||||||
|
await interaction.user.send({ embeds: [embed] });
|
||||||
|
})
|
||||||
|
await interaction.reply({ ephemeral: true, content: "Check your DMs!" });
|
||||||
|
}
|
19
interactionHandlers/commands/lookup.js
Normal file
19
interactionHandlers/commands/lookup.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const findUser = interaction.options.getUser('user');
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [findUser.id]);
|
||||||
|
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `No extension found for ${findUser.username}`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply({ content: `${findUser} has extension ${lookup.extension}`, ephemeral: true });
|
||||||
|
|
||||||
|
}
|
142
interactionHandlers/commands/manage.js
Normal file
142
interactionHandlers/commands/manage.js
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
|
||||||
|
switch (subcommand) {
|
||||||
|
case 'create': // Create an extension for a user
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var forUser = interaction.options.getUser('user');
|
||||||
|
var newExt = interaction.options.getInteger('extension') ? interaction.options.getInteger('extension') : await fpbx.getNextAvailableExtension();
|
||||||
|
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
|
||||||
|
if (lookup) {
|
||||||
|
await interaction.editReply({ content: `User already has an extension, it's ${lookup.extension}!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await interaction.editReply({ content: `Creating extension ${newExt} for ${forUser.username}`, ephemeral: true });
|
||||||
|
fpbx.addExtension(newExt, forUser.username).then(async (res) => {
|
||||||
|
if (res.addExtension.status != true) {
|
||||||
|
await interaction.editReply({ content: `Something went wrong :(`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('INSERT INTO discord_users (discordId, extension) VALUES (?, ?)', [forUser.id, newExt]);
|
||||||
|
await interaction.editReply({ content: `Extension ${newExt} created! Getting info..`, ephemeral: true });
|
||||||
|
await fpbx.reload();
|
||||||
|
const extInfo = await fpbx.getExtension(newExt);
|
||||||
|
await interaction.editReply({ embeds: [{
|
||||||
|
title: "Extension Info",
|
||||||
|
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
|
||||||
|
color: 0x00ff00
|
||||||
|
}], ephemeral: true })
|
||||||
|
}).catch(async (error) => {
|
||||||
|
log.error(error);
|
||||||
|
await interaction.editReply({ content: 'There was an error while creating the extension!', ephemeral: true });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "delete": // Delete an extension for a user
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var forUser = interaction.options.getUser('user');
|
||||||
|
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.editReply({ content: `User does not have an extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await interaction.editReply({ content: `Deleting extension ${lookup.extension} for ${forUser.username}`, ephemeral: true });
|
||||||
|
fpbx.deleteExtension(lookup.extension).then(async (res) => {
|
||||||
|
console.log(res)
|
||||||
|
if (res[0].deleteExtension.status != true) {
|
||||||
|
await interaction.editReply({ content: `Something went wrong :(`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('DELETE FROM discord_users WHERE discordId = ?', [forUser.id]);
|
||||||
|
await fpbx.reload();
|
||||||
|
await interaction.editReply({ content: `Extension ${lookup.extension} deleted!`, ephemeral: true });
|
||||||
|
}).catch(async (error) => {
|
||||||
|
log.error(error);
|
||||||
|
await interaction.editReply({ content: 'There was an error while deleting the extension!', ephemeral: true });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "lookup": // Get user extension info
|
||||||
|
var forUser = interaction.options.getUser('user');
|
||||||
|
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `User does not have an extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const extInfo = await fpbx.getExtension(lookup.extension);
|
||||||
|
return await interaction.reply({
|
||||||
|
ephemeral: true, embeds: [
|
||||||
|
{
|
||||||
|
title: "Extension Info",
|
||||||
|
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
|
||||||
|
color: 0x00ff00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "link": // Link an extension to a user (or at least try to)
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var forUser = interaction.options.getUser('user');
|
||||||
|
var ext = interaction.options.getInteger('extension');
|
||||||
|
if (!ext) {
|
||||||
|
await interaction.editReply({ content: `You must provide an extension to link!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [forUser.id]);
|
||||||
|
if (lookup) {
|
||||||
|
await interaction.editReply({ content: `User already has an extension, it's ${lookup.extension}!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var [extLookup] = await pool.query('SELECT * FROM discord_users WHERE extension = ?', [ext]);
|
||||||
|
if (extLookup) {
|
||||||
|
await interaction.editReply({ content: `Extension ${ext} is already linked to another user! It's linked to ${extLookup.discordId}`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var [extExists] = await pool.query('SELECT * FROM asterisk.devices WHERE id = ?', [ext]);
|
||||||
|
if (!extExists) {
|
||||||
|
await interaction.editReply({ content: `Extension ${ext} does not exist!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('INSERT INTO discord_users (discordId, extension) VALUES (?, ?)', [forUser.id, ext]);
|
||||||
|
await interaction.editReply({ content: `Extension ${ext} linked to ${forUser.username}!`, ephemeral: true });
|
||||||
|
break;
|
||||||
|
case "unlink": // Unlink an extension from a user
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
var ext = interaction.options.getInteger('extension');
|
||||||
|
if (!ext) {
|
||||||
|
await interaction.editReply({ content: `You must provide an extension to unlink!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var [lookup] = await pool.query('SELECT * FROM discord_users WHERE extension = ?', [ext]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.editReply({ content: `Extension ${ext} is not linked to any user!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('DELETE FROM discord_users WHERE extension = ?', [ext]);
|
||||||
|
await interaction.editReply({ content: `Extension ${ext} unlinked from ${lookup.discordId}!`, ephemeral: true });
|
||||||
|
break;
|
||||||
|
case "list-orphans": // List all orphaned extensions (extensions without a Discord user linked)
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
const orphans = await pool.query('SELECT id,description FROM asterisk.devices WHERE id NOT IN (SELECT extension FROM asterisk.discord_users)');
|
||||||
|
if (orphans.length === 0) {
|
||||||
|
await interaction.editReply({ content: 'No orphaned extensions found!', ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
orphans.sort((a, b) => Number(a.id) - Number(b.id));
|
||||||
|
const orphanList = orphans.map(o => `**Extension:** \`${o.id}\` - **Name:** \`${o.description}\``).join('\n');
|
||||||
|
await interaction.editReply({
|
||||||
|
content: `**Orphaned Extensions:**\n${orphanList}`,
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await interaction.reply({ content: 'Unknown subcommand!', ephemeral: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
3
interactionHandlers/commands/new.js
Normal file
3
interactionHandlers/commands/new.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = require("../common/createExt").execute;
|
38
interactionHandlers/commands/paging.js
Normal file
38
interactionHandlers/commands/paging.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const subcommand = interaction.options.getSubcommand();
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [interaction.user.id]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `We're sorry, It doesn't look like you have an extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pageGroup = await interaction.options.getString('group');
|
||||||
|
switch (subcommand) {
|
||||||
|
case 'add':
|
||||||
|
fpbx.joinPageGroup(lookup.extension, pageGroup).then(async (res) => {
|
||||||
|
if (res == true) {
|
||||||
|
await fpbx.reload();
|
||||||
|
await interaction.reply({ content: `Added!`, ephemeral: true });
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: `Something went wrong (Or you're already in that page group!)`, ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
fpbx.leavePageGroup(lookup.extension, pageGroup).then(async (res) => {
|
||||||
|
if (res == true) {
|
||||||
|
await fpbx.reload();
|
||||||
|
await interaction.reply({ content: `Removed!`, ephemeral: true });
|
||||||
|
} else {
|
||||||
|
await interaction.reply({ content: `Something went wrong (Or you're not in that page group!)`, ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
3
interactionHandlers/commands/whoami.js
Normal file
3
interactionHandlers/commands/whoami.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = require("../common/getExtInfo").execute;
|
19
interactionHandlers/commands/whois.js
Normal file
19
interactionHandlers/commands/whois.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const findExt = interaction.options.getInteger('extension');
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE extension = ?', [findExt]);
|
||||||
|
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `No linked Discord account found for extension ${findExt}`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.reply({ content: `${findExt} belongs to <@${lookup.discordId}>`, ephemeral: true });
|
||||||
|
|
||||||
|
}
|
41
interactionHandlers/common/createExt.js
Normal file
41
interactionHandlers/common/createExt.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [interaction.user.id]);
|
||||||
|
if (lookup) {
|
||||||
|
await interaction.editReply({ content: `You already have an extension, it's ${lookup.extension}!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await interaction.editReply({ content: `Finding available extension`, ephemeral: true });
|
||||||
|
fpbx.getNextAvailableExtension().then(async (nextExt) => {
|
||||||
|
await interaction.editReply({ content: `Found ${nextExt}. Creating..`, ephemeral: true });
|
||||||
|
fpbx.addExtension(nextExt, interaction.user.username).then(async (res) => {
|
||||||
|
if (res.addExtension.status != true) {
|
||||||
|
await interaction.editReply({ content: `Something went wrong :(`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await pool.query('INSERT INTO discord_users (discordId, extension) VALUES (?, ?)', [interaction.user.id, nextExt]);
|
||||||
|
await interaction.editReply({ content: `Extension ${nextExt} created! Getting info..`, ephemeral: true });
|
||||||
|
await fpbx.reload();
|
||||||
|
const extInfo = await fpbx.getExtension(nextExt);
|
||||||
|
await interaction.editReply({ embeds: [{
|
||||||
|
title: "Your Extension Info",
|
||||||
|
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
|
||||||
|
color: 0x00ff00
|
||||||
|
}], ephemeral: true })
|
||||||
|
if (process.env.EXTENSION_ROLE_ID) await interaction.member.roles.add(process.env.EXTENSION_ROLE_ID);
|
||||||
|
}).catch(async (error) => {
|
||||||
|
log.error(error);
|
||||||
|
await interaction.editReply({ content: 'There was an error while creating your extension!', ephemeral: true });
|
||||||
|
});
|
||||||
|
}).catch(async (error) => {
|
||||||
|
log.error(error);
|
||||||
|
await interaction.editReply({ content: 'There was an error while creating your extension!', ephemeral: true });
|
||||||
|
});
|
||||||
|
}
|
34
interactionHandlers/common/getExtInfo.js
Normal file
34
interactionHandlers/common/getExtInfo.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
const pool = global.pool
|
||||||
|
const fpbx = global.fpbx
|
||||||
|
const client = global.client
|
||||||
|
const log = global.log
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = async (interaction) => {
|
||||||
|
const [lookup] = await pool.query('SELECT * FROM discord_users WHERE discordId = ?', [interaction.user.id]);
|
||||||
|
if (!lookup) {
|
||||||
|
await interaction.reply({ content: `We're sorry, It doesn't look like you have an extension!`, ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
const extInfo = await fpbx.getExtension(lookup.extension);
|
||||||
|
await interaction.editReply({
|
||||||
|
ephemeral: true, embeds: [
|
||||||
|
{
|
||||||
|
title: "Your Extension Info",
|
||||||
|
description: `**PBX Address:** \`${process.env.PBX_HOSTNAME}\`\n**Extension/Username:** \`${extInfo.fetchExtension.user.extension}\`\n**Name:** \`${extInfo.fetchExtension.user.name}\`\n**Password:** ||\`${extInfo.fetchExtension.user.extPassword}\`||`,
|
||||||
|
color: 0x00ff00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const EXTENSION_ROLE_ID = process.env.EXTENSION_ROLE_ID;
|
||||||
|
if (EXTENSION_ROLE_ID) {
|
||||||
|
const member = await interaction.guild.members.fetch(interaction.user.id);
|
||||||
|
if (!member.roles.cache.has(EXTENSION_ROLE_ID)) {
|
||||||
|
await member.roles.add(EXTENSION_ROLE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
interactionHandlers/components/getExtensionInfo.js
Normal file
3
interactionHandlers/components/getExtensionInfo.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = require("../common/getExtInfo").execute;
|
3
interactionHandlers/components/newExtension.js
Normal file
3
interactionHandlers/components/newExtension.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
module.exports.execute = require("../common/createExt").execute;
|
72
migrations.js
Normal file
72
migrations.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const mariadb = require('mariadb');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const util = require("util")
|
||||||
|
|
||||||
|
|
||||||
|
function runMigrations(pool) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let connection;
|
||||||
|
|
||||||
|
pool.getConnection()
|
||||||
|
.then(conn => {
|
||||||
|
connection = conn;
|
||||||
|
|
||||||
|
// Ensure a migrations table exists to track applied migrations
|
||||||
|
return connection.query(`CREATE TABLE IF NOT EXISTS migrations (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);`);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Read all migration files
|
||||||
|
const migrationDir = path.join(__dirname, 'migrations');
|
||||||
|
const files = fs.readdirSync(migrationDir).sort(); // Sort to apply in order
|
||||||
|
|
||||||
|
return files.reduce((promise, file) => {
|
||||||
|
return promise.then(() => {
|
||||||
|
const migrationName = path.basename(file);
|
||||||
|
|
||||||
|
// Check if the migration has already been applied
|
||||||
|
return connection.query(
|
||||||
|
'SELECT 1 FROM migrations WHERE name = ? LIMIT 1',
|
||||||
|
[migrationName]
|
||||||
|
).then(([rows]) => {
|
||||||
|
if (Object.keys(rows || {}).length > 0) {
|
||||||
|
//console.log(`Skipping already applied migration: ${migrationName}`);
|
||||||
|
return; // Skip this migration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and execute the migration SQL
|
||||||
|
const migrationPath = path.join(migrationDir, file);
|
||||||
|
const sql = fs.readFileSync(migrationPath, 'utf8');
|
||||||
|
return connection.query(sql).then(() => {
|
||||||
|
// Record the applied migration
|
||||||
|
return connection.query(
|
||||||
|
'INSERT INTO migrations (name) VALUES (?)',
|
||||||
|
[migrationName]
|
||||||
|
).then(() => {
|
||||||
|
console.log(`Applied migration: ${migrationName}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('All migrations applied successfully!');
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Error running migrations:', err);
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (connection) connection.release();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = runMigrations
|
1
migrations/000_alter_users_table.sql
Normal file
1
migrations/000_alter_users_table.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE users ADD CONSTRAINT unique_extension UNIQUE (extension);
|
4
migrations/001_init_discord_users_table.sql
Normal file
4
migrations/001_init_discord_users_table.sql
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS discord_users (
|
||||||
|
extension VARCHAR(20) PRIMARY KEY,
|
||||||
|
discordId VARCHAR(25) NOT NULL
|
||||||
|
);
|
5
migrations/002_init_deletions_table.sql
Normal file
5
migrations/002_init_deletions_table.sql
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS discord_deletions (
|
||||||
|
extension VARCHAR(20) PRIMARY KEY,
|
||||||
|
discordId VARCHAR(25) NOT NULL,
|
||||||
|
deleteAt TIMESTAMP NOT NULL
|
||||||
|
);
|
414
package-lock.json
generated
414
package-lock.json
generated
|
@ -12,93 +12,240 @@
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"discord.js": "14.14.1",
|
"discord.js": "14.14.1",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"freepbx-graphql-client": "^0.1.1",
|
"freepbx-graphql-client": "^0.1.1",
|
||||||
"mariadb": "^3.2.0",
|
"mariadb": "^3.2.0",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"sqlite3": "^5.1.4",
|
"sqlite3": "^5.1.4",
|
||||||
"ssh2": "^1.15.0"
|
"ssh2": "^1.15.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/builders": {
|
"node_modules/@discordjs/builders": {
|
||||||
"version": "1.6.5",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.0.tgz",
|
||||||
"integrity": "sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==",
|
"integrity": "sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/formatters": "^0.3.2",
|
"@discordjs/formatters": "^0.6.0",
|
||||||
"@discordjs/util": "^1.0.1",
|
"@discordjs/util": "^1.1.1",
|
||||||
"@sapphire/shapeshift": "^3.9.2",
|
"@sapphire/shapeshift": "^4.0.0",
|
||||||
"discord-api-types": "0.37.50",
|
"discord-api-types": "^0.37.114",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"ts-mixer": "^6.0.3",
|
"ts-mixer": "^6.0.4",
|
||||||
"tslib": "^2.6.1"
|
"tslib": "^2.6.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=16.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@discordjs/builders/node_modules/@discordjs/formatters": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"discord-api-types": "^0.37.114"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/builders/node_modules/discord-api-types": {
|
||||||
|
"version": "0.37.117",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.117.tgz",
|
||||||
|
"integrity": "sha512-d+Z6RKd7v3q22lsil7yASucqMfVVV0s0XSqu3cw7kyHVXiDO/mAnqMzqma26IYnIm2mk3TlupYJDGrdL908ZKA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/builders/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/@discordjs/collection": {
|
"node_modules/@discordjs/collection": {
|
||||||
"version": "1.5.3",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
||||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=16.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/formatters": {
|
"node_modules/@discordjs/formatters": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.3.tgz",
|
||||||
"integrity": "sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==",
|
"integrity": "sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord-api-types": "0.37.50"
|
"discord-api-types": "0.37.61"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=16.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/rest": {
|
"node_modules/@discordjs/rest": {
|
||||||
"version": "2.0.1",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.2.tgz",
|
||||||
"integrity": "sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==",
|
"integrity": "sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/collection": "^1.5.3",
|
"@discordjs/collection": "^2.1.1",
|
||||||
"@discordjs/util": "^1.0.1",
|
"@discordjs/util": "^1.1.1",
|
||||||
"@sapphire/async-queue": "^1.5.0",
|
"@sapphire/async-queue": "^1.5.3",
|
||||||
"@sapphire/snowflake": "^3.5.1",
|
"@sapphire/snowflake": "^3.5.3",
|
||||||
"@vladfrangu/async_event_emitter": "^2.2.2",
|
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||||
"discord-api-types": "0.37.50",
|
"discord-api-types": "^0.37.114",
|
||||||
"magic-bytes.js": "^1.0.15",
|
"magic-bytes.js": "^1.10.0",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "^2.6.3",
|
||||||
"undici": "5.22.1"
|
"undici": "6.19.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": {
|
||||||
|
"version": "3.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
|
||||||
|
"integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v14.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest/node_modules/discord-api-types": {
|
||||||
|
"version": "0.37.117",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.117.tgz",
|
||||||
|
"integrity": "sha512-d+Z6RKd7v3q22lsil7yASucqMfVVV0s0XSqu3cw7kyHVXiDO/mAnqMzqma26IYnIm2mk3TlupYJDGrdL908ZKA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest/node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/rest/node_modules/undici": {
|
||||||
|
"version": "6.19.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
|
||||||
|
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/util": {
|
"node_modules/@discordjs/util": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz",
|
||||||
"integrity": "sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==",
|
"integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@discordjs/ws": {
|
"node_modules/@discordjs/ws": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.0.tgz",
|
||||||
"integrity": "sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==",
|
"integrity": "sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/collection": "^1.5.3",
|
"@discordjs/collection": "^2.1.0",
|
||||||
"@discordjs/rest": "^2.0.1",
|
"@discordjs/rest": "^2.4.1",
|
||||||
"@discordjs/util": "^1.0.1",
|
"@discordjs/util": "^1.1.0",
|
||||||
"@sapphire/async-queue": "^1.5.0",
|
"@sapphire/async-queue": "^1.5.2",
|
||||||
"@types/ws": "^8.5.5",
|
"@types/ws": "^8.5.10",
|
||||||
"@vladfrangu/async_event_emitter": "^2.2.2",
|
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||||
"discord-api-types": "0.37.50",
|
"discord-api-types": "^0.37.114",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "^2.6.2",
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=16.11.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/ws/node_modules/@types/ws": {
|
||||||
|
"version": "8.5.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
|
||||||
|
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/ws/node_modules/discord-api-types": {
|
||||||
|
"version": "0.37.117",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.117.tgz",
|
||||||
|
"integrity": "sha512-d+Z6RKd7v3q22lsil7yASucqMfVVV0s0XSqu3cw7kyHVXiDO/mAnqMzqma26IYnIm2mk3TlupYJDGrdL908ZKA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/ws/node_modules/ws": {
|
||||||
|
"version": "8.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
|
@ -151,31 +298,33 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sapphire/async-queue": {
|
"node_modules/@sapphire/async-queue": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||||
"integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==",
|
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.0.0",
|
"node": ">=v14.0.0",
|
||||||
"npm": ">=7.0.0"
|
"npm": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sapphire/shapeshift": {
|
"node_modules/@sapphire/shapeshift": {
|
||||||
"version": "3.9.2",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
||||||
"integrity": "sha512-YRbCXWy969oGIdqR/wha62eX8GNHsvyYi0Rfd4rNW6tSVVa8p0ELiMEuOH/k8rgtvRoM+EMV7Csqz77YdwiDpA==",
|
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.0.0",
|
"node": ">=v16"
|
||||||
"npm": ">=7.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sapphire/snowflake": {
|
"node_modules/@sapphire/snowflake": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz",
|
||||||
"integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==",
|
"integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.0.0",
|
"node": ">=v14.0.0",
|
||||||
"npm": ">=7.0.0"
|
"npm": ">=7.0.0"
|
||||||
|
@ -196,22 +345,28 @@
|
||||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
|
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.5.7",
|
"version": "22.10.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.10.tgz",
|
||||||
"integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA=="
|
"integrity": "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.20.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.5",
|
"version": "8.5.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz",
|
||||||
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
|
"integrity": "sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vladfrangu/async_event_emitter": {
|
"node_modules/@vladfrangu/async_event_emitter": {
|
||||||
"version": "2.2.2",
|
"version": "2.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz",
|
||||||
"integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==",
|
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v14.0.0",
|
"node": ">=v14.0.0",
|
||||||
"npm": ">=7.0.0"
|
"npm": ">=7.0.0"
|
||||||
|
@ -350,17 +505,6 @@
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/busboy": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
|
||||||
"dependencies": {
|
|
||||||
"streamsearch": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cacache": {
|
"node_modules/cacache": {
|
||||||
"version": "15.3.0",
|
"version": "15.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
|
||||||
|
@ -419,6 +563,7 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
|
@ -512,34 +657,48 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/discord-api-types": {
|
"node_modules/discord-api-types": {
|
||||||
"version": "0.37.50",
|
"version": "0.37.61",
|
||||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.50.tgz",
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz",
|
||||||
"integrity": "sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg=="
|
"integrity": "sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/discord.js": {
|
"node_modules/discord.js": {
|
||||||
"version": "14.13.0",
|
"version": "14.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.14.1.tgz",
|
||||||
"integrity": "sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==",
|
"integrity": "sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/builders": "^1.6.5",
|
"@discordjs/builders": "^1.7.0",
|
||||||
"@discordjs/collection": "^1.5.3",
|
"@discordjs/collection": "1.5.3",
|
||||||
"@discordjs/formatters": "^0.3.2",
|
"@discordjs/formatters": "^0.3.3",
|
||||||
"@discordjs/rest": "^2.0.1",
|
"@discordjs/rest": "^2.1.0",
|
||||||
"@discordjs/util": "^1.0.1",
|
"@discordjs/util": "^1.0.2",
|
||||||
"@discordjs/ws": "^1.0.1",
|
"@discordjs/ws": "^1.0.2",
|
||||||
"@sapphire/snowflake": "^3.5.1",
|
"@sapphire/snowflake": "3.5.1",
|
||||||
"@types/ws": "^8.5.5",
|
"@types/ws": "8.5.9",
|
||||||
"discord-api-types": "0.37.50",
|
"discord-api-types": "0.37.61",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "3.1.3",
|
||||||
"lodash.snakecase": "^4.1.1",
|
"lodash.snakecase": "4.1.1",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "2.6.2",
|
||||||
"undici": "5.22.1",
|
"undici": "5.27.2",
|
||||||
"ws": "^8.13.0"
|
"ws": "8.14.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.11.0"
|
"node": ">=16.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
|
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
@ -583,7 +742,8 @@
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.3",
|
"version": "1.15.3",
|
||||||
|
@ -621,6 +781,7 @@
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/freepbx-graphql-client/-/freepbx-graphql-client-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/freepbx-graphql-client/-/freepbx-graphql-client-0.1.1.tgz",
|
||||||
"integrity": "sha512-JqDTlL0EA/bUMit9aODIupWSqF87WHWrCD6i716FzeOzS46cMFq/OajzftTMwOZQf20MMkJM2HI6CRnNlRGl6A==",
|
"integrity": "sha512-JqDTlL0EA/bUMit9aODIupWSqF87WHWrCD6i716FzeOzS46cMFq/OajzftTMwOZQf20MMkJM2HI6CRnNlRGl6A==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graphql": "^15.6.1",
|
"graphql": "^15.6.1",
|
||||||
"graphql-request": "^3.6.1"
|
"graphql-request": "^3.6.1"
|
||||||
|
@ -831,7 +992,8 @@
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.snakecase": {
|
"node_modules/lodash.snakecase": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -850,9 +1012,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/magic-bytes.js": {
|
"node_modules/magic-bytes.js": {
|
||||||
"version": "1.0.15",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
|
||||||
"integrity": "sha512-bpRmwbRHqongRhA+mXzbLWjVy7ylqmfMBYaQkSs6pac0z6hBTvsgrH0r4FBYd/UYVJBmS6Rp/O+oCCQVLzKV1g=="
|
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
@ -1085,6 +1248,18 @@
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-fetch": {
|
"node_modules/node-fetch": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
@ -1444,14 +1619,6 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/streamsearch": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
@ -1514,14 +1681,16 @@
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
},
|
},
|
||||||
"node_modules/ts-mixer": {
|
"node_modules/ts-mixer": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
||||||
"integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ=="
|
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||||
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/tweetnacl": {
|
"node_modules/tweetnacl": {
|
||||||
"version": "0.14.5",
|
"version": "0.14.5",
|
||||||
|
@ -1529,16 +1698,23 @@
|
||||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
|
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "5.22.1",
|
"version": "5.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz",
|
||||||
"integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==",
|
"integrity": "sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"busboy": "^1.6.0"
|
"@fastify/busboy": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0"
|
"node": ">=14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/unique-filename": {
|
"node_modules/unique-filename": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
|
||||||
|
@ -1562,6 +1738,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
@ -1605,9 +1790,10 @@
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.13.0",
|
"version": "8.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
||||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node index.js"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
@ -12,8 +13,10 @@
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"discord.js": "14.14.1",
|
"discord.js": "14.14.1",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"freepbx-graphql-client": "^0.1.1",
|
"freepbx-graphql-client": "^0.1.1",
|
||||||
"mariadb": "^3.2.0",
|
"mariadb": "^3.2.0",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"sqlite3": "^5.1.4",
|
"sqlite3": "^5.1.4",
|
||||||
"ssh2": "^1.15.0"
|
"ssh2": "^1.15.0"
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "Test",
|
|
||||||
"value": "700"
|
|
||||||
}
|
|
||||||
]
|
|
Loading…
Reference in a new issue