mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-01 16:30:08 +00:00
Merge branch 'main' of https://github.com/discordeno/discordeno into proxy-ws
This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
{
|
||||
"name": "Deno",
|
||||
"dockerFile": "Dockerfile",
|
||||
"name": "Deno",
|
||||
"dockerFile": "Dockerfile",
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash"
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"denoland.vscode-deno"
|
||||
],
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"denoland.vscode-deno"
|
||||
]
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
|
||||
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
||||
|
||||
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
|
||||
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
||||
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "vscode"
|
||||
}
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "vscode"
|
||||
}
|
||||
|
||||
3
.github/CODEOWNERS
vendored
Normal file
3
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* @ayntee @Skillz4Killz
|
||||
|
||||
*.ts @ayntee @Skillz4Killz @itohatweb
|
||||
35
.github/CONTRIBUTING.md
vendored
35
.github/CONTRIBUTING.md
vendored
@@ -15,9 +15,11 @@
|
||||
|
||||
Examples of good PR title:
|
||||
|
||||
- fix(controllers/interactions): cache member from INTERACTION_CREATE payload
|
||||
- fix(handlers/INTERACTION_CREATE): cache member object
|
||||
- docs: improve wording
|
||||
- feat(handlers/guild): add editGuild() function Examples of bad PR title:
|
||||
- feat: add cache manager module
|
||||
- feat(helpers): add editGuild()
|
||||
- refactor(ws/shard): remove redundant checks
|
||||
|
||||
Examples of bad PR title:
|
||||
|
||||
@@ -48,3 +50,32 @@ Examples of bad PR title:
|
||||
wherever possible.
|
||||
- Please follow the
|
||||
[guidelines for inclusive code](https://chromium.googlesource.com/chromium/src/+/master/styleguide/inclusive_code.md).
|
||||
|
||||
## Types Guide
|
||||
|
||||
- Must use snake case (according to Discord API).
|
||||
- Each field or property must be accompanied with a reasonable JSDoc comment
|
||||
right above its type definition.
|
||||
- The name of the type must be prefixed with `Discord`.
|
||||
- Must be placed inside of the types module (in `src/types` directory).
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
avatar: string | null;
|
||||
bot?: boolean;
|
||||
system?: boolean;
|
||||
mfaEnabled?: boolean;
|
||||
locale?: string;
|
||||
verified?: boolean;
|
||||
email?: string;
|
||||
flags?: number;
|
||||
premiumType?: number;
|
||||
}
|
||||
|
||||
export type DiscordUser = SnakeCaseProps<DiscordUserInternal>;
|
||||
```
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug** A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce** Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior** A clear and concise description of what you expected to
|
||||
happen.
|
||||
|
||||
**Screenshots** If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version details (please complete the following information):**
|
||||
|
||||
- Discordeno version: [e.g. 10.5.0]
|
||||
- Deno version: [e.g. 1.8.0]
|
||||
|
||||
**Additional context** Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feat
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.** A clear and
|
||||
concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like** A clear and concise description of what you
|
||||
want to happen.
|
||||
|
||||
**Describe alternatives you've considered** A clear and concise description of
|
||||
any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context** Add any other context or screenshots about the feature
|
||||
request here.
|
||||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Deploy
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
branches: main
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -22,4 +22,4 @@ jobs:
|
||||
commit_message: ${{ github.event.head_commit.message }}
|
||||
cname: discordeno.mod.land
|
||||
keep_files: true
|
||||
enable_jekyll: false
|
||||
enable_jekyll: false
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -9,4 +9,4 @@ jobs:
|
||||
- name: Run fmt check script
|
||||
run: deno fmt --check
|
||||
- name: Run lint script
|
||||
run: deno lint src/** test/** --unstable --ignore=./src/types/
|
||||
run: deno lint src/** test/** --unstable --ignore=./src/types,./src/interactions/types
|
||||
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -13,10 +13,8 @@ jobs:
|
||||
deno-version: ${{ matrix.deno }}
|
||||
- name: Cache dependencies
|
||||
run: deno cache mod.ts
|
||||
- name: Run local tests
|
||||
run: TEST_TYPE=local deno test --allow-env
|
||||
- name: Run API tests
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: TEST_TYPE=api deno test --allow-net --allow-env
|
||||
- name: Run test script
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: deno test --allow-net --allow-env
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -19,4 +19,10 @@ public/
|
||||
.idea/
|
||||
|
||||
# Windows
|
||||
desktop.ini
|
||||
desktop.ini
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
*.code-workspace
|
||||
|
||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"deno.unstable": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll": true
|
||||
}
|
||||
}
|
||||
|
||||
21
README.md
21
README.md
@@ -15,7 +15,8 @@ Discordeno follows [Semantic Versioning](https://semver.org/)
|
||||
- **Secure & stable**: Discordeno is actively maintained to ensure great
|
||||
performance and convenience. Moreover, it internally checks all missing
|
||||
permissions before forwarding a request to the Discord API so that the client
|
||||
does not get globally-banned by Discord.
|
||||
does not get
|
||||
[globally-banned by Discord](https://discord.com/developers/docs/topics/rate-limits#invalid-request-limit).
|
||||
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use,
|
||||
versatile while being efficient and lightweight. Follows
|
||||
[Convention Over Configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
|
||||
@@ -42,7 +43,7 @@ startBot({
|
||||
console.log("Successfully connected to gateway");
|
||||
},
|
||||
messageCreate(message) {
|
||||
if (message.content === "!ping") {
|
||||
if (message.content === "ping") {
|
||||
message.reply("Pong using Discordeno!");
|
||||
}
|
||||
},
|
||||
@@ -50,21 +51,21 @@ startBot({
|
||||
});
|
||||
```
|
||||
|
||||
### Boilerplates
|
||||
### Templates
|
||||
|
||||
Note to developers: don't worry a lot of developers start out programming a
|
||||
Discord bot as their first project (I did 😉) and it is not so easy to do so.
|
||||
Discordeno is designed and built considering all the issues that I and a lot of
|
||||
developers had when I first started out coding Discord bots with existing
|
||||
libraries. If you are a beginner, you can check out these awesome official and
|
||||
unofficial boilerplates:
|
||||
unofficial templates:
|
||||
|
||||
- [Discordeno Boilerplate (official)](https://github.com/discordeno/boilerplate)
|
||||
- [Discordeno Template (official)](https://github.com/discordeno/template)
|
||||
- [Serverless Slash Commands Template
|
||||
(official)](https://github.com/discordeno/slash-commands-boilerplate)
|
||||
(official)](https://github.com/discordeno/slash-commands-template)
|
||||
- [Add Your Own!](https://github.com/discordeno/discordeno/pulls)
|
||||
|
||||
## Useful Links
|
||||
## Links
|
||||
|
||||
- [Website](https://discordeno.mod.land)
|
||||
- [Documentation](https://doc.deno.land/https/deno.land/x/discordeno/mod.ts)
|
||||
@@ -73,8 +74,4 @@ unofficial boilerplates:
|
||||
## Contributing
|
||||
|
||||
We appreciate your help! Before contributing, please read the
|
||||
[Contributing Guide](https://github.com/discordeno/discordeno/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
### License
|
||||
|
||||
[License can be found here](https://github.com/discordeno/discordeno/blob/master/LICENSE)
|
||||
[Contributing Guide](https://github.com/discordeno/discordeno/blob/main/.github/CONTRIBUTING.md).
|
||||
|
||||
2
deps.ts
2
deps.ts
@@ -1 +1 @@
|
||||
export { encode } from "https://deno.land/std@0.87.0/encoding/base64.ts";
|
||||
export { encode } from "https://deno.land/std@0.90.0/encoding/base64.ts";
|
||||
|
||||
@@ -54,5 +54,5 @@ resources:
|
||||
- [TypeScript Crash Course by Traversy
|
||||
Media](https://www.youtube.com/watch?v=rAy_3SIqT-E)
|
||||
|
||||
There is always more resources... Take your time and don't fret! Come back when
|
||||
There are always more resources... Take your time and don't fret! Come back when
|
||||
you are ready, we can't wait to see what your Discordeno created bot does!
|
||||
|
||||
@@ -34,17 +34,17 @@ To begin customizing, create a file in the structures folder called `member.ts`.
|
||||
The name of the file is not important at all.
|
||||
|
||||
```ts
|
||||
async function createMember() {
|
||||
async function createMemberStruct() {
|
||||
}
|
||||
```
|
||||
|
||||
We start by declaring a function that will be run to create the structure. Once
|
||||
again the name here is not important. The function must take the same arguments
|
||||
that the internal function takes. In this case the createMember function takes 2
|
||||
arguments. `data: MemberCreatePayload, guildID: string`
|
||||
that the internal function takes. In this case the createMemberStruct function
|
||||
takes 2 arguments. `data: MemberCreatePayload, guildID: string`
|
||||
|
||||
```ts
|
||||
async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
||||
}
|
||||
```
|
||||
|
||||
@@ -53,7 +53,7 @@ want. My recommendation is to start by copying the current code from the
|
||||
internal libraries structure.
|
||||
|
||||
```ts
|
||||
async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
||||
const {
|
||||
joined_at: joinedAt,
|
||||
premium_since: premiumSince,
|
||||
@@ -92,7 +92,7 @@ and `guild` properties to the member.
|
||||
```ts
|
||||
import { rawAvatarURL } from "../../deps.ts";
|
||||
|
||||
async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
||||
// Hidden code here to make it easier to see the changes
|
||||
|
||||
const member = {
|
||||
@@ -116,11 +116,11 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
```
|
||||
|
||||
Now we need to use this function and telling Discordeno to override the internal
|
||||
createMember function. To do this, we will modify the internal functions. This
|
||||
is where we reassign the value of the function.
|
||||
createMemberStruct function. To do this, we will modify the internal functions.
|
||||
This is where we reassign the value of the function.
|
||||
|
||||
```ts
|
||||
structures.createMember = createMember;
|
||||
structures.createMemberStruct = createMemberStruct;
|
||||
```
|
||||
|
||||
Awesome. Now, we have one more step to complete which is to declare these new
|
||||
@@ -144,7 +144,7 @@ declare module "../../deps.ts" {
|
||||
The code should look like this right now:
|
||||
|
||||
```ts
|
||||
async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
||||
const {
|
||||
joined_at: joinedAt,
|
||||
premium_since: premiumSince,
|
||||
@@ -184,7 +184,7 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
return member;
|
||||
}
|
||||
|
||||
structures.createMember = createMember;
|
||||
structures.createMemberStruct = createMemberStruct;
|
||||
|
||||
declare module "../../deps.ts" {
|
||||
interface Member {
|
||||
@@ -268,7 +268,7 @@ import {
|
||||
rawAvatarURL,
|
||||
} from "../../deps.ts";
|
||||
|
||||
async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
async function createMemberStruct(data: MemberCreatePayload, guildID: string) {
|
||||
const {
|
||||
id,
|
||||
bot,
|
||||
@@ -291,7 +291,7 @@ async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
};
|
||||
}
|
||||
|
||||
structures.createMember = createMember;
|
||||
structures.createMemberStruct = createMemberStruct;
|
||||
|
||||
declare module "../../deps.ts" {
|
||||
interface Member {
|
||||
@@ -304,15 +304,15 @@ declare module "../../deps.ts" {
|
||||
}
|
||||
```
|
||||
|
||||
You might be seeing an error on `structures.createMember`. This is happening
|
||||
because our new member structures is modifying/removing existing properties that
|
||||
the lib internally said it would have. To solve this, simply just add a
|
||||
ts-ignore above it as you know better than TS that the typings are being
|
||||
overwritten.
|
||||
You might be seeing an error on `structures.createMemberStruct`. This is
|
||||
happening because our new member structures is modifying/removing existing
|
||||
properties that the lib internally said it would have. To solve this, simply
|
||||
just add a ts-ignore above it as you know better than TS that the typings are
|
||||
being overwritten.
|
||||
|
||||
```ts
|
||||
// @ts-ignore
|
||||
structures.createMember = createMember;
|
||||
structures.createMemberStruct = createMemberStruct;
|
||||
```
|
||||
|
||||
## Custom Cache
|
||||
@@ -347,10 +347,10 @@ methods on the cacheHandlers. The current list of methods available are:
|
||||
- set
|
||||
- forEach
|
||||
|
||||
## Custom Gateway Payload Handling (Controllers)
|
||||
## Custom Gateway Payload Handling (Handlers)
|
||||
|
||||
Controllers are one of the most powerful features of Discordeno. They allow you
|
||||
to take control of how Discordeno handles the Discord payloads from the gateway.
|
||||
Handlers are one of the most powerful features of Discordeno. They allow you to
|
||||
take control of how Discordeno handles the Discord payloads from the gateway.
|
||||
When an event comes in, you can override and control how you want it to work.
|
||||
For example, if your bot does not use emojis at all, you could simply just take
|
||||
control over the GUILD_EMOJIS_UPDATE event and prevent anyone from caching any
|
||||
@@ -362,14 +362,10 @@ Someone once asked if it was possible to make Discordeno, show the number of
|
||||
users currently typing in the server. He had managed to build this himself in
|
||||
his bot, but he wanted to do it inside the library itself. In order to keep
|
||||
Discordeno minimalistic and memory efficient I avoided adding this. So let's see
|
||||
how we could achieve this same thing with Controllers.
|
||||
how we could achieve this same thing:
|
||||
|
||||
```ts
|
||||
import {
|
||||
controllers,
|
||||
eventHandlers,
|
||||
TypingStartPayload,
|
||||
} from "../../../deps.ts";
|
||||
import { eventHandlers, handlers, TypingStartPayload } from "../../../deps.ts";
|
||||
|
||||
const typingUsers = new Map<String, number>();
|
||||
|
||||
@@ -379,9 +375,7 @@ function createTimeout(userID: String) {
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
controllers.TYPING_START = function (data) {
|
||||
if (data.t !== "TYPING_START") return;
|
||||
|
||||
handlers.TYPING_START = function (data) {
|
||||
const payload = data.d as TypingStartPayload;
|
||||
eventHandlers.typingStart?.(payload);
|
||||
|
||||
@@ -393,18 +387,16 @@ controllers.TYPING_START = function (data) {
|
||||
};
|
||||
```
|
||||
|
||||
Controllers are amazing in so many ways. This is just a basic example but it's
|
||||
true potential is only limited by your imagination. I would love to see what you
|
||||
all can create with controllers.
|
||||
This is just a basic example but it's true potential is only limited by your
|
||||
imagination. I would love to see what you all can create.
|
||||
|
||||
Something worth noting about why Discordeno controllers are so amazing is that
|
||||
it allows you to never depend on me. When Discord releases something new, you
|
||||
don't need to wait for me to update the library to access it. Without
|
||||
controllers, if you wanted access to a feature you would need to wait for the
|
||||
library to be updated or have to fork it, modify it and modify your code for it.
|
||||
Then when the library does get updated, you need to switch back to it and modify
|
||||
your code again possibly to how the lib designed it. With controllers, you never
|
||||
have to fork or anything. Just take control!
|
||||
Something worth noting about why Discordeno handlers are so amazing is that it
|
||||
allows you to never depend on me. When Discord releases something new, you don't
|
||||
need to wait for me to update the library to access it. Without handlers, if you
|
||||
wanted access to a feature you would need to wait for the library to be updated
|
||||
or have to fork it, modify it and modify your code for it. Then when the library
|
||||
does get updated, you need to switch back to it and modify your code again
|
||||
possibly to how the lib designed it. With handlers, you never have to fork or
|
||||
anything. Just take control!
|
||||
|
||||
Controllers are extremely powerful. **Remember with great power comes great
|
||||
bugs!**
|
||||
Remember with great power comes great bugs!
|
||||
|
||||
@@ -21,13 +21,14 @@ of it's code! A breaking change in typings is a breaking change for the library!
|
||||
|
||||
## How Stable Is Discordeno?
|
||||
|
||||
One of the biggest issues with almost every library(I have used) is stability.
|
||||
None of the libraries gave much love and attention to TypeScript developers the
|
||||
way it deserves. Sometimes TypeScript projects would break because breaking
|
||||
changes to typings did not make a MAJOR bump so TypeScript bots in production
|
||||
would break. Sometimes I was personally maintaing the typings because no one
|
||||
else was for that lib. Some libs were pre 1.0 and didn't even have a stable
|
||||
branch/version where I would not have to worry about breaking changes.
|
||||
One of the biggest issues with almost every library (that I have used) is
|
||||
stability. None of the libraries gave much love and attention to TypeScript
|
||||
developers the way it deserves. Sometimes TypeScript projects would break
|
||||
because breaking changes to typings did not make a MAJOR bump so TypeScript bots
|
||||
in production would break. Sometimes I was personally maintaining the typings
|
||||
because no one else was for that lib. Some libs were pre 1.0 and didn't even
|
||||
have a stable branch/version where I would not have to worry about breaking
|
||||
changes.
|
||||
|
||||
This is why I made it one of my foundational goals of this library to have the
|
||||
best stability for TypeScript developers. No matter how small, a breaking change
|
||||
@@ -39,7 +40,7 @@ experience for end users.
|
||||
## Why Doesn't Discordeno Use Classes or EventEmitter?
|
||||
|
||||
This is a design decision for the lib itself. You can still use class if you
|
||||
want on your bot. In fact, I hope someone makes a framework/boilerplate for this
|
||||
want on your bot. In fact, I hope someone makes a framework/templates for this
|
||||
lib one day using classes so that devs have a choice on which style they prefer.
|
||||
Without trying to write an entire thesis statement on the reasons why I avoided
|
||||
Classes in this lib, I will just link to the best resources I believe help
|
||||
|
||||
@@ -42,7 +42,7 @@ import { startBot } from "https://deno.land/x/discordeno@10.0.0/mod.ts";
|
||||
## Example Usage
|
||||
|
||||
Starting with Discordeno is very simple, you can start from scratch without any
|
||||
boilerplates/frameworks: Add this snippet of code into a new TypeScript file:
|
||||
templates/frameworks: Add this snippet of code into a new TypeScript file:
|
||||
|
||||
```ts
|
||||
import { startBot } from "https://deno.land/x/discordeno/mod.ts";
|
||||
|
||||
@@ -22,7 +22,7 @@ For the purposes of this guide, I will be using the current
|
||||
## Preparations
|
||||
|
||||
- First, create a Discordeno Bot using the
|
||||
[Generator Boilerplate](https://github.com/discordeno/boilerplate) I will name
|
||||
[Generator Template](https://github.com/discordeno/template) I will name
|
||||
it Zodiac.
|
||||
|
||||
- Then `git clone https://github.com/Skillz4Killz/Zodiac.git`
|
||||
@@ -329,7 +329,7 @@ createCommand({
|
||||
|
||||
Awesome, that is a full command converted from Discord.JS to Discordeno. See how
|
||||
easy it is! Let's convert one more command to see how to really take full
|
||||
advantage of Discordeno boilerplate and have something amazing.
|
||||
advantage of Discordeno template and have something amazing.
|
||||
|
||||
Discord.JS Kick Command Version
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ you go through this guide it will make a lot more sense.
|
||||
> If you don't have these yet please prepare them first before going forward.
|
||||
|
||||
- First, create a Discordeno Bot using the
|
||||
[Generator Boilerplate](https://github.com/discordeno/boilerplate/generate).
|
||||
[Generator Template](https://github.com/discordeno/template/generate).
|
||||
Give it any name you like. For the purpose of this guide we will call it,
|
||||
Stargate.
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ translations work properly now.
|
||||
|
||||
```json
|
||||
{
|
||||
"DESCRIPTION": "Hug yourself or another user."
|
||||
"DESCRIPTION": "Hug yourself or another user."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -58,9 +58,9 @@ add those in.
|
||||
|
||||
```json
|
||||
{
|
||||
"DESCRIPTION": "Hug yourself or another user.",
|
||||
"SELF": "If you had no one to hug you could have hugged me. Years from now, when you're thinking about me, you're gonna say: 'How did I ever get along without that wonderful, constant companion?' *Woof.*",
|
||||
"OTHER": "{{user}} was hugged by {{mention}}"
|
||||
"DESCRIPTION": "Hug yourself or another user.",
|
||||
"SELF": "If you had no one to hug you could have hugged me. Years from now, when you're thinking about me, you're gonna say: 'How did I ever get along without that wonderful, constant companion?' *Woof.*",
|
||||
"OTHER": "{{user}} was hugged by {{mention}}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -127,9 +127,9 @@ example of different languages.
|
||||
|
||||
```json
|
||||
{
|
||||
"DESCRIPTION": "Abrázate a ti mismo oa otro usuario",
|
||||
"SELF": "Si no tuvieras a nadie a quien abrazar, podrías haberme abrazado. Años a partir de ahora, cuando estés pensando en mí, dirás: '¿Cómo me las arreglé sin esa maravillosa y constante compañera?' *Guau.*",
|
||||
"OTHER": "{{user}} fue abrazado por {{mention}}"
|
||||
"DESCRIPTION": "Abrázate a ti mismo oa otro usuario",
|
||||
"SELF": "Si no tuvieras a nadie a quien abrazar, podrías haberme abrazado. Años a partir de ahora, cuando estés pensando en mí, dirás: '¿Cómo me las arreglé sin esa maravillosa y constante compañera?' *Guau.*",
|
||||
"OTHER": "{{user}} fue abrazado por {{mention}}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
33
mod.ts
33
mod.ts
@@ -1,30 +1,17 @@
|
||||
export * from "./src/api/controllers/bans.ts";
|
||||
export * from "./src/api/controllers/cache.ts";
|
||||
export * from "./src/api/controllers/channels.ts";
|
||||
export * from "./src/api/controllers/guilds.ts";
|
||||
export * from "./src/api/controllers/members.ts";
|
||||
export * from "./src/api/controllers/messages.ts";
|
||||
export * from "./src/api/controllers/misc.ts";
|
||||
export * from "./src/api/controllers/mod.ts";
|
||||
export * from "./src/api/controllers/reactions.ts";
|
||||
export * from "./src/api/controllers/roles.ts";
|
||||
export * from "./src/api/handlers/channel.ts";
|
||||
export * from "./src/api/handlers/guild.ts";
|
||||
export * from "./src/api/handlers/member.ts";
|
||||
export * from "./src/api/handlers/message.ts";
|
||||
export * from "./src/api/handlers/oauth.ts";
|
||||
export * from "./src/api/handlers/webhook.ts";
|
||||
export * from "./src/api/structures/channel.ts";
|
||||
export * from "./src/api/structures/guild.ts";
|
||||
export * from "./src/api/structures/member.ts";
|
||||
export * from "./src/api/structures/message.ts";
|
||||
export * from "./src/api/structures/mod.ts";
|
||||
export * from "./src/api/structures/role.ts";
|
||||
export * from "./src/bot.ts";
|
||||
export * from "./src/cache.ts";
|
||||
export * from "./src/handlers/mod.ts";
|
||||
export * from "./src/helpers/mod.ts";
|
||||
export * from "./src/rest/mod.ts";
|
||||
export * from "./src/structures/channel.ts";
|
||||
export * from "./src/structures/guild.ts";
|
||||
export * from "./src/structures/member.ts";
|
||||
export * from "./src/structures/mod.ts";
|
||||
export * from "./src/structures/role.ts";
|
||||
export * from "./src/structures/template.ts";
|
||||
export * from "./src/types/mod.ts";
|
||||
export * from "./src/util/cache.ts";
|
||||
export * from "./src/util/collection.ts";
|
||||
export * from "./src/util/constants.ts";
|
||||
export * from "./src/util/permissions.ts";
|
||||
export * from "./src/util/utils.ts";
|
||||
export * from "./src/util/random.ts";
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildBanPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalGuildBanAdd(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_BAN_ADD") return;
|
||||
|
||||
const payload = data.d as GuildBanPayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildBanAdd?.(guild, payload.user, member);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildBanRemove(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_BAN_REMOVE") return;
|
||||
|
||||
const payload = data.d as GuildBanPayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildBanRemove?.(guild, payload.user, member);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
ChannelCreatePayload,
|
||||
ChannelTypes,
|
||||
DiscordPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalChannelCreate(data: DiscordPayload) {
|
||||
if (data.t !== "CHANNEL_CREATE") return;
|
||||
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
const channelStruct = await structures.createChannel(payload);
|
||||
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
eventHandlers.channelCreate?.(channelStruct);
|
||||
}
|
||||
|
||||
export async function handleInternalChannelDelete(data: DiscordPayload) {
|
||||
if (data.t !== "CHANNEL_DELETE") return;
|
||||
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
|
||||
const cachedChannel = await cacheHandlers.get("channels", payload.id);
|
||||
if (!cachedChannel) return;
|
||||
|
||||
if (cachedChannel.type === ChannelTypes.GUILD_VOICE && payload.guild_id) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
|
||||
if (guild) {
|
||||
return Promise.all(guild.voiceStates.map(async (vs, key) => {
|
||||
if (vs.channelID !== payload.id) return;
|
||||
|
||||
// Since this channel was deleted all voice states for this channel should be deleted
|
||||
guild.voiceStates.delete(key);
|
||||
|
||||
const member = await cacheHandlers.get("members", vs.userID);
|
||||
if (!member) return;
|
||||
|
||||
eventHandlers.voiceChannelLeave?.(member, vs.channelID);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
await cacheHandlers.delete("channels", payload.id);
|
||||
cacheHandlers.forEach("messages", (message) => {
|
||||
if (message.channelID === payload.id) {
|
||||
cacheHandlers.delete("messages", message.id);
|
||||
}
|
||||
});
|
||||
eventHandlers.channelDelete?.(cachedChannel);
|
||||
}
|
||||
|
||||
export async function handleInternalChannelUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "CHANNEL_UPDATE") return;
|
||||
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
const cachedChannel = await cacheHandlers.get("channels", payload.id);
|
||||
const channelStruct = await structures.createChannel(payload);
|
||||
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
if (!cachedChannel) return;
|
||||
|
||||
eventHandlers.channelUpdate?.(channelStruct, cachedChannel);
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
CreateGuildPayload,
|
||||
DiscordPayload,
|
||||
GuildDeletePayload,
|
||||
GuildEmojisUpdatePayload,
|
||||
GuildUpdateChange,
|
||||
UpdateGuildPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalGuildCreate(
|
||||
data: DiscordPayload,
|
||||
shardID: number,
|
||||
) {
|
||||
if (data.t !== "GUILD_CREATE") return;
|
||||
|
||||
const payload = data.d as CreateGuildPayload;
|
||||
// When shards resume they emit GUILD_CREATE again.
|
||||
if (await cacheHandlers.has("guilds", payload.id)) return;
|
||||
|
||||
const guildStruct = await structures.createGuild(
|
||||
data.d as CreateGuildPayload,
|
||||
shardID,
|
||||
);
|
||||
|
||||
await cacheHandlers.set("guilds", guildStruct.id, guildStruct);
|
||||
|
||||
if (await cacheHandlers.has("unavailableGuilds", payload.id)) {
|
||||
await cacheHandlers.delete("unavailableGuilds", payload.id);
|
||||
}
|
||||
|
||||
if (!cache.isReady) return eventHandlers.guildLoaded?.(guildStruct);
|
||||
eventHandlers.guildCreate?.(guildStruct);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildDelete(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_DELETE") return;
|
||||
|
||||
const payload = data.d as GuildDeletePayload;
|
||||
cacheHandlers.forEach("messages", (message) => {
|
||||
if (message.guildID === payload.id) {
|
||||
cacheHandlers.delete("messages", message.id);
|
||||
}
|
||||
});
|
||||
|
||||
cacheHandlers.forEach("channels", (channel) => {
|
||||
if (channel.guildID === payload.id) {
|
||||
cacheHandlers.delete("channels", channel.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (payload.unavailable) {
|
||||
return cacheHandlers.set("unavailableGuilds", payload.id, Date.now());
|
||||
}
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.id);
|
||||
if (!guild) return;
|
||||
|
||||
await cacheHandlers.delete("guilds", payload.id);
|
||||
|
||||
eventHandlers.guildDelete?.(guild);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_UPDATE") return;
|
||||
|
||||
const payload = data.d as UpdateGuildPayload;
|
||||
const cachedGuild = await cacheHandlers.get("guilds", payload.id);
|
||||
if (!cachedGuild) return;
|
||||
|
||||
const keysToSkip = [
|
||||
"roles",
|
||||
"guild_hashes",
|
||||
"guild_id",
|
||||
"max_members",
|
||||
"emojis",
|
||||
];
|
||||
|
||||
const changes = Object.entries(payload)
|
||||
.map(([key, value]) => {
|
||||
if (keysToSkip.includes(key)) return;
|
||||
|
||||
// @ts-ignore index signature
|
||||
const cachedValue = cachedGuild[key];
|
||||
if (cachedValue !== value) {
|
||||
// Guild create sends undefined and update sends false.
|
||||
if (!cachedValue && !value) return;
|
||||
|
||||
if (Array.isArray(cachedValue) && Array.isArray(value)) {
|
||||
const different = (cachedValue.length !== value.length) ||
|
||||
cachedValue.find((val) => !value.includes(val)) ||
|
||||
value.find((val) => !cachedValue.includes(val));
|
||||
if (!different) return;
|
||||
}
|
||||
|
||||
// @ts-ignore index signature
|
||||
cachedGuild[key] = value;
|
||||
return { key, oldValue: cachedValue, value };
|
||||
}
|
||||
}).filter((change) => change) as GuildUpdateChange[];
|
||||
|
||||
await cacheHandlers.set("guilds", payload.id, cachedGuild);
|
||||
|
||||
eventHandlers.guildUpdate?.(cachedGuild, changes);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildEmojisUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_EMOJIS_UPDATE") return;
|
||||
|
||||
const payload = data.d as GuildEmojisUpdatePayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedEmojis = guild.emojis;
|
||||
guild.emojis = payload.emojis;
|
||||
|
||||
cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
eventHandlers.guildEmojisUpdate?.(
|
||||
guild,
|
||||
payload.emojis,
|
||||
cachedEmojis,
|
||||
);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
ApplicationCommandEvent,
|
||||
DiscordPayload,
|
||||
InteractionCommandPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalInteractionCreate(data: DiscordPayload) {
|
||||
if (data.t !== "INTERACTION_CREATE") return;
|
||||
|
||||
const payload = data.d as InteractionCommandPayload;
|
||||
const memberStruct = await structures.createMember(
|
||||
payload.member,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
eventHandlers.interactionCreate?.(
|
||||
{
|
||||
...payload,
|
||||
member: memberStruct,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function handleInternalApplicationCommandCreate(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
if (data.t !== "APPLICATION_COMMAND_CREATE") return;
|
||||
|
||||
const {
|
||||
guild_id: guildID,
|
||||
application_id: applicationID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
eventHandlers.applicationCommandCreate?.({
|
||||
...rest,
|
||||
guildID,
|
||||
applicationID,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalApplicationCommandUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "APPLICATION_COMMAND_UPDATE") return;
|
||||
|
||||
const {
|
||||
application_id: applicationID,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
eventHandlers.applicationCommandUpdate?.({
|
||||
...rest,
|
||||
guildID,
|
||||
applicationID,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalApplicationCommandDelete(data: DiscordPayload) {
|
||||
if (data.t !== "APPLICATION_COMMAND_DELETE") return;
|
||||
|
||||
const {
|
||||
application_id: applicationID,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
eventHandlers.applicationCommandDelete?.({
|
||||
...rest,
|
||||
guildID,
|
||||
applicationID,
|
||||
});
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
GuildBanPayload,
|
||||
GuildMemberAddPayload,
|
||||
GuildMemberChunkPayload,
|
||||
GuildMemberUpdatePayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalGuildMemberAdd(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_MEMBER_ADD") return;
|
||||
|
||||
const payload = data.d as GuildMemberAddPayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
guild.memberCount++;
|
||||
const memberStruct = await structures.createMember(
|
||||
payload,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
eventHandlers.guildMemberAdd?.(guild, memberStruct);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildMemberRemove(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_MEMBER_REMOVE") return;
|
||||
|
||||
const payload = data.d as GuildBanPayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
guild.memberCount--;
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildMemberRemove?.(guild, payload.user, member);
|
||||
|
||||
member?.guilds.delete(guild.id);
|
||||
if (member && !member.guilds.size) {
|
||||
await cacheHandlers.delete("members", member.id);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleInternalGuildMemberUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_MEMBER_UPDATE") return;
|
||||
|
||||
const payload = data.d as GuildMemberUpdatePayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedMember = await cacheHandlers.get("members", payload.user.id);
|
||||
const guildMember = cachedMember?.guilds.get(payload.guild_id);
|
||||
|
||||
const newMemberData = {
|
||||
...payload,
|
||||
// deno-lint-ignore camelcase
|
||||
premium_since: payload.premium_since || undefined,
|
||||
// deno-lint-ignore camelcase
|
||||
joined_at: new Date(guildMember?.joinedAt || Date.now())
|
||||
.toISOString(),
|
||||
deaf: guildMember?.deaf || false,
|
||||
mute: guildMember?.mute || false,
|
||||
roles: payload.roles,
|
||||
};
|
||||
const memberStruct = await structures.createMember(
|
||||
newMemberData,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
if (guildMember?.nick !== payload.nick) {
|
||||
eventHandlers.nicknameUpdate?.(
|
||||
guild,
|
||||
memberStruct,
|
||||
payload.nick,
|
||||
guildMember?.nick,
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.pending === false && guildMember?.pending === true) {
|
||||
eventHandlers.membershipScreeningPassed?.(guild, memberStruct);
|
||||
}
|
||||
|
||||
const roleIDs = guildMember?.roles || [];
|
||||
|
||||
roleIDs.forEach((id) => {
|
||||
if (!payload.roles.includes(id)) {
|
||||
eventHandlers.roleLost?.(guild, memberStruct, id);
|
||||
}
|
||||
});
|
||||
|
||||
payload.roles.forEach((id) => {
|
||||
if (!roleIDs.includes(id)) {
|
||||
eventHandlers.roleGained?.(guild, memberStruct, id);
|
||||
}
|
||||
});
|
||||
|
||||
eventHandlers.guildMemberUpdate?.(guild, memberStruct, cachedMember);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildMembersChunk(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_MEMBERS_CHUNK") return;
|
||||
|
||||
const payload = data.d as GuildMemberChunkPayload;
|
||||
|
||||
const members = await Promise.all(
|
||||
payload.members.map(async (member) => {
|
||||
const memberStruct = await structures.createMember(
|
||||
member,
|
||||
payload.guild_id,
|
||||
);
|
||||
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
return memberStruct;
|
||||
}),
|
||||
);
|
||||
|
||||
// Check if its necessary to resolve the fetchmembers promise for this chunk or if more chunks will be coming
|
||||
if (
|
||||
payload.nonce
|
||||
) {
|
||||
const resolve = cache.fetchAllMembersProcessingRequests.get(payload.nonce);
|
||||
if (!resolve) return;
|
||||
|
||||
if (payload.chunk_index + 1 === payload.chunk_count) {
|
||||
cache.fetchAllMembersProcessingRequests.delete(payload.nonce);
|
||||
// Only 1 chunk most likely is all members or users only request a small amount of users
|
||||
if (payload.chunk_count === 1) {
|
||||
return resolve(new Collection(members.map((m) => [m.id, m])));
|
||||
}
|
||||
|
||||
return resolve(
|
||||
await cacheHandlers.filter(
|
||||
"members",
|
||||
(m) => m.guilds.has(payload.guild_id),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
MessageCreateOptions,
|
||||
MessageDeleteBulkPayload,
|
||||
MessageDeletePayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalMessageCreate(data: DiscordPayload) {
|
||||
if (data.t !== "MESSAGE_CREATE") return;
|
||||
|
||||
const payload = data.d as MessageCreateOptions;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (channel) channel.lastMessageID = payload.id;
|
||||
|
||||
const guild = payload.guild_id
|
||||
? await cacheHandlers.get("guilds", payload.guild_id)
|
||||
: undefined;
|
||||
|
||||
if (payload.member && guild) {
|
||||
// If in a guild cache the author as a member
|
||||
const memberStruct = await structures.createMember(
|
||||
{ ...payload.member, user: payload.author },
|
||||
guild.id,
|
||||
);
|
||||
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
|
||||
await Promise.all(payload.mentions.map(async (mention) => {
|
||||
// Cache the member if its a valid member
|
||||
if (mention.member && guild) {
|
||||
const memberStruct = await structures.createMember(
|
||||
{ ...mention.member, user: mention },
|
||||
guild.id,
|
||||
);
|
||||
|
||||
return cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}));
|
||||
|
||||
const message = await structures.createMessage(payload);
|
||||
// Cache the message
|
||||
await cacheHandlers.set("messages", payload.id, message);
|
||||
|
||||
eventHandlers.messageCreate?.(message);
|
||||
}
|
||||
|
||||
export async function handleInternalMessageDelete(data: DiscordPayload) {
|
||||
if (data.t !== "MESSAGE_DELETE") return;
|
||||
|
||||
const payload = data.d as MessageDeletePayload;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
eventHandlers.messageDelete?.(
|
||||
{ id: payload.id, channel },
|
||||
await cacheHandlers.get("messages", payload.id),
|
||||
);
|
||||
|
||||
await cacheHandlers.delete("messages", payload.id);
|
||||
}
|
||||
|
||||
export async function handleInternalMessageDeleteBulk(data: DiscordPayload) {
|
||||
if (data.t !== "MESSAGE_DELETE_BULK") return;
|
||||
|
||||
const payload = data.d as MessageDeleteBulkPayload;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
return Promise.all(payload.ids.map(async (id) => {
|
||||
eventHandlers.messageDelete?.(
|
||||
{ id, channel },
|
||||
await cacheHandlers.get("messages", id),
|
||||
);
|
||||
await cacheHandlers.delete("messages", id);
|
||||
}));
|
||||
}
|
||||
|
||||
export async function handleInternalMessageUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "MESSAGE_UPDATE") return;
|
||||
|
||||
const payload = data.d as MessageCreateOptions;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
const cachedMessage = await cacheHandlers.get("messages", payload.id);
|
||||
if (!cachedMessage) return;
|
||||
|
||||
const oldMessage = {
|
||||
attachments: cachedMessage.attachments,
|
||||
content: cachedMessage.content,
|
||||
embeds: cachedMessage.embeds,
|
||||
editedTimestamp: cachedMessage.editedTimestamp,
|
||||
tts: cachedMessage.tts,
|
||||
pinned: cachedMessage.pinned,
|
||||
};
|
||||
|
||||
// Messages with embeds can trigger update but they wont have edited_timestamp
|
||||
if (
|
||||
!payload.edited_timestamp ||
|
||||
(cachedMessage.content === payload.content)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await structures.createMessage(payload);
|
||||
|
||||
await cacheHandlers.set("messages", payload.id, message);
|
||||
|
||||
eventHandlers.messageUpdate?.(message, oldMessage);
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
import { eventHandlers, setApplicationID, setBotID } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
IntegrationCreateUpdateEvent,
|
||||
IntegrationDeleteEvent,
|
||||
InviteCreateEvent,
|
||||
InviteDeleteEvent,
|
||||
PresenceUpdatePayload,
|
||||
ReadyPayload,
|
||||
TypingStartPayload,
|
||||
UserPayload,
|
||||
VoiceStateUpdatePayload,
|
||||
WebhookUpdatePayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { delay } from "../../util/utils.ts";
|
||||
import { allowNextShard } from "../../ws/shard_manager.ts";
|
||||
import { initialMemberLoadQueue } from "../structures/guild.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
/** This function is the internal handler for the ready event. Users can override this with controllers if desired. */
|
||||
export async function handleInternalReady(
|
||||
data: DiscordPayload,
|
||||
shardID: number,
|
||||
) {
|
||||
if (data.t !== "READY") return;
|
||||
|
||||
const payload = data.d as ReadyPayload;
|
||||
setBotID(payload.user.id);
|
||||
setApplicationID(payload.application.id);
|
||||
|
||||
// Triggered on each shard
|
||||
eventHandlers.shardReady?.(shardID);
|
||||
if (payload.shard && shardID === payload.shard[1] - 1) {
|
||||
const loadedAllGuilds = async () => {
|
||||
const guildsMissing = async () => {
|
||||
for (const g of payload.guilds) {
|
||||
if (!(await cacheHandlers.has("guilds", g.id))) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (await guildsMissing()) {
|
||||
setTimeout(loadedAllGuilds, 2000);
|
||||
} else {
|
||||
// The bot has already started, the last shard is resumed, however.
|
||||
if (cache.isReady) return;
|
||||
|
||||
cache.isReady = true;
|
||||
eventHandlers.ready?.();
|
||||
|
||||
// All the members that came in on guild creates should now be processed 1 by 1
|
||||
for (const [guildID, members] of initialMemberLoadQueue.entries()) {
|
||||
await Promise.all(
|
||||
members.map(async (member) => {
|
||||
const memberStruct = await structures.createMember(
|
||||
member,
|
||||
guildID,
|
||||
);
|
||||
|
||||
return cacheHandlers.set(
|
||||
"members",
|
||||
memberStruct.id,
|
||||
memberStruct,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(loadedAllGuilds, 2000);
|
||||
}
|
||||
|
||||
// Wait 5 seconds to spawn next shard
|
||||
await delay(5000);
|
||||
allowNextShard();
|
||||
}
|
||||
|
||||
/** This function is the internal handler for the presence update event. Users can override this with controllers if desired. */
|
||||
export async function handleInternalPresenceUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "PRESENCE_UPDATE") return;
|
||||
|
||||
const payload = data.d as PresenceUpdatePayload;
|
||||
const oldPresence = await cacheHandlers.get("presences", payload.user.id);
|
||||
await cacheHandlers.set("presences", payload.user.id, payload);
|
||||
|
||||
eventHandlers.presenceUpdate?.(payload, oldPresence);
|
||||
}
|
||||
|
||||
/** This function is the internal handler for the typings event. Users can override this with controllers if desired. */
|
||||
export function handleInternalTypingStart(data: DiscordPayload) {
|
||||
if (data.t !== "TYPING_START") return;
|
||||
eventHandlers.typingStart?.(data.d as TypingStartPayload);
|
||||
}
|
||||
|
||||
/** This function is the internal handler for the user update event. Users can override this with controllers if desired. */
|
||||
export async function handleInternalUserUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "USER_UPDATE") return;
|
||||
|
||||
const userData = data.d as UserPayload;
|
||||
|
||||
const member = await cacheHandlers.get("members", userData.id);
|
||||
if (!member) return;
|
||||
|
||||
Object.entries(userData).forEach(([key, value]) => {
|
||||
// @ts-ignore index signatures
|
||||
if (member[key] !== value) return member[key] = value;
|
||||
});
|
||||
|
||||
await cacheHandlers.set("members", userData.id, member);
|
||||
|
||||
eventHandlers.botUpdate?.(userData);
|
||||
}
|
||||
|
||||
/** This function is the internal handler for the voice state update event. Users can override this with controllers if desired. */
|
||||
export async function handleInternalVoiceStateUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "VOICE_STATE_UPDATE") return;
|
||||
|
||||
const payload = data.d as VoiceStateUpdatePayload;
|
||||
if (!payload.guild_id) return;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = payload.member
|
||||
? await structures.createMember(payload.member, guild.id)
|
||||
: await cacheHandlers.get("members", payload.user_id);
|
||||
if (!member) return;
|
||||
|
||||
// No cached state before so lets make one for em
|
||||
const cachedState = guild.voiceStates.get(payload.user_id);
|
||||
|
||||
guild.voiceStates.set(payload.user_id, {
|
||||
...payload,
|
||||
guildID: payload.guild_id,
|
||||
channelID: payload.channel_id || "",
|
||||
userID: payload.user_id,
|
||||
sessionID: payload.session_id,
|
||||
selfDeaf: payload.self_deaf,
|
||||
selfMute: payload.self_mute,
|
||||
selfStream: payload.self_stream || false,
|
||||
});
|
||||
|
||||
await cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
if (cachedState?.channelID !== payload.channel_id) {
|
||||
// Either joined or moved channels
|
||||
if (payload.channel_id) {
|
||||
if (cachedState?.channelID) { // Was in a channel before
|
||||
eventHandlers.voiceChannelSwitch?.(
|
||||
member,
|
||||
payload.channel_id,
|
||||
cachedState.channelID,
|
||||
);
|
||||
} else { // Was not in a channel before so user just joined
|
||||
eventHandlers.voiceChannelJoin?.(member, payload.channel_id);
|
||||
}
|
||||
} // Left the channel
|
||||
else if (cachedState?.channelID) {
|
||||
guild.voiceStates.delete(payload.user_id);
|
||||
eventHandlers.voiceChannelLeave?.(member, cachedState.channelID);
|
||||
}
|
||||
}
|
||||
|
||||
eventHandlers.voiceStateUpdate?.(member, payload);
|
||||
}
|
||||
|
||||
/** This function is the internal handler for the webhooks update event. Users can override this with controllers if desired. */
|
||||
export function handleInternalWebhooksUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "WEBHOOKS_UPDATE") return;
|
||||
|
||||
const options = data.d as WebhookUpdatePayload;
|
||||
eventHandlers.webhooksUpdate?.(
|
||||
options.channel_id,
|
||||
options.guild_id,
|
||||
);
|
||||
}
|
||||
|
||||
export function handleInternalIntegrationCreate(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
if (data.t !== "INTEGRATION_CREATE") return;
|
||||
|
||||
const {
|
||||
guild_id: guildID,
|
||||
enable_emoticons: enableEmoticons,
|
||||
expire_behavior: expireBehavior,
|
||||
expire_grace_period: expireGracePeriod,
|
||||
subscriber_count: subscriberCount,
|
||||
role_id: roleID,
|
||||
synced_at: syncedAt,
|
||||
...rest
|
||||
} = data.d as IntegrationCreateUpdateEvent;
|
||||
|
||||
eventHandlers.integrationCreate?.({
|
||||
...rest,
|
||||
guildID,
|
||||
enableEmoticons,
|
||||
expireBehavior,
|
||||
expireGracePeriod,
|
||||
syncedAt,
|
||||
subscriberCount,
|
||||
roleID,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalIntegrationUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "INTEGRATION_UPDATE") return;
|
||||
|
||||
const {
|
||||
enable_emoticons: enableEmoticons,
|
||||
expire_behavior: expireBehavior,
|
||||
expire_grace_period: expireGracePeriod,
|
||||
role_id: roleID,
|
||||
subscriber_count: subscriberCount,
|
||||
synced_at: syncedAt,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = data.d as IntegrationCreateUpdateEvent;
|
||||
|
||||
eventHandlers.integrationUpdate?.({
|
||||
...rest,
|
||||
guildID,
|
||||
subscriberCount,
|
||||
enableEmoticons,
|
||||
expireGracePeriod,
|
||||
roleID,
|
||||
expireBehavior,
|
||||
syncedAt,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalIntegrationDelete(data: DiscordPayload) {
|
||||
if (data.t !== "INTEGRATION_DELETE") return;
|
||||
|
||||
const {
|
||||
guild_id: guildID,
|
||||
application_id: applicationID,
|
||||
...rest
|
||||
} = data.d as IntegrationDeleteEvent;
|
||||
|
||||
eventHandlers.integrationDelete?.({
|
||||
...rest,
|
||||
applicationID,
|
||||
guildID,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalInviteCreate(payload: DiscordPayload) {
|
||||
if (payload.t !== "INVITE_CREATE") return;
|
||||
|
||||
const {
|
||||
channel_id: channelID,
|
||||
created_at: createdAt,
|
||||
max_age: maxAge,
|
||||
guild_id: guildID,
|
||||
target_user: targetUser,
|
||||
target_user_type: targetUserType,
|
||||
max_uses: maxUses,
|
||||
...rest
|
||||
} = payload.d as InviteCreateEvent;
|
||||
|
||||
eventHandlers.inviteCreate?.({
|
||||
...rest,
|
||||
channelID,
|
||||
guildID,
|
||||
maxAge,
|
||||
targetUser,
|
||||
targetUserType,
|
||||
maxUses,
|
||||
createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
export function handleInternalInviteDelete(payload: DiscordPayload) {
|
||||
if (payload.t !== "INVITE_DELETE") return;
|
||||
|
||||
const {
|
||||
channel_id: channelID,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = payload.d as InviteDeleteEvent;
|
||||
|
||||
eventHandlers.inviteDelete?.({
|
||||
...rest,
|
||||
channelID,
|
||||
guildID,
|
||||
});
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import {
|
||||
handleInternalGuildBanAdd,
|
||||
handleInternalGuildBanRemove,
|
||||
} from "./bans.ts";
|
||||
import {
|
||||
handleInternalChannelCreate,
|
||||
handleInternalChannelDelete,
|
||||
handleInternalChannelUpdate,
|
||||
} from "./channels.ts";
|
||||
import {
|
||||
handleInternalGuildCreate,
|
||||
handleInternalGuildDelete,
|
||||
handleInternalGuildEmojisUpdate,
|
||||
handleInternalGuildUpdate,
|
||||
} from "./guilds.ts";
|
||||
import {
|
||||
handleInternalApplicationCommandCreate,
|
||||
handleInternalApplicationCommandDelete,
|
||||
handleInternalApplicationCommandUpdate,
|
||||
handleInternalInteractionCreate,
|
||||
} from "./interactions.ts";
|
||||
import {
|
||||
handleInternalGuildMemberAdd,
|
||||
handleInternalGuildMemberRemove,
|
||||
handleInternalGuildMembersChunk,
|
||||
handleInternalGuildMemberUpdate,
|
||||
} from "./members.ts";
|
||||
import {
|
||||
handleInternalMessageCreate,
|
||||
handleInternalMessageDelete,
|
||||
handleInternalMessageDeleteBulk,
|
||||
handleInternalMessageUpdate,
|
||||
} from "./messages.ts";
|
||||
import {
|
||||
handleInternalIntegrationCreate,
|
||||
handleInternalIntegrationDelete,
|
||||
handleInternalIntegrationUpdate,
|
||||
handleInternalInviteCreate,
|
||||
handleInternalInviteDelete,
|
||||
handleInternalPresenceUpdate,
|
||||
handleInternalReady,
|
||||
handleInternalTypingStart,
|
||||
handleInternalUserUpdate,
|
||||
handleInternalVoiceStateUpdate,
|
||||
handleInternalWebhooksUpdate,
|
||||
} from "./misc.ts";
|
||||
import {
|
||||
handleInternalMessageReactionAdd,
|
||||
handleInternalMessageReactionRemove,
|
||||
handleInternalMessageReactionRemoveAll,
|
||||
handleInternalMessageReactionRemoveEmoji,
|
||||
} from "./reactions.ts";
|
||||
import {
|
||||
handleInternalGuildRoleCreate,
|
||||
handleInternalGuildRoleDelete,
|
||||
handleInternalGuildRoleUpdate,
|
||||
} from "./roles.ts";
|
||||
|
||||
export let controllers = {
|
||||
READY: handleInternalReady,
|
||||
CHANNEL_CREATE: handleInternalChannelCreate,
|
||||
CHANNEL_DELETE: handleInternalChannelDelete,
|
||||
CHANNEL_UPDATE: handleInternalChannelUpdate,
|
||||
GUILD_CREATE: handleInternalGuildCreate,
|
||||
GUILD_DELETE: handleInternalGuildDelete,
|
||||
GUILD_UPDATE: handleInternalGuildUpdate,
|
||||
GUILD_BAN_ADD: handleInternalGuildBanAdd,
|
||||
GUILD_BAN_REMOVE: handleInternalGuildBanRemove,
|
||||
GUILD_EMOJIS_UPDATE: handleInternalGuildEmojisUpdate,
|
||||
GUILD_MEMBER_ADD: handleInternalGuildMemberAdd,
|
||||
GUILD_MEMBER_REMOVE: handleInternalGuildMemberRemove,
|
||||
GUILD_MEMBER_UPDATE: handleInternalGuildMemberUpdate,
|
||||
GUILD_MEMBERS_CHUNK: handleInternalGuildMembersChunk,
|
||||
GUILD_ROLE_CREATE: handleInternalGuildRoleCreate,
|
||||
GUILD_ROLE_DELETE: handleInternalGuildRoleDelete,
|
||||
GUILD_ROLE_UPDATE: handleInternalGuildRoleUpdate,
|
||||
INTERACTION_CREATE: handleInternalInteractionCreate,
|
||||
APPLICATION_COMMAND_CREATE: handleInternalApplicationCommandCreate,
|
||||
APPLICATION_COMMAND_DELETE: handleInternalApplicationCommandDelete,
|
||||
APPLICATION_COMMAND_UPDATE: handleInternalApplicationCommandUpdate,
|
||||
MESSAGE_CREATE: handleInternalMessageCreate,
|
||||
MESSAGE_DELETE: handleInternalMessageDelete,
|
||||
MESSAGE_DELETE_BULK: handleInternalMessageDeleteBulk,
|
||||
MESSAGE_UPDATE: handleInternalMessageUpdate,
|
||||
MESSAGE_REACTION_ADD: handleInternalMessageReactionAdd,
|
||||
MESSAGE_REACTION_REMOVE: handleInternalMessageReactionRemove,
|
||||
MESSAGE_REACTION_REMOVE_ALL: handleInternalMessageReactionRemoveAll,
|
||||
MESSAGE_REACTION_REMOVE_EMOJI: handleInternalMessageReactionRemoveEmoji,
|
||||
PRESENCE_UPDATE: handleInternalPresenceUpdate,
|
||||
TYPING_START: handleInternalTypingStart,
|
||||
USER_UPDATE: handleInternalUserUpdate,
|
||||
VOICE_STATE_UPDATE: handleInternalVoiceStateUpdate,
|
||||
WEBHOOKS_UPDATE: handleInternalWebhooksUpdate,
|
||||
INTEGRATION_CREATE: handleInternalIntegrationCreate,
|
||||
INTEGRATION_UPDATE: handleInternalIntegrationUpdate,
|
||||
INTEGRATION_DELETE: handleInternalIntegrationDelete,
|
||||
INVITE_CREATE: handleInternalInviteCreate,
|
||||
INVITE_DELETE: handleInternalInviteDelete,
|
||||
};
|
||||
|
||||
export type Controllers = typeof controllers;
|
||||
|
||||
export function updateControllers(newControllers: Controllers) {
|
||||
controllers = {
|
||||
...controllers,
|
||||
...newControllers,
|
||||
};
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import { botID, eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
BaseMessageReactionPayload,
|
||||
DiscordPayload,
|
||||
MessageReactionPayload,
|
||||
MessageReactionRemoveEmojiPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalMessageReactionAdd(data: DiscordPayload) {
|
||||
if (data.t !== "MESSAGE_REACTION_ADD") return;
|
||||
|
||||
const payload = data.d as MessageReactionPayload;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message) {
|
||||
const reactionExisted = message.reactions?.find(
|
||||
(reaction) =>
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name,
|
||||
);
|
||||
|
||||
if (reactionExisted) reactionExisted.count++;
|
||||
else {
|
||||
const newReaction = {
|
||||
count: 1,
|
||||
me: payload.user_id === botID,
|
||||
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
|
||||
};
|
||||
message.reactions = message.reactions
|
||||
? [...message.reactions, newReaction]
|
||||
: [newReaction];
|
||||
}
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
if (payload.member && payload.guild_id) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (guild) {
|
||||
const memberStruct = await structures.createMember(
|
||||
payload.member,
|
||||
guild.id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}
|
||||
|
||||
const uncachedOptions = {
|
||||
...payload,
|
||||
id: payload.message_id,
|
||||
channelID: payload.channel_id,
|
||||
guildID: payload.guild_id || "",
|
||||
};
|
||||
|
||||
eventHandlers.reactionAdd?.(
|
||||
uncachedOptions,
|
||||
payload.emoji,
|
||||
payload.user_id,
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleInternalMessageReactionRemove(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
if (data.t !== "MESSAGE_REACTION_REMOVE") return;
|
||||
|
||||
const payload = data.d as MessageReactionPayload;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message) {
|
||||
const reactionExisted = message.reactions?.find(
|
||||
(reaction) =>
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name,
|
||||
);
|
||||
|
||||
if (reactionExisted) reactionExisted.count--;
|
||||
else {
|
||||
const newReaction = {
|
||||
count: 1,
|
||||
me: payload.user_id === botID,
|
||||
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
|
||||
};
|
||||
message.reactions = message.reactions
|
||||
? [...message.reactions, newReaction]
|
||||
: [newReaction];
|
||||
}
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
if (payload.member && payload.guild_id) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (guild) {
|
||||
const memberStruct = await structures.createMember(
|
||||
payload.member,
|
||||
guild.id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}
|
||||
|
||||
const uncachedOptions = {
|
||||
...payload,
|
||||
id: payload.message_id,
|
||||
channelID: payload.channel_id,
|
||||
guildID: payload.guild_id,
|
||||
};
|
||||
|
||||
eventHandlers.reactionRemove?.(
|
||||
uncachedOptions,
|
||||
payload.emoji,
|
||||
payload.user_id,
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleInternalMessageReactionRemoveAll(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
if (data.t !== "MESSAGE_REACTION_REMOVE_ALL") return;
|
||||
|
||||
const payload = data.d as BaseMessageReactionPayload;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message?.reactions) {
|
||||
message.reactions = undefined;
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
eventHandlers.reactionRemoveAll?.(data.d as BaseMessageReactionPayload);
|
||||
}
|
||||
|
||||
export async function handleInternalMessageReactionRemoveEmoji(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
if (data.t !== "MESSAGE_REACTION_REMOVE_EMOJI") return;
|
||||
|
||||
const payload = data.d as MessageReactionRemoveEmojiPayload;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message?.reactions) {
|
||||
message.reactions = message.reactions?.filter(
|
||||
(reaction) =>
|
||||
!(
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name
|
||||
),
|
||||
);
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
eventHandlers.reactionRemoveEmoji?.(
|
||||
data.d as MessageReactionRemoveEmojiPayload,
|
||||
);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
GuildRoleDeletePayload,
|
||||
GuildRolePayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "./cache.ts";
|
||||
|
||||
export async function handleInternalGuildRoleCreate(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_ROLE_CREATE") return;
|
||||
|
||||
const payload = data.d as GuildRolePayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const role = await structures.createRole(payload.role);
|
||||
guild.roles = guild.roles.set(payload.role.id, role);
|
||||
await cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
eventHandlers.roleCreate?.(guild, role);
|
||||
}
|
||||
|
||||
export async function handleInternalGuildRoleDelete(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_ROLE_DELETE") return;
|
||||
|
||||
const payload = data.d as GuildRoleDeletePayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedRole = guild.roles.get(payload.role_id)!;
|
||||
guild.roles.delete(payload.role_id);
|
||||
|
||||
if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole);
|
||||
|
||||
// For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking.
|
||||
cacheHandlers.forEach("members", (member) => {
|
||||
// Not in the relevant guild so just skip.
|
||||
if (!member.guilds.has(guild.id)) return;
|
||||
|
||||
member.guilds.forEach((g) => {
|
||||
// Member does not have this role
|
||||
if (!g.roles.includes(payload.role_id)) return;
|
||||
// Remove this role from the members cache
|
||||
g.roles = g.roles.filter((id) => id !== payload.role_id);
|
||||
cacheHandlers.set("members", member.id, member);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleInternalGuildRoleUpdate(data: DiscordPayload) {
|
||||
if (data.t !== "GUILD_ROLE_UPDATE") return;
|
||||
|
||||
const payload = data.d as GuildRolePayload;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedRole = guild.roles.get(payload.role.id);
|
||||
if (!cachedRole) return;
|
||||
|
||||
const role = await structures.createRole(payload.role);
|
||||
guild.roles.set(payload.role.id, role);
|
||||
await cacheHandlers.set("guilds", guild.id, guild);
|
||||
|
||||
eventHandlers.roleUpdate?.(guild, role, cachedRole);
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
ChannelEditOptions,
|
||||
ChannelTypes,
|
||||
CreateInviteOptions,
|
||||
Errors,
|
||||
FollowedChannelPayload,
|
||||
GetMessages,
|
||||
GetMessagesAfter,
|
||||
GetMessagesAround,
|
||||
GetMessagesBefore,
|
||||
InvitePayload,
|
||||
MessageContent,
|
||||
MessageCreateOptions,
|
||||
Permission,
|
||||
Permissions,
|
||||
RawOverwrite,
|
||||
WebhookPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
botHasChannelPermissions,
|
||||
botHasPermission,
|
||||
calculateBits,
|
||||
} from "../../util/permissions.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
|
||||
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
|
||||
export function channelOverwriteHasPermission(
|
||||
guildID: string,
|
||||
id: string,
|
||||
overwrites: RawOverwrite[],
|
||||
permissions: Permission[],
|
||||
) {
|
||||
const overwrite = overwrites.find((perm) => perm.id === id) ||
|
||||
overwrites.find((perm) => perm.id === guildID);
|
||||
|
||||
return permissions.every((perm) => {
|
||||
if (overwrite) {
|
||||
const allowBits = overwrite.allow;
|
||||
const denyBits = overwrite.deny;
|
||||
if (BigInt(denyBits) & BigInt(Permissions[perm])) return false;
|
||||
if (BigInt(allowBits) & BigInt(Permissions[perm])) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch a single message from the server. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */
|
||||
export async function getMessage(
|
||||
channelID: string,
|
||||
id: string,
|
||||
) {
|
||||
const hasViewChannelPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["VIEW_CHANNEL"],
|
||||
);
|
||||
if (
|
||||
!hasViewChannelPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_VIEW_CHANNEL);
|
||||
}
|
||||
|
||||
const hasReadMessageHistoryPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["READ_MESSAGE_HISTORY"],
|
||||
);
|
||||
if (
|
||||
!hasReadMessageHistoryPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
|
||||
}
|
||||
|
||||
const result = await RequestManager.get(
|
||||
endpoints.CHANNEL_MESSAGE(channelID, id),
|
||||
) as MessageCreateOptions;
|
||||
|
||||
return structures.createMessage(result);
|
||||
}
|
||||
|
||||
/** Fetches between 2-100 messages. Requires VIEW_CHANNEL and READ_MESSAGE_HISTORY */
|
||||
export async function getMessages(
|
||||
channelID: string,
|
||||
options?:
|
||||
| GetMessagesAfter
|
||||
| GetMessagesBefore
|
||||
| GetMessagesAround
|
||||
| GetMessages,
|
||||
) {
|
||||
const hasViewChannelPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["VIEW_CHANNEL"],
|
||||
);
|
||||
if (
|
||||
!hasViewChannelPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_VIEW_CHANNEL);
|
||||
}
|
||||
|
||||
const hasReadMessageHistoryPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["READ_MESSAGE_HISTORY"],
|
||||
);
|
||||
if (
|
||||
!hasReadMessageHistoryPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
|
||||
}
|
||||
|
||||
if (options?.limit && options.limit > 100) return;
|
||||
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.CHANNEL_MESSAGES(channelID),
|
||||
options,
|
||||
)) as MessageCreateOptions[];
|
||||
|
||||
return Promise.all(result.map((res) => structures.createMessage(res)));
|
||||
}
|
||||
|
||||
/** Get pinned messages in this channel. */
|
||||
export async function getPins(channelID: string) {
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.CHANNEL_PINS(channelID),
|
||||
)) as MessageCreateOptions[];
|
||||
|
||||
return Promise.all(result.map((res) => structures.createMessage(res)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a typing indicator for the specified channel. Generally bots should **NOT** implement this route.
|
||||
* However, if a bot is responding to a command and expects the computation to take a few seconds,
|
||||
* this endpoint may be called to let the user know that the bot is processing their message.
|
||||
*/
|
||||
export async function startTyping(channelID: string) {
|
||||
const result = await RequestManager.post(endpoints.CHANNEL_TYPING(channelID));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Send a message to the channel. Requires SEND_MESSAGES permission. */
|
||||
export async function sendMessage(
|
||||
channelID: string,
|
||||
content: string | MessageContent,
|
||||
) {
|
||||
if (typeof content === "string") content = { content };
|
||||
|
||||
const channel = await cacheHandlers.get("channels", channelID);
|
||||
// If the channel is cached, we can do extra checks/safety
|
||||
if (channel) {
|
||||
if (
|
||||
![ChannelTypes.DM, ChannelTypes.GUILD_NEWS, ChannelTypes.GUILD_TEXT]
|
||||
.includes(channel.type)
|
||||
) {
|
||||
throw new Error(Errors.CHANNEL_NOT_TEXT_BASED);
|
||||
}
|
||||
|
||||
const hasSendMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["SEND_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasSendMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_SEND_MESSAGES);
|
||||
}
|
||||
|
||||
const hasSendTtsMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["SEND_TTS_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
content.tts &&
|
||||
!hasSendTtsMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_SEND_TTS_MESSAGE);
|
||||
}
|
||||
|
||||
const hasEmbedLinksPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["EMBED_LINKS"],
|
||||
);
|
||||
if (
|
||||
content.embed &&
|
||||
!hasEmbedLinksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_EMBED_LINKS);
|
||||
}
|
||||
|
||||
if (content.mentions?.repliedUser) {
|
||||
if (
|
||||
!(await botHasChannelPermissions(
|
||||
channelID,
|
||||
["READ_MESSAGE_HISTORY"],
|
||||
))
|
||||
) {
|
||||
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use ... for content length due to unicode characters and js .length handling
|
||||
if (content.content && [...content.content].length > 2000) {
|
||||
throw new Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
if (content.mentions) {
|
||||
if (content.mentions.users?.length) {
|
||||
if (content.mentions.parse?.includes("users")) {
|
||||
content.mentions.parse = content.mentions.parse.filter((p) =>
|
||||
p !== "users"
|
||||
);
|
||||
}
|
||||
|
||||
if (content.mentions.users.length > 100) {
|
||||
content.mentions.users = content.mentions.users.slice(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.mentions.roles?.length) {
|
||||
if (content.mentions.parse?.includes("roles")) {
|
||||
content.mentions.parse = content.mentions.parse.filter((p) =>
|
||||
p !== "roles"
|
||||
);
|
||||
}
|
||||
|
||||
if (content.mentions.roles.length > 100) {
|
||||
content.mentions.roles = content.mentions.roles.slice(0, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
endpoints.CHANNEL_MESSAGES(channelID),
|
||||
{
|
||||
...content,
|
||||
allowed_mentions: content.mentions
|
||||
? {
|
||||
...content.mentions,
|
||||
replied_user: content.mentions.repliedUser,
|
||||
}
|
||||
: undefined,
|
||||
...(content.replyMessageID
|
||||
? {
|
||||
message_reference: {
|
||||
message_id: content.replyMessageID,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
) as MessageCreateOptions;
|
||||
|
||||
return structures.createMessage(result);
|
||||
}
|
||||
|
||||
/** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */
|
||||
export async function deleteMessages(
|
||||
channelID: string,
|
||||
ids: string[],
|
||||
reason?: string,
|
||||
) {
|
||||
const hasManageMessages = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessages
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
if (ids.length < 2) {
|
||||
throw new Error(Errors.DELETE_MESSAGES_MIN);
|
||||
}
|
||||
|
||||
if (ids.length > 100) {
|
||||
console.warn(
|
||||
`This endpoint only accepts a maximum of 100 messages. Deleting the first 100 message ids provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
endpoints.CHANNEL_BULK_DELETE(channelID),
|
||||
{
|
||||
messages: ids.splice(0, 100),
|
||||
reason,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Gets the invites for this channel. Requires MANAGE_CHANNEL */
|
||||
export async function getChannelInvites(channelID: string) {
|
||||
const hasManagaChannels = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_CHANNELS"],
|
||||
);
|
||||
if (
|
||||
!hasManagaChannels
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
|
||||
}
|
||||
|
||||
const result = await RequestManager.get(endpoints.CHANNEL_INVITES(channelID));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */
|
||||
export async function createInvite(
|
||||
channelID: string,
|
||||
options: CreateInviteOptions,
|
||||
) {
|
||||
const hasCreateInstantInvitePerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["CREATE_INSTANT_INVITE"],
|
||||
);
|
||||
if (
|
||||
!hasCreateInstantInvitePerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_CREATE_INSTANT_INVITE);
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
endpoints.CHANNEL_INVITES(channelID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns an invite for the given code. */
|
||||
export async function getInvite(inviteCode: string) {
|
||||
const result = await RequestManager.get(
|
||||
endpoints.INVITE(inviteCode),
|
||||
);
|
||||
|
||||
return result as InvitePayload;
|
||||
}
|
||||
|
||||
/** Deletes an invite for the given code. Requires `MANAGE_CHANNELS` or `MANAGE_GUILD` permission */
|
||||
export async function deleteInvite(
|
||||
channelID: string,
|
||||
inviteCode: string,
|
||||
) {
|
||||
const hasPerm = await botHasChannelPermissions(channelID, [
|
||||
"MANAGE_CHANNELS",
|
||||
]);
|
||||
|
||||
if (!hasPerm) {
|
||||
const channel = await cacheHandlers.get("channels", channelID);
|
||||
|
||||
const hasManageGuildPerm = await botHasPermission(channel!.guildID, [
|
||||
"MANAGE_GUILD",
|
||||
]);
|
||||
|
||||
if (!hasManageGuildPerm) {
|
||||
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
|
||||
}
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.INVITE(inviteCode),
|
||||
);
|
||||
|
||||
return result as InvitePayload;
|
||||
}
|
||||
|
||||
/** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */
|
||||
export async function getChannelWebhooks(channelID: string) {
|
||||
const hasManageWebhooksPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_WEBHOOKS"],
|
||||
);
|
||||
if (
|
||||
!hasManageWebhooksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
|
||||
}
|
||||
|
||||
const result = await RequestManager.get(
|
||||
endpoints.CHANNEL_WEBHOOKS(channelID),
|
||||
);
|
||||
|
||||
return result as WebhookPayload[];
|
||||
}
|
||||
|
||||
interface EditChannelRequest {
|
||||
amount: number;
|
||||
timestamp: number;
|
||||
channelID: string;
|
||||
items: {
|
||||
channelID: string;
|
||||
options: ChannelEditOptions;
|
||||
}[];
|
||||
}
|
||||
|
||||
const editChannelNameTopicQueue = new Map<string, EditChannelRequest>();
|
||||
let editChannelProcessing = false;
|
||||
|
||||
function processEditChannelQueue() {
|
||||
if (!editChannelProcessing) return;
|
||||
|
||||
const now = Date.now();
|
||||
editChannelNameTopicQueue.forEach((request) => {
|
||||
if (now > request.timestamp) return;
|
||||
// 10 minutes have passed so we can reset this channel again
|
||||
if (!request.items.length) {
|
||||
return editChannelNameTopicQueue.delete(request.channelID);
|
||||
}
|
||||
request.amount = 0;
|
||||
// There are items to process for this request
|
||||
const details = request.items.shift();
|
||||
|
||||
if (!details) return;
|
||||
|
||||
editChannel(details.channelID, details.options);
|
||||
const secondDetails = request.items.shift();
|
||||
if (!secondDetails) return;
|
||||
|
||||
return editChannel(
|
||||
secondDetails.channelID,
|
||||
secondDetails.options,
|
||||
);
|
||||
});
|
||||
|
||||
if (editChannelNameTopicQueue.size) {
|
||||
setTimeout(() => processEditChannelQueue(), 600000);
|
||||
} else {
|
||||
editChannelProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */
|
||||
export async function editChannel(
|
||||
channelID: string,
|
||||
options: ChannelEditOptions,
|
||||
reason?: string,
|
||||
) {
|
||||
const hasManageChannelsPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_CHANNELS"],
|
||||
);
|
||||
if (
|
||||
!hasManageChannelsPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
|
||||
}
|
||||
|
||||
if (options.name || options.topic) {
|
||||
const request = editChannelNameTopicQueue.get(channelID);
|
||||
if (!request) {
|
||||
// If this hasnt been done before simply add 1 for it
|
||||
editChannelNameTopicQueue.set(channelID, {
|
||||
channelID: channelID,
|
||||
amount: 1,
|
||||
// 10 minutes from now
|
||||
timestamp: Date.now() + 600000,
|
||||
items: [],
|
||||
});
|
||||
} else if (request.amount === 1) {
|
||||
// Start queuing future requests to this channel
|
||||
request.amount = 2;
|
||||
request.timestamp = Date.now() + 600000;
|
||||
} else {
|
||||
// 2 have already been used add to queue
|
||||
request.items.push({ channelID, options });
|
||||
if (editChannelProcessing) return;
|
||||
editChannelProcessing = true;
|
||||
processEditChannelQueue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
...options,
|
||||
// deno-lint-ignore camelcase
|
||||
rate_limit_per_user: options.slowmode,
|
||||
// deno-lint-ignore camelcase
|
||||
parent_id: options.parentID,
|
||||
// deno-lint-ignore camelcase
|
||||
user_limit: options.userLimit,
|
||||
// deno-lint-ignore camelcase
|
||||
permission_overwrites: options.overwrites?.map(
|
||||
(overwrite) => {
|
||||
return {
|
||||
...overwrite,
|
||||
allow: calculateBits(overwrite.allow),
|
||||
deny: calculateBits(overwrite.deny),
|
||||
};
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.CHANNEL_BASE(channelID),
|
||||
{
|
||||
...payload,
|
||||
reason,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Follow a News Channel to send messages to a target channel. Requires the `MANAGE_WEBHOOKS` permission in the target channel. Returns the webhook id. */
|
||||
export async function followChannel(
|
||||
sourceChannelID: string,
|
||||
targetChannelID: string,
|
||||
) {
|
||||
const hasManageWebhooksPerm = await botHasChannelPermissions(
|
||||
targetChannelID,
|
||||
["MANAGE_WEBHOOKS"],
|
||||
);
|
||||
if (
|
||||
!hasManageWebhooksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
|
||||
}
|
||||
|
||||
const data = await RequestManager.post(
|
||||
endpoints.CHANNEL_FOLLOW(sourceChannelID),
|
||||
{
|
||||
webhook_channel_id: targetChannelID,
|
||||
},
|
||||
) as FollowedChannelPayload;
|
||||
|
||||
return data.webhook_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a channel is synchronized with its parent/category channel or not.
|
||||
* @param channelID The ID of the channel to test for synchronization
|
||||
* @return Returns `true` if the channel is synchronized, otherwise `false`. Returns `false` if the channel is not cached.
|
||||
*/
|
||||
export async function isChannelSynced(channelID: string) {
|
||||
const channel = await cacheHandlers.get("channels", channelID);
|
||||
if (!channel?.parentID) return false;
|
||||
|
||||
const parentChannel = await cacheHandlers.get("channels", channel.parentID);
|
||||
if (!parentChannel) return false;
|
||||
|
||||
return channel.permissionOverwrites?.every((overwrite) => {
|
||||
const permission = parentChannel.permissionOverwrites?.find((ow) =>
|
||||
ow.id === overwrite.id
|
||||
);
|
||||
if (!permission) return false;
|
||||
return !(overwrite.allow !== permission.allow ||
|
||||
overwrite.deny !== permission.deny);
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,302 +0,0 @@
|
||||
import { botID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
ChannelCreatePayload,
|
||||
DMChannelCreatePayload,
|
||||
EditMemberOptions,
|
||||
Errors,
|
||||
ImageFormats,
|
||||
ImageSize,
|
||||
MessageContent,
|
||||
} from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
botHasPermission,
|
||||
higherRolePosition,
|
||||
highestRole,
|
||||
} from "../../util/permissions.ts";
|
||||
import { formatImageURL, urlToBase64 } from "../../util/utils.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import { Member, structures } from "../structures/mod.ts";
|
||||
import { sendMessage } from "./channel.ts";
|
||||
|
||||
/** The users custom avatar or the default avatar if you don't have a member object. */
|
||||
export function rawAvatarURL(
|
||||
userID: string,
|
||||
discriminator: string,
|
||||
avatar?: string | null,
|
||||
size: ImageSize = 128,
|
||||
format?: ImageFormats,
|
||||
) {
|
||||
return avatar
|
||||
? formatImageURL(endpoints.USER_AVATAR(userID, avatar), size, format)
|
||||
: endpoints.USER_DEFAULT_AVATAR(Number(discriminator) % 5);
|
||||
}
|
||||
|
||||
/** The users custom avatar or the default avatar */
|
||||
export function avatarURL(
|
||||
member: Member,
|
||||
size: ImageSize = 128,
|
||||
format?: ImageFormats,
|
||||
) {
|
||||
return rawAvatarURL(
|
||||
member.id,
|
||||
member.discriminator,
|
||||
member.avatar,
|
||||
size,
|
||||
format,
|
||||
);
|
||||
}
|
||||
|
||||
/** Add a role to the member */
|
||||
export async function addRole(
|
||||
guildID: string,
|
||||
memberID: string,
|
||||
roleID: string,
|
||||
reason?: string,
|
||||
) {
|
||||
const botsHighestRole = await highestRole(guildID, botID);
|
||||
if (botsHighestRole) {
|
||||
const hasHigherRolePosition = await higherRolePosition(
|
||||
guildID,
|
||||
botsHighestRole.id,
|
||||
roleID,
|
||||
);
|
||||
if (
|
||||
!hasHigherRolePosition &&
|
||||
(await cacheHandlers.get("guilds", guildID))?.ownerID !== botID
|
||||
) {
|
||||
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
|
||||
}
|
||||
}
|
||||
|
||||
const hasPerm = await botHasPermission(guildID, ["MANAGE_ROLES"]);
|
||||
if (!hasPerm) {
|
||||
throw new Error(Errors.MISSING_MANAGE_ROLES);
|
||||
}
|
||||
|
||||
const result = await RequestManager.put(
|
||||
endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Remove a role from the member */
|
||||
export async function removeRole(
|
||||
guildID: string,
|
||||
memberID: string,
|
||||
roleID: string,
|
||||
reason?: string,
|
||||
) {
|
||||
const botsHighestRole = await highestRole(guildID, botID);
|
||||
|
||||
if (botsHighestRole) {
|
||||
const hasHigherRolePosition = await higherRolePosition(
|
||||
guildID,
|
||||
botsHighestRole.id,
|
||||
roleID,
|
||||
);
|
||||
if (
|
||||
!hasHigherRolePosition &&
|
||||
(await cacheHandlers.get("guilds", guildID))?.ownerID !== botID
|
||||
) {
|
||||
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
|
||||
}
|
||||
}
|
||||
|
||||
const hasPerm = await botHasPermission(guildID, ["MANAGE_ROLES"]);
|
||||
if (!hasPerm) {
|
||||
throw new Error(Errors.MISSING_MANAGE_ROLES);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */
|
||||
export async function sendDirectMessage(
|
||||
memberID: string,
|
||||
content: string | MessageContent,
|
||||
) {
|
||||
let dmChannel = await cacheHandlers.get("channels", memberID);
|
||||
if (!dmChannel) {
|
||||
// If not available in cache create a new one.
|
||||
const dmChannelData = await RequestManager.post(
|
||||
endpoints.USER_DM,
|
||||
{ recipient_id: memberID },
|
||||
) as DMChannelCreatePayload;
|
||||
const channelStruct = await structures.createChannel(
|
||||
dmChannelData as unknown as ChannelCreatePayload,
|
||||
);
|
||||
// Recreate the channel and add it undert he users id
|
||||
await cacheHandlers.set("channels", memberID, channelStruct);
|
||||
dmChannel = channelStruct;
|
||||
}
|
||||
|
||||
// If it does exist try sending a message to this user
|
||||
return sendMessage(dmChannel.id, content);
|
||||
}
|
||||
|
||||
/** Kick a member from the server */
|
||||
export async function kick(guildID: string, memberID: string, reason?: string) {
|
||||
const botsHighestRole = await highestRole(guildID, botID);
|
||||
const membersHighestRole = await highestRole(guildID, memberID);
|
||||
if (
|
||||
botsHighestRole && membersHighestRole &&
|
||||
botsHighestRole.position <= membersHighestRole.position
|
||||
) {
|
||||
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
|
||||
}
|
||||
|
||||
const hasPerm = await botHasPermission(guildID, ["KICK_MEMBERS"]);
|
||||
if (!hasPerm) {
|
||||
throw new Error(Errors.MISSING_KICK_MEMBERS);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.GUILD_MEMBER(guildID, memberID),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Edit the member */
|
||||
export async function editMember(
|
||||
guildID: string,
|
||||
memberID: string,
|
||||
options: EditMemberOptions,
|
||||
) {
|
||||
if (options.nick) {
|
||||
if (options.nick.length > 32) {
|
||||
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
|
||||
}
|
||||
|
||||
const hasManageNickPerm = await botHasPermission(
|
||||
guildID,
|
||||
["MANAGE_NICKNAMES"],
|
||||
);
|
||||
if (!hasManageNickPerm) {
|
||||
throw new Error(Errors.MISSING_MANAGE_NICKNAMES);
|
||||
}
|
||||
}
|
||||
|
||||
const hasManageRolesPerm = await botHasPermission(
|
||||
guildID,
|
||||
["MANAGE_ROLES"],
|
||||
);
|
||||
if (
|
||||
options.roles &&
|
||||
!hasManageRolesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_ROLES);
|
||||
}
|
||||
|
||||
if (options.mute) {
|
||||
const hasMuteMembersPerm = await botHasPermission(
|
||||
guildID,
|
||||
["MUTE_MEMBERS"],
|
||||
);
|
||||
// TODO: This should check if the member is in a voice channel
|
||||
if (
|
||||
!hasMuteMembersPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MUTE_MEMBERS);
|
||||
}
|
||||
}
|
||||
|
||||
const hasDeafenMembersPerm = await botHasPermission(
|
||||
guildID,
|
||||
["DEAFEN_MEMBERS"],
|
||||
);
|
||||
if (
|
||||
options.deaf &&
|
||||
!hasDeafenMembersPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_DEAFEN_MEMBERS);
|
||||
}
|
||||
|
||||
// TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.GUILD_MEMBER(guildID, memberID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a member from a voice channel to another.
|
||||
* @param guildID the id of the guild which the channel exists in
|
||||
* @param memberID the id of the member to move.
|
||||
* @param channelID id of channel to move user to (if they are connected to voice)
|
||||
*/
|
||||
export function moveMember(
|
||||
guildID: string,
|
||||
memberID: string,
|
||||
channelID: string,
|
||||
) {
|
||||
return editMember(guildID, memberID, { channel_id: channelID });
|
||||
}
|
||||
|
||||
/** Kicks a member from a voice channel */
|
||||
export function kickFromVoiceChannel(guildID: string, memberID: string) {
|
||||
return editMember(guildID, memberID, { channel_id: null });
|
||||
}
|
||||
|
||||
/** Modifies the bot's username or avatar.
|
||||
* NOTE: username: if changed may cause the bot's discriminator to be randomized.
|
||||
*/
|
||||
export async function editBotProfile(username?: string, botAvatarURL?: string) {
|
||||
// Nothing was edited
|
||||
if (!username && !botAvatarURL) return;
|
||||
// Check username requirements if username was provided
|
||||
if (username) {
|
||||
if (username.length > 32) {
|
||||
throw new Error(Errors.USERNAME_MAX_LENGTH);
|
||||
}
|
||||
if (username.length < 2) {
|
||||
throw new Error(Errors.USERNAME_MIN_LENGTH);
|
||||
}
|
||||
if (["@", "#", ":", "```"].some((char) => username.includes(char))) {
|
||||
throw new Error(Errors.USERNAME_INVALID_CHARACTER);
|
||||
}
|
||||
if (["discordtag", "everyone", "here"].includes(username)) {
|
||||
throw new Error(Errors.USERNAME_INVALID_USERNAME);
|
||||
}
|
||||
}
|
||||
|
||||
const avatar = botAvatarURL ? await urlToBase64(botAvatarURL) : undefined;
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.USER_BOT,
|
||||
{
|
||||
username: username?.trim(),
|
||||
avatar,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Edit the nickname of the bot in this guild */
|
||||
export async function editBotNickname(
|
||||
guildID: string,
|
||||
nickname: string | null,
|
||||
) {
|
||||
const hasPerm = await botHasPermission(guildID, ["CHANGE_NICKNAME"]);
|
||||
if (!hasPerm) throw new Error(Errors.MISSING_CHANGE_NICKNAME);
|
||||
|
||||
const response = await RequestManager.patch(
|
||||
endpoints.USER_NICK(guildID),
|
||||
{ nick: nickname },
|
||||
) as { nick: string };
|
||||
|
||||
return response.nick;
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
import { botID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
Errors,
|
||||
MessageContent,
|
||||
MessageCreateOptions,
|
||||
UserPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { botHasChannelPermissions } from "../../util/permissions.ts";
|
||||
import { delay } from "../../util/utils.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import { Message, structures } from "../structures/mod.ts";
|
||||
|
||||
/** Delete a message with the channel id and message id only. */
|
||||
export async function deleteMessageByID(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reason?: string,
|
||||
delayMilliseconds = 0,
|
||||
) {
|
||||
const message = await cacheHandlers.get("messages", messageID);
|
||||
if (message) return deleteMessage(message, reason, delayMilliseconds);
|
||||
|
||||
if (delayMilliseconds) await delay(delayMilliseconds);
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE(channelID, messageID),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Delete a message */
|
||||
export async function deleteMessage(
|
||||
message: Message,
|
||||
reason?: string,
|
||||
delayMilliseconds = 0,
|
||||
) {
|
||||
if (message.author.id !== botID) {
|
||||
// This needs to check the channels permission not the guild permission
|
||||
const hasManageMessages = await botHasChannelPermissions(
|
||||
message.channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessages
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
}
|
||||
|
||||
if (delayMilliseconds) await delay(delayMilliseconds);
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE(message.channelID, message.id),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Pin a message in a channel. Requires MANAGE_MESSAGES. Max pins allowed in a channel = 50. */
|
||||
export async function pin(channelID: string, messageID: string) {
|
||||
const hasManageMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
|
||||
const result = await RequestManager.put(
|
||||
endpoints.CHANNEL_PIN(channelID, messageID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Unpin a message in a channel. Requires MANAGE_MESSAGES. */
|
||||
export async function unpin(channelID: string, messageID: string) {
|
||||
const hasManageMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_PIN(channelID, messageID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Create a reaction for the message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. Requires READ_MESSAGE_HISTORY and ADD_REACTIONS */
|
||||
export async function addReaction(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reaction: string,
|
||||
) {
|
||||
const hasAddReactionsPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["ADD_REACTIONS"],
|
||||
);
|
||||
if (!hasAddReactionsPerm) {
|
||||
throw new Error(Errors.MISSING_ADD_REACTIONS);
|
||||
}
|
||||
|
||||
const hasReadMessageHistoryPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["READ_MESSAGE_HISTORY"],
|
||||
);
|
||||
if (
|
||||
!hasReadMessageHistoryPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
|
||||
}
|
||||
|
||||
if (reaction.startsWith("<:")) {
|
||||
reaction = reaction.substring(2, reaction.length - 1);
|
||||
} else if (reaction.startsWith("<a:")) {
|
||||
reaction = reaction.substring(3, reaction.length - 1);
|
||||
}
|
||||
|
||||
const result = await RequestManager.put(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION_ME(
|
||||
channelID,
|
||||
messageID,
|
||||
reaction,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: add a return?
|
||||
/** Adds multiple reactions to a message. If `ordered` is true(default is false), it will add the reactions one at a time in the order provided. Note: Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. Requires READ_MESSAGE_HISTORY and ADD_REACTIONS */
|
||||
export async function addReactions(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reactions: string[],
|
||||
ordered = false,
|
||||
) {
|
||||
if (!ordered) {
|
||||
await Promise.all(
|
||||
reactions.map((reaction) => addReaction(channelID, messageID, reaction)),
|
||||
);
|
||||
} else {
|
||||
for (const reaction of reactions) {
|
||||
await addReaction(channelID, messageID, reaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes a reaction from the bot on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */
|
||||
export async function removeReaction(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reaction: string,
|
||||
) {
|
||||
if (reaction.startsWith("<:")) {
|
||||
reaction = reaction.substring(2, reaction.length - 1);
|
||||
} else if (reaction.startsWith("<a:")) {
|
||||
reaction = reaction.substring(3, reaction.length - 1);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION_ME(
|
||||
channelID,
|
||||
messageID,
|
||||
reaction,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Removes a reaction from the specified user on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */
|
||||
export async function removeUserReaction(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reaction: string,
|
||||
userID: string,
|
||||
) {
|
||||
const hasManageMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (!hasManageMessagesPerm) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
|
||||
if (reaction.startsWith("<:")) {
|
||||
reaction = reaction.substring(2, reaction.length - 1);
|
||||
} else if (reaction.startsWith("<a:")) {
|
||||
reaction = reaction.substring(3, reaction.length - 1);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION_USER(
|
||||
channelID,
|
||||
messageID,
|
||||
reaction,
|
||||
userID,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Removes all reactions for all emojis on this message. */
|
||||
export async function removeAllReactions(channelID: string, messageID: string) {
|
||||
const hasManageMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE_REACTIONS(channelID, messageID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Removes all reactions for a single emoji on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */
|
||||
export async function removeReactionEmoji(
|
||||
channelID: string,
|
||||
messageID: string,
|
||||
reaction: string,
|
||||
) {
|
||||
const hasManageMessagesPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasManageMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
|
||||
}
|
||||
|
||||
if (reaction.startsWith("<:")) {
|
||||
reaction = reaction.substring(2, reaction.length - 1);
|
||||
} else if (reaction.startsWith("<a:")) {
|
||||
reaction = reaction.substring(3, reaction.length - 1);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Get a list of users that reacted with this emoji. */
|
||||
export async function getReactions(message: Message, reaction: string) {
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION(message.channelID, message.id, reaction),
|
||||
)) as UserPayload[];
|
||||
|
||||
return Promise.all(result.map(async (res) => {
|
||||
const member = await cacheHandlers.get("members", res.id);
|
||||
return member || res;
|
||||
}));
|
||||
}
|
||||
|
||||
/** Edit the message. */
|
||||
export async function editMessage(
|
||||
message: Message,
|
||||
content: string | MessageContent,
|
||||
) {
|
||||
if (
|
||||
message.author.id !== botID
|
||||
) {
|
||||
throw "You can only edit a message that was sent by the bot.";
|
||||
}
|
||||
|
||||
if (typeof content === "string") content = { content };
|
||||
|
||||
const hasSendMessagesPerm = await botHasChannelPermissions(
|
||||
message.channelID,
|
||||
["SEND_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
!hasSendMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_SEND_MESSAGES);
|
||||
}
|
||||
|
||||
const hasSendTtsMessagesPerm = await botHasChannelPermissions(
|
||||
message.channelID,
|
||||
["SEND_TTS_MESSAGES"],
|
||||
);
|
||||
if (
|
||||
content.tts &&
|
||||
!hasSendTtsMessagesPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_SEND_TTS_MESSAGE);
|
||||
}
|
||||
|
||||
if (content.content && content.content.length > 2000) {
|
||||
throw new Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.CHANNEL_MESSAGE(message.channelID, message.id),
|
||||
content,
|
||||
);
|
||||
|
||||
return structures.createMessage(result as MessageCreateOptions);
|
||||
}
|
||||
|
||||
/** Crosspost a message in a News Channel to following channels. */
|
||||
export async function publishMessage(channelID: string, messageID: string) {
|
||||
const data = await RequestManager.post(
|
||||
endpoints.CHANNEL_MESSAGE_CROSSPOST(channelID, messageID),
|
||||
) as MessageCreateOptions;
|
||||
|
||||
return structures.createMessage(data);
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
import {
|
||||
channelOverwriteHasPermission,
|
||||
createInvite,
|
||||
deleteInvite,
|
||||
deleteMessages,
|
||||
editChannel,
|
||||
followChannel,
|
||||
getChannelInvites,
|
||||
getChannelWebhooks,
|
||||
getInvite,
|
||||
getMessage,
|
||||
getMessages,
|
||||
getPins,
|
||||
isChannelSynced,
|
||||
sendMessage,
|
||||
startTyping,
|
||||
} from "./channel.ts";
|
||||
import { getGatewayBot } from "./gateway.ts";
|
||||
import {
|
||||
ban,
|
||||
categoryChildrenIDs,
|
||||
createEmoji,
|
||||
createGuildChannel,
|
||||
createGuildFromTemplate,
|
||||
createGuildRole,
|
||||
createGuildTemplate,
|
||||
createServer,
|
||||
deleteChannel,
|
||||
deleteChannelOverwrite,
|
||||
deleteEmoji,
|
||||
deleteGuildTemplate,
|
||||
deleteIntegration,
|
||||
deleteRole,
|
||||
deleteServer,
|
||||
editChannelOverwrite,
|
||||
editEmbed,
|
||||
editEmoji,
|
||||
editGuild,
|
||||
editGuildTemplate,
|
||||
editIntegration,
|
||||
editRole,
|
||||
emojiURL,
|
||||
fetchMembers,
|
||||
getAuditLogs,
|
||||
getAvailableVoiceRegions,
|
||||
getBan,
|
||||
getBans,
|
||||
getChannel,
|
||||
getChannels,
|
||||
getEmbed,
|
||||
getEmoji,
|
||||
getEmojis,
|
||||
getGuild,
|
||||
getGuildPreview,
|
||||
getGuildTemplate,
|
||||
getGuildTemplates,
|
||||
getIntegrations,
|
||||
getInvites,
|
||||
getMember,
|
||||
getMembers,
|
||||
getMembersByQuery,
|
||||
getPruneCount,
|
||||
getRoles,
|
||||
getTemplate,
|
||||
getUser,
|
||||
getVanityURL,
|
||||
getVoiceRegions,
|
||||
getWebhooks,
|
||||
guildBannerURL,
|
||||
guildIconURL,
|
||||
guildSplashURL,
|
||||
leaveGuild,
|
||||
pruneMembers,
|
||||
swapChannels,
|
||||
swapRoles,
|
||||
syncGuildTemplate,
|
||||
syncIntegration,
|
||||
unban,
|
||||
} from "./guild.ts";
|
||||
import {
|
||||
addRole,
|
||||
avatarURL,
|
||||
editBotNickname,
|
||||
editBotProfile,
|
||||
editMember,
|
||||
kick,
|
||||
kickFromVoiceChannel,
|
||||
moveMember,
|
||||
rawAvatarURL,
|
||||
removeRole,
|
||||
sendDirectMessage,
|
||||
} from "./member.ts";
|
||||
import {
|
||||
addReaction,
|
||||
addReactions,
|
||||
deleteMessage,
|
||||
deleteMessageByID,
|
||||
editMessage,
|
||||
getReactions,
|
||||
pin,
|
||||
publishMessage,
|
||||
removeAllReactions,
|
||||
removeReaction,
|
||||
removeReactionEmoji,
|
||||
removeUserReaction,
|
||||
unpin,
|
||||
} from "./message.ts";
|
||||
import { getApplicationInformation } from "./oauth.ts";
|
||||
import {
|
||||
createSlashCommand,
|
||||
createWebhook,
|
||||
deleteSlashCommand,
|
||||
deleteSlashResponse,
|
||||
deleteWebhookMessage,
|
||||
editSlashCommand,
|
||||
editSlashResponse,
|
||||
editWebhookMessage,
|
||||
executeSlashCommand,
|
||||
executeWebhook,
|
||||
getSlashCommand,
|
||||
getSlashCommands,
|
||||
getWebhook,
|
||||
upsertSlashCommand,
|
||||
upsertSlashCommands,
|
||||
} from "./webhook.ts";
|
||||
|
||||
export let handlers = {
|
||||
// Channel handler
|
||||
channelOverwriteHasPermission,
|
||||
createInvite,
|
||||
deleteMessages,
|
||||
editChannel,
|
||||
followChannel,
|
||||
getChannelInvites,
|
||||
getChannelWebhooks,
|
||||
getMessage,
|
||||
getMessages,
|
||||
getPins,
|
||||
isChannelSynced,
|
||||
sendMessage,
|
||||
getInvite,
|
||||
deleteInvite,
|
||||
startTyping,
|
||||
|
||||
// Gateway handler
|
||||
getGatewayBot,
|
||||
|
||||
// Guild handler
|
||||
ban,
|
||||
categoryChildrenIDs,
|
||||
createEmoji,
|
||||
createGuildChannel,
|
||||
createGuildFromTemplate,
|
||||
createGuildRole,
|
||||
createGuildTemplate,
|
||||
createServer,
|
||||
deleteChannel,
|
||||
deleteEmoji,
|
||||
deleteGuildTemplate,
|
||||
deleteIntegration,
|
||||
deleteRole,
|
||||
deleteServer,
|
||||
editEmbed,
|
||||
editEmoji,
|
||||
editGuild,
|
||||
editGuildTemplate,
|
||||
editIntegration,
|
||||
editRole,
|
||||
emojiURL,
|
||||
fetchMembers,
|
||||
getAuditLogs,
|
||||
getBan,
|
||||
getBans,
|
||||
getChannel,
|
||||
getChannels,
|
||||
getEmbed,
|
||||
getEmoji,
|
||||
getEmojis,
|
||||
getGuild,
|
||||
getGuildPreview,
|
||||
getGuildTemplate,
|
||||
getGuildTemplates,
|
||||
getAvailableVoiceRegions,
|
||||
getIntegrations,
|
||||
getInvites,
|
||||
getMember,
|
||||
getMembers,
|
||||
getTemplate,
|
||||
getMembersByQuery,
|
||||
getPruneCount,
|
||||
getRoles,
|
||||
getUser,
|
||||
getVanityURL,
|
||||
getVoiceRegions,
|
||||
getWebhooks,
|
||||
guildBannerURL,
|
||||
guildIconURL,
|
||||
guildSplashURL,
|
||||
leaveGuild,
|
||||
pruneMembers,
|
||||
swapChannels,
|
||||
editChannelOverwrite,
|
||||
deleteChannelOverwrite,
|
||||
swapRoles,
|
||||
syncGuildTemplate,
|
||||
syncIntegration,
|
||||
unban,
|
||||
|
||||
// Member handler
|
||||
addRole,
|
||||
avatarURL,
|
||||
editBotProfile,
|
||||
editBotNickname,
|
||||
editMember,
|
||||
kick,
|
||||
moveMember,
|
||||
rawAvatarURL,
|
||||
removeRole,
|
||||
sendDirectMessage,
|
||||
kickFromVoiceChannel,
|
||||
|
||||
// Message handler
|
||||
addReaction,
|
||||
addReactions,
|
||||
deleteMessage,
|
||||
deleteMessageByID,
|
||||
editMessage,
|
||||
getReactions,
|
||||
pin,
|
||||
publishMessage,
|
||||
removeAllReactions,
|
||||
removeReaction,
|
||||
removeReactionEmoji,
|
||||
removeUserReaction,
|
||||
unpin,
|
||||
|
||||
// Webhook handler
|
||||
createWebhook,
|
||||
executeWebhook,
|
||||
getWebhook,
|
||||
editWebhookMessage,
|
||||
deleteWebhookMessage,
|
||||
createSlashCommand,
|
||||
getSlashCommand,
|
||||
getSlashCommands,
|
||||
upsertSlashCommand,
|
||||
upsertSlashCommands,
|
||||
editSlashCommand,
|
||||
deleteSlashCommand,
|
||||
executeSlashCommand,
|
||||
deleteSlashResponse,
|
||||
editSlashResponse,
|
||||
|
||||
// OAuth handler
|
||||
getApplicationInformation,
|
||||
};
|
||||
|
||||
export type Handlers = typeof handlers;
|
||||
|
||||
export function updateHandlers(newHandlers: Partial<Handlers>) {
|
||||
handlers = {
|
||||
...handlers,
|
||||
...newHandlers,
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { OAuthApplication } from "../../types/oauth.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Returns the bot's OAuth2 application object without `flags`. */
|
||||
export async function getApplicationInformation() {
|
||||
const result = await RequestManager.get(
|
||||
endpoints.OAUTH2_APPLICATION,
|
||||
);
|
||||
|
||||
return result as OAuthApplication;
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
CreateSlashCommandOptions,
|
||||
EditSlashCommandOptions,
|
||||
EditSlashResponseOptions,
|
||||
EditWebhookMessageOptions,
|
||||
Errors,
|
||||
ExecuteSlashCommandOptions,
|
||||
ExecuteWebhookOptions,
|
||||
MessageCreateOptions,
|
||||
SlashCommand,
|
||||
UpsertSlashCommandOptions,
|
||||
UpsertSlashCommandsOptions,
|
||||
WebhookCreateOptions,
|
||||
WebhookEditOptions,
|
||||
WebhookPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { botHasChannelPermissions } from "../../util/permissions.ts";
|
||||
import { urlToBase64 } from "../../util/utils.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
|
||||
/**
|
||||
* Create a new webhook. Requires the MANAGE_WEBHOOKS permission. Returns a webhook object on success. Webhook names follow our naming restrictions that can be found in our Usernames and Nicknames documentation, with the following additional stipulations:
|
||||
*
|
||||
* Webhook names cannot be: 'clyde'
|
||||
*/
|
||||
export async function createWebhook(
|
||||
channelID: string,
|
||||
options: WebhookCreateOptions,
|
||||
) {
|
||||
const hasManageWebhooksPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_WEBHOOKS"],
|
||||
);
|
||||
if (
|
||||
!hasManageWebhooksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
|
||||
}
|
||||
|
||||
if (
|
||||
// Specific usernames that discord does not allow
|
||||
options.name === "clyde" ||
|
||||
// Character limit checks. [...] checks are because of js unicode length handling
|
||||
[...options.name].length < 2 || [...options.name].length > 32
|
||||
) {
|
||||
throw new Error(Errors.INVALID_WEBHOOK_NAME);
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
endpoints.CHANNEL_WEBHOOKS(channelID),
|
||||
{
|
||||
...options,
|
||||
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
|
||||
},
|
||||
);
|
||||
|
||||
return result as WebhookPayload;
|
||||
}
|
||||
|
||||
/** Edit a webhook. Requires the `MANAGE_WEBHOOKS` permission. Returns the updated webhook object on success. */
|
||||
export async function editWebhook(
|
||||
channelID: string,
|
||||
webhookID: string,
|
||||
options: WebhookEditOptions,
|
||||
) {
|
||||
const hasManageWebhooksPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_WEBHOOKS"],
|
||||
);
|
||||
if (
|
||||
!hasManageWebhooksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(endpoints.WEBHOOK_ID(webhookID), {
|
||||
...options,
|
||||
channel_id: options.channelID,
|
||||
});
|
||||
|
||||
return result as WebhookPayload;
|
||||
}
|
||||
|
||||
/** Edit a webhook. Returns the updated webhook object on success. */
|
||||
export async function editWebhookWithToken(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
options: Omit<WebhookEditOptions, "channelID">,
|
||||
) {
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.WEBHOOK(webhookID, webhookToken),
|
||||
options,
|
||||
);
|
||||
|
||||
return result as WebhookPayload;
|
||||
}
|
||||
|
||||
/** Delete a webhook permanently. Requires the `MANAGE_WEBHOOKS` permission. Returns a undefined on success */
|
||||
export async function deleteWebhook(channelID: string, webhookID: string) {
|
||||
const hasManageWebhooksPerm = await botHasChannelPermissions(
|
||||
channelID,
|
||||
["MANAGE_WEBHOOKS"],
|
||||
);
|
||||
if (
|
||||
!hasManageWebhooksPerm
|
||||
) {
|
||||
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
|
||||
}
|
||||
|
||||
const result = await RequestManager.delete(endpoints.WEBHOOK_ID(webhookID));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Delete a webhook permanently. Returns a undefined on success */
|
||||
export async function deleteWebhookWithToken(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
) {
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.WEBHOOK(webhookID, webhookToken),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the new webhook object for the given id. */
|
||||
export async function getWebhook(webhookID: string) {
|
||||
const result = await RequestManager.get(endpoints.WEBHOOK_ID(webhookID));
|
||||
|
||||
return result as WebhookPayload;
|
||||
}
|
||||
|
||||
/** Returns the new webhook object for the given id, this call does not require authentication and returns no user in the webhook object. */
|
||||
export async function getWebhookWithToken(webhookID: string, token: string) {
|
||||
const result = await RequestManager.get(
|
||||
endpoints.WEBHOOK(webhookID, token),
|
||||
);
|
||||
|
||||
return result as WebhookPayload;
|
||||
}
|
||||
|
||||
/** Execute a webhook with webhook ID and webhook token */
|
||||
export async function executeWebhook(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
options: ExecuteWebhookOptions,
|
||||
) {
|
||||
if (!options.content && !options.file && !options.embeds) {
|
||||
throw new Error(Errors.INVALID_WEBHOOK_OPTIONS);
|
||||
}
|
||||
|
||||
if (options.content && options.content.length > 2000) {
|
||||
throw Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
if (options.embeds && options.embeds.length > 10) {
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
|
||||
if (options.mentions) {
|
||||
if (options.mentions.users?.length) {
|
||||
if (options.mentions.parse.includes("users")) {
|
||||
options.mentions.parse = options.mentions.parse.filter((p) =>
|
||||
p !== "users"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mentions.users.length > 100) {
|
||||
options.mentions.users = options.mentions.users.slice(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.mentions.roles?.length) {
|
||||
if (options.mentions.parse.includes("roles")) {
|
||||
options.mentions.parse = options.mentions.parse.filter((p) =>
|
||||
p !== "roles"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mentions.roles.length > 100) {
|
||||
options.mentions.roles = options.mentions.roles.slice(0, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
`${endpoints.WEBHOOK(webhookID, webhookToken)}${
|
||||
options.wait ? "?wait=true" : ""
|
||||
}`,
|
||||
{
|
||||
...options,
|
||||
allowed_mentions: options.mentions,
|
||||
avatar_url: options.avatar_url,
|
||||
},
|
||||
);
|
||||
if (!options.wait) return;
|
||||
|
||||
return structures.createMessage(result as MessageCreateOptions);
|
||||
}
|
||||
|
||||
export async function editWebhookMessage(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
messageID: string,
|
||||
options: EditWebhookMessageOptions,
|
||||
) {
|
||||
if (options.content && options.content.length > 2000) {
|
||||
throw Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
if (options.embeds && options.embeds.length > 10) {
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
|
||||
if (options.allowed_mentions) {
|
||||
if (options.allowed_mentions.users?.length) {
|
||||
if (options.allowed_mentions.parse.includes("users")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
|
||||
p,
|
||||
) => p !== "users");
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.users.length > 100) {
|
||||
options.allowed_mentions.users = options.allowed_mentions.users.slice(
|
||||
0,
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles?.length) {
|
||||
if (options.allowed_mentions.parse.includes("roles")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
|
||||
p,
|
||||
) => p !== "roles");
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles.length > 100) {
|
||||
options.allowed_mentions.roles = options.allowed_mentions.roles.slice(
|
||||
0,
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken, messageID),
|
||||
{ ...options, allowed_mentions: options.allowed_mentions },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteWebhookMessage(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
messageID: string,
|
||||
) {
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken, messageID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are two kinds of Slash Commands: global commands and guild commands. Global commands are available for every guild that adds your app; guild commands are specific to the guild you specify when making them. Command names are unique per application within each scope (global and guild). That means:
|
||||
*
|
||||
* - Your app **cannot** have two global commands with the same name
|
||||
* - Your app **cannot** have two guild commands within the same name **on the same guild**
|
||||
* - Your app **can** have a global and guild command with the same name
|
||||
* - Multiple apps **can** have commands with the same names
|
||||
*
|
||||
* Global commands are cached for **1 hour**. That means that new global commands will fan out slowly across all guilds, and will be guaranteed to be updated in an hour.
|
||||
* Guild commands update **instantly**. We recommend you use guild commands for quick testing, and global commands when they're ready for public use.
|
||||
*/
|
||||
export async function createSlashCommand(options: CreateSlashCommandOptions) {
|
||||
// Use ... for content length due to unicode characters and js .length handling
|
||||
if ([...options.name].length < 2 || [...options.name].length > 32) {
|
||||
throw new Error(Errors.INVALID_SLASH_NAME);
|
||||
}
|
||||
|
||||
if (
|
||||
[...options.description].length < 1 || [...options.description].length > 100
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
options.guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, options.guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Fetchs the global command for the given ID. If a guildID is provided, the guild command will be fetched. */
|
||||
export async function getSlashCommand(commandID: string, guildID?: string) {
|
||||
const result = await RequestManager.get(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD_ID(applicationID, guildID, commandID)
|
||||
: endpoints.COMMANDS_ID(applicationID, commandID),
|
||||
);
|
||||
|
||||
return result as SlashCommand;
|
||||
}
|
||||
|
||||
/** Fetch all of the global commands for your application. */
|
||||
export async function getSlashCommands(guildID?: string) {
|
||||
// TODO: Should this be a returned as a collection?
|
||||
const result = await RequestManager.get(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an existing slash command. If this command did not exist, it will create it.
|
||||
*/
|
||||
export async function upsertSlashCommand(
|
||||
commandID: string,
|
||||
options: UpsertSlashCommandOptions,
|
||||
guildID?: string,
|
||||
) {
|
||||
// Use ... for content length due to unicode characters and js .length handling
|
||||
if ([...options.name].length < 2 || [...options.name].length > 32) {
|
||||
throw new Error(Errors.INVALID_SLASH_NAME);
|
||||
}
|
||||
|
||||
if (
|
||||
[...options.description].length < 1 || [...options.description].length > 100
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD_ID(
|
||||
applicationID,
|
||||
guildID,
|
||||
commandID,
|
||||
)
|
||||
: endpoints.COMMANDS_ID(applicationID, commandID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk edit existing slash commands. If a command does not exist, it will create it.
|
||||
*
|
||||
* **NOTE:** Any slash commands that are not specified in this function will be **deleted**. If you don't provide the commandID and rename your command, the command gets a new ID.
|
||||
*/
|
||||
export async function upsertSlashCommands(
|
||||
options: UpsertSlashCommandsOptions[],
|
||||
guildID?: string,
|
||||
) {
|
||||
const data = options.map((option) => {
|
||||
// Use ... for content length due to unicode characters and js .length handling
|
||||
if ([...option.name].length < 2 || [...option.name].length > 32) {
|
||||
throw new Error(Errors.INVALID_SLASH_NAME);
|
||||
}
|
||||
|
||||
if (
|
||||
[...option.description].length < 1 || [...option.description].length > 100
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
|
||||
}
|
||||
|
||||
return option;
|
||||
});
|
||||
|
||||
const result = await RequestManager.put(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
data,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: remove this function for v11
|
||||
/**
|
||||
* Edit an existing slash command.
|
||||
* @deprecated This function will be removed in v11. Use `upsertSlashCommand()` instead
|
||||
*/
|
||||
export async function editSlashCommand(
|
||||
commandID: string,
|
||||
options: EditSlashCommandOptions,
|
||||
guildID?: string,
|
||||
) {
|
||||
// Use ... for content length due to unicode characters and js .length handling
|
||||
if ([...options.name].length < 2 || [...options.name].length > 32) {
|
||||
throw new Error(Errors.INVALID_SLASH_NAME);
|
||||
}
|
||||
|
||||
if (
|
||||
[...options.description].length < 1 || [...options.description].length > 100
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD_ID(
|
||||
applicationID,
|
||||
guildID,
|
||||
commandID,
|
||||
)
|
||||
: endpoints.COMMANDS_ID(applicationID, commandID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Deletes a slash command. */
|
||||
export function deleteSlashCommand(id: string, guildID?: string) {
|
||||
if (!guildID) {
|
||||
return RequestManager.delete(endpoints.COMMANDS_ID(applicationID, id));
|
||||
}
|
||||
return RequestManager.delete(
|
||||
endpoints.COMMANDS_GUILD_ID(applicationID, guildID, id),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a response to a users slash command. The command data will have the id and token necessary to respond.
|
||||
* Interaction `tokens` are valid for **15 minutes** and can be used to send followup messages.
|
||||
*
|
||||
* NOTE: By default we will suppress mentions. To enable mentions, just pass any mentions object.
|
||||
*/
|
||||
export async function executeSlashCommand(
|
||||
id: string,
|
||||
token: string,
|
||||
options: ExecuteSlashCommandOptions,
|
||||
) {
|
||||
// If its already been executed, we need to send a followup response
|
||||
if (cache.executedSlashCommands.has(token)) {
|
||||
return RequestManager.post(endpoints.WEBHOOK(applicationID, token), {
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
// Expire in 15 minutes
|
||||
cache.executedSlashCommands.set(token, id);
|
||||
setTimeout(
|
||||
() => cache.executedSlashCommands.delete(token),
|
||||
Date.now() + 900000,
|
||||
);
|
||||
|
||||
// If no mentions are provided, force disable mentions
|
||||
if (!(options.data.allowed_mentions)) {
|
||||
options.data.allowed_mentions = { parse: [] };
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(
|
||||
endpoints.INTERACTION_ID_TOKEN(id, token),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** To delete your response to a slash command. If a message id is not provided, it will default to deleting the original response. */
|
||||
export async function deleteSlashResponse(
|
||||
token: string,
|
||||
messageID?: string,
|
||||
) {
|
||||
const result = await RequestManager.delete(
|
||||
messageID
|
||||
? endpoints.INTERACTION_ID_TOKEN_MESSAGEID(
|
||||
applicationID,
|
||||
token,
|
||||
messageID,
|
||||
)
|
||||
: endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationID, token),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** To edit your response to a slash command. If a messageID is not provided it will default to editing the original response. */
|
||||
export async function editSlashResponse(
|
||||
token: string,
|
||||
options: EditSlashResponseOptions,
|
||||
) {
|
||||
if (options.content && options.content.length > 2000) {
|
||||
throw Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
if (options.embeds && options.embeds.length > 10) {
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
|
||||
if (options.allowed_mentions) {
|
||||
if (options.allowed_mentions.users?.length) {
|
||||
if (options.allowed_mentions.parse.includes("users")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
|
||||
p,
|
||||
) => p !== "users");
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.users.length > 100) {
|
||||
options.allowed_mentions.users = options.allowed_mentions.users.slice(
|
||||
0,
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles?.length) {
|
||||
if (options.allowed_mentions.parse.includes("roles")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
|
||||
p,
|
||||
) => p !== "roles");
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles.length > 100) {
|
||||
options.allowed_mentions.roles = options.allowed_mentions.roles.slice(
|
||||
0,
|
||||
100,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
options.messageID
|
||||
? endpoints.WEBHOOK_MESSAGE(applicationID, token, options.messageID)
|
||||
: endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationID, token),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import {
|
||||
ChannelCreatePayload,
|
||||
ChannelEditOptions,
|
||||
ChannelType,
|
||||
MessageContent,
|
||||
Overwrite,
|
||||
Permission,
|
||||
RawOverwrite,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import {
|
||||
channelOverwriteHasPermission,
|
||||
editChannel,
|
||||
sendMessage,
|
||||
} from "../handlers/channel.ts";
|
||||
import {
|
||||
deleteChannel,
|
||||
deleteChannelOverwrite,
|
||||
editChannelOverwrite,
|
||||
} from "../handlers/guild.ts";
|
||||
import { kickFromVoiceChannel } from "../handlers/member.ts";
|
||||
import { CleanVoiceState, Guild } from "./guild.ts";
|
||||
import { Member } from "./member.ts";
|
||||
import { Message } from "./message.ts";
|
||||
|
||||
const baseChannel: Partial<Channel> = {
|
||||
get guild() {
|
||||
return cache.guilds.get(this.guildID!);
|
||||
},
|
||||
get messages() {
|
||||
return cache.messages.filter((m) => m.channelID === this.id!);
|
||||
},
|
||||
get mention() {
|
||||
return `<#${this.id!}>`;
|
||||
},
|
||||
get voiceStates() {
|
||||
return this.guild?.voiceStates.filter((voiceState) =>
|
||||
voiceState.channelID === this.id
|
||||
);
|
||||
},
|
||||
get connectedMembers() {
|
||||
const voiceStates = this.voiceStates;
|
||||
if (!voiceStates) return undefined;
|
||||
|
||||
return new Collection(
|
||||
voiceStates.map((vs, key) => [key, cache.members.get(key)]),
|
||||
);
|
||||
},
|
||||
send(content) {
|
||||
return sendMessage(this.id!, content);
|
||||
},
|
||||
disconnect(memberID) {
|
||||
return kickFromVoiceChannel(this.guildID!, memberID);
|
||||
},
|
||||
delete() {
|
||||
return deleteChannel(this.guildID!, this.id!);
|
||||
},
|
||||
editOverwrite(id, options) {
|
||||
return editChannelOverwrite(this.guildID!, this.id!, id, options);
|
||||
},
|
||||
deleteOverwrite(id) {
|
||||
return deleteChannelOverwrite(this.guildID!, this.id!, id);
|
||||
},
|
||||
hasPermission(overwrites, permissions) {
|
||||
return channelOverwriteHasPermission(
|
||||
this.guildID!,
|
||||
this.id!,
|
||||
overwrites,
|
||||
permissions,
|
||||
);
|
||||
},
|
||||
edit(options, reason) {
|
||||
return editChannel(this.id!, options, reason);
|
||||
},
|
||||
};
|
||||
|
||||
// deno-lint-ignore require-await
|
||||
export async function createChannel(
|
||||
data: ChannelCreatePayload,
|
||||
guildID?: string,
|
||||
) {
|
||||
const {
|
||||
guild_id: rawGuildID = "",
|
||||
last_message_id: lastMessageID,
|
||||
user_limit: userLimit,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
parent_id: parentID = undefined,
|
||||
last_pin_timestamp: lastPinTimestamp,
|
||||
permission_overwrites: permissionOverwrites = [],
|
||||
nsfw = false,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const restProps: Record<string, ReturnType<typeof createNewProp>> = {};
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
const channel = Object.create(baseChannel, {
|
||||
...restProps,
|
||||
guildID: createNewProp(guildID || rawGuildID),
|
||||
lastMessageID: createNewProp(lastMessageID),
|
||||
userLimit: createNewProp(userLimit),
|
||||
rateLimitPerUser: createNewProp(rateLimitPerUser),
|
||||
parentID: createNewProp(parentID),
|
||||
lastPinTimestamp: createNewProp(
|
||||
lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined,
|
||||
),
|
||||
permissionOverwrites: createNewProp(permissionOverwrites),
|
||||
nsfw: createNewProp(nsfw),
|
||||
});
|
||||
|
||||
return channel as Channel;
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
/** The id of this channel */
|
||||
id: string;
|
||||
/** Sorting position of the channel */
|
||||
position?: number;
|
||||
/** The name of the channel (2-100 characters) */
|
||||
name?: string;
|
||||
/** The channel topic (0-1024 characters) */
|
||||
topic?: string;
|
||||
/** The bitrate (in bits) of the voice channel */
|
||||
bitrate?: number;
|
||||
/** The type of the channel */
|
||||
type: ChannelType;
|
||||
/** The guild id of the channel if it is a guild channel. */
|
||||
guildID: string;
|
||||
/** The id of the last message sent in this channel */
|
||||
lastMessageID?: string;
|
||||
/** The amount of users allowed in this voice channel. */
|
||||
userLimit?: number;
|
||||
/** The rate limit(slowmode) in this text channel that users can send messages. */
|
||||
rateLimitPerUser?: number;
|
||||
/** The category id for this channel */
|
||||
parentID?: string;
|
||||
/** The last time when a message was pinned in this channel */
|
||||
lastPinTimestamp?: number;
|
||||
/** The permission overwrites for this channel */
|
||||
permissionOverwrites: RawOverwrite[];
|
||||
/** Whether this channel is nsfw or not */
|
||||
nsfw: boolean;
|
||||
|
||||
// GETTERS
|
||||
|
||||
/**
|
||||
* Gets the guild object for this channel.
|
||||
*
|
||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||
*/
|
||||
guild?: Guild;
|
||||
/**
|
||||
* Gets the messages from cache that were sent in this channel
|
||||
*
|
||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||
*/
|
||||
messages: Collection<string, Message>;
|
||||
/** The mention of the channel */
|
||||
mention: string;
|
||||
/**
|
||||
* Gets the voice states for this channel
|
||||
*
|
||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||
*/
|
||||
voiceStates?: Collection<string, CleanVoiceState>;
|
||||
/**
|
||||
* Gets the connected members for this channel undefined if member is not cached
|
||||
*
|
||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||
*/
|
||||
connectedMembers?: Collection<string, Member | undefined>;
|
||||
|
||||
// METHODS
|
||||
|
||||
/** Send a message to the channel. Requires SEND_MESSAGES permission. */
|
||||
send(content: string | MessageContent): ReturnType<typeof sendMessage>;
|
||||
/** Disconnect a member from a voice channel. Requires MOVE_MEMBERS permission. */
|
||||
disconnect(memberID: string): ReturnType<typeof kickFromVoiceChannel>;
|
||||
/** Delete the channel */
|
||||
delete(): ReturnType<typeof deleteChannel>;
|
||||
/** Edit a channel Overwrite */
|
||||
editOverwrite(
|
||||
overwriteID: string,
|
||||
options: Omit<Overwrite, "id">,
|
||||
): ReturnType<typeof editChannelOverwrite>;
|
||||
/** Delete a channel Overwrite */
|
||||
deleteOverwrite(
|
||||
overwriteID: string,
|
||||
): ReturnType<typeof deleteChannelOverwrite>;
|
||||
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
|
||||
hasPermission(
|
||||
overwrites: RawOverwrite[],
|
||||
permissions: Permission[],
|
||||
): ReturnType<typeof channelOverwriteHasPermission>;
|
||||
/** Edit the channel */
|
||||
edit(
|
||||
options: ChannelEditOptions,
|
||||
reason?: string,
|
||||
): ReturnType<typeof editChannel>;
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
import { botID } from "../../bot.ts";
|
||||
import {
|
||||
BanOptions,
|
||||
CreateGuildPayload,
|
||||
Emoji,
|
||||
GetAuditLogsOptions,
|
||||
GuildEditOptions,
|
||||
GuildFeatures,
|
||||
GuildMember,
|
||||
ImageFormats,
|
||||
ImageSize,
|
||||
MemberCreatePayload,
|
||||
Presence,
|
||||
VoiceState,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import {
|
||||
ban,
|
||||
deleteServer,
|
||||
editGuild,
|
||||
getAuditLogs,
|
||||
getBan,
|
||||
getBans,
|
||||
getInvites,
|
||||
guildBannerURL,
|
||||
guildIconURL,
|
||||
leaveGuild,
|
||||
unban,
|
||||
} from "../handlers/guild.ts";
|
||||
import { Member } from "./member.ts";
|
||||
import { Channel, Role, structures } from "./mod.ts";
|
||||
|
||||
export const initialMemberLoadQueue = new Map<string, MemberCreatePayload[]>();
|
||||
|
||||
const baseGuild: Partial<Guild> = {
|
||||
get members() {
|
||||
return cache.members.filter((member) => member.guilds.has(this.id!));
|
||||
},
|
||||
get channels() {
|
||||
return cache.channels.filter((channel) => channel.guildID === this.id);
|
||||
},
|
||||
get afkChannel() {
|
||||
return cache.channels.get(this.afkChannelID!);
|
||||
},
|
||||
get publicUpdatesChannel() {
|
||||
return cache.channels.get(this.publicUpdatesChannelID!);
|
||||
},
|
||||
get rulesChannel() {
|
||||
return cache.channels.get(this.rulesChannelID!);
|
||||
},
|
||||
get systemChannel() {
|
||||
return cache.channels.get(this.systemChannelID!);
|
||||
},
|
||||
get bot() {
|
||||
return cache.members.get(botID);
|
||||
},
|
||||
get botMember() {
|
||||
return this.bot?.guilds.get(this.id!);
|
||||
},
|
||||
get botVoice() {
|
||||
return this.voiceStates?.get(botID);
|
||||
},
|
||||
get owner() {
|
||||
return cache.members.get(this.ownerID!);
|
||||
},
|
||||
get partnered() {
|
||||
return Boolean(this.features?.includes("PARTNERED"));
|
||||
},
|
||||
get verified() {
|
||||
return Boolean(this.features?.includes("VERIFIED"));
|
||||
},
|
||||
bannerURL(size, format) {
|
||||
return guildBannerURL(this as Guild, size, format);
|
||||
},
|
||||
delete() {
|
||||
return deleteServer(this.id!);
|
||||
},
|
||||
edit(options) {
|
||||
return editGuild(this.id!, options);
|
||||
},
|
||||
auditLogs(options) {
|
||||
return getAuditLogs(this.id!, options);
|
||||
},
|
||||
getBan(memberID) {
|
||||
return getBan(this.id!, memberID);
|
||||
},
|
||||
bans() {
|
||||
return getBans(this.id!);
|
||||
},
|
||||
ban(memberID, options) {
|
||||
return ban(this.id!, memberID, options);
|
||||
},
|
||||
unban(memberID) {
|
||||
return unban(this.id!, memberID);
|
||||
},
|
||||
invites() {
|
||||
return getInvites(this.id!);
|
||||
},
|
||||
iconURL(size, format) {
|
||||
return guildIconURL(this as Guild, size, format);
|
||||
},
|
||||
leave() {
|
||||
return leaveGuild(this.id!);
|
||||
},
|
||||
};
|
||||
|
||||
export async function createGuild(data: CreateGuildPayload, shardID: number) {
|
||||
const {
|
||||
disovery_splash: discoverySplash,
|
||||
default_message_notifications: defaultMessageNotifications,
|
||||
explicit_content_filter: explicitContentFilter,
|
||||
system_channel_flags: systemChannelFlags,
|
||||
rules_channel_id: rulesChannelID,
|
||||
public_updates_channel_id: publicUpdatesChannelID,
|
||||
max_video_channel_users: maxVideoChannelUsers,
|
||||
approximate_member_count: approximateMemberCount,
|
||||
approximate_presence_count: approximatePresenceCount,
|
||||
owner_id: ownerID,
|
||||
afk_channel_id: afkChannelID,
|
||||
afk_timeout: afkTimeout,
|
||||
widget_enabled: widgetEnabled,
|
||||
widget_channel_id: widgetChannelID,
|
||||
verification_level: verificationLevel,
|
||||
mfa_level: mfaLevel,
|
||||
system_channel_id: systemChannelID,
|
||||
max_presences: maxPresences,
|
||||
max_members: maxMembers,
|
||||
vanity_url_code: vanityURLCode,
|
||||
premium_tier: premiumTier,
|
||||
premium_subscription_count: premiumSubscriptionCount,
|
||||
preferred_locale: preferredLocale,
|
||||
joined_at: joinedAt,
|
||||
member_count: memberCount = 0,
|
||||
voice_states: voiceStates = [],
|
||||
channels = [],
|
||||
members,
|
||||
presences = [],
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const roles = await Promise.all(
|
||||
data.roles.map((role) => structures.createRole(role)),
|
||||
);
|
||||
|
||||
await Promise.all(channels.map(async (channel) => {
|
||||
const channelStruct = await structures.createChannel(channel);
|
||||
return cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
}));
|
||||
|
||||
const restProps: Record<string, ReturnType<typeof createNewProp>> = {};
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
const guild = Object.create(baseGuild, {
|
||||
...restProps,
|
||||
discoverySplash: createNewProp(discoverySplash),
|
||||
defaultMessageNotifications: createNewProp(defaultMessageNotifications),
|
||||
explicitContentFilter: createNewProp(explicitContentFilter),
|
||||
rulesChannelID: createNewProp(rulesChannelID),
|
||||
publicUpdatesChannelID: createNewProp(publicUpdatesChannelID),
|
||||
maxVideoChannelUsers: createNewProp(maxVideoChannelUsers),
|
||||
approximateMemberCount: createNewProp(approximateMemberCount),
|
||||
approximatePresenceCount: createNewProp(approximatePresenceCount),
|
||||
shardID: createNewProp(shardID),
|
||||
ownerID: createNewProp(ownerID),
|
||||
afkChannelID: createNewProp(afkChannelID),
|
||||
afkTimeout: createNewProp(afkTimeout),
|
||||
widgetEnabled: createNewProp(widgetEnabled),
|
||||
widgetChannelID: createNewProp(widgetChannelID),
|
||||
verificationLevel: createNewProp(verificationLevel),
|
||||
mfaLevel: createNewProp(mfaLevel),
|
||||
systemChannelID: createNewProp(systemChannelID),
|
||||
maxPresences: createNewProp(maxPresences),
|
||||
maxMembers: createNewProp(maxMembers),
|
||||
vanityURLCode: createNewProp(vanityURLCode),
|
||||
premiumTier: createNewProp(premiumTier),
|
||||
premiumSubscriptionCount: createNewProp(premiumSubscriptionCount),
|
||||
preferredLocale: createNewProp(preferredLocale),
|
||||
roles: createNewProp(new Collection(roles.map((r: Role) => [r.id, r]))),
|
||||
joinedAt: createNewProp(Date.parse(joinedAt)),
|
||||
presences: createNewProp(
|
||||
new Collection(presences.map((p: Presence) => [p.user.id, p])),
|
||||
),
|
||||
memberCount: createNewProp(memberCount),
|
||||
voiceStates: createNewProp(
|
||||
new Collection(
|
||||
voiceStates.map((vs: VoiceState) => [
|
||||
vs.user_id,
|
||||
{
|
||||
...vs,
|
||||
guildID: vs.guild_id,
|
||||
channelID: vs.channel_id,
|
||||
userID: vs.user_id,
|
||||
sessionID: vs.session_id,
|
||||
selfDeaf: vs.self_deaf,
|
||||
selfMute: vs.self_mute,
|
||||
selfStream: vs.self_stream,
|
||||
},
|
||||
]),
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
initialMemberLoadQueue.set(guild.id, members);
|
||||
|
||||
return guild as Guild;
|
||||
}
|
||||
|
||||
export interface Guild {
|
||||
/** The guild id */
|
||||
id: string;
|
||||
/** The guild name 2-100 characters */
|
||||
name: string;
|
||||
/** The guild icon image hash */
|
||||
icon: string | null;
|
||||
/** The guild splash image hash */
|
||||
splash: string | null;
|
||||
/** Discovery splash has; only present for guilds with the "DISCOVERABLE" feature */
|
||||
disoverySplash: string | null;
|
||||
/** The voice region id for the guild */
|
||||
region: string;
|
||||
/** Default message notifications level */
|
||||
defaultMessageNotifications: number;
|
||||
/** Explicit content filter level */
|
||||
explicitContentFilter: number;
|
||||
/** The custom guild emojis */
|
||||
emojis: Emoji[];
|
||||
/** Enabled guild features */
|
||||
features: GuildFeatures[];
|
||||
/** System channel flags */
|
||||
systemChannelFlags: number;
|
||||
/** The id of the channel where guilds with the PUBLIC feature can display rules and or guidelines. */
|
||||
rulesChannelID: string | null;
|
||||
/** The description for the guild */
|
||||
description: string | null;
|
||||
/** The banner hash */
|
||||
banner: string | null;
|
||||
/** The id of the channel where admins and moderators of guilds with the PUBLIC feature receive notices from Discord */
|
||||
publicUpdatesChannelID: string | null;
|
||||
/** The maximum amount of users in a video channel. */
|
||||
maxVideoChannelUsers?: number;
|
||||
/** The approximate number of members in this guild, returned from the GET /guild/id endpoint when with_counts is true */
|
||||
approximateMemberCount?: number;
|
||||
/** The approximate number of non-offline members in this guild, returned from the GET /guild/id endpoint when with_counts is true */
|
||||
approximatePresenceCount?: number;
|
||||
/** Whether this is considered a large guild */
|
||||
large: boolean;
|
||||
/** Whether this guild is unavailable */
|
||||
unavailable: boolean;
|
||||
/** The shard id that this guild is on */
|
||||
shardID: number;
|
||||
/** The owner id of the guild. */
|
||||
ownerID: string;
|
||||
/** The afk channel id for this guild. */
|
||||
afkChannelID: string;
|
||||
/** The amount of time before a user is moved to AFK. */
|
||||
afkTimeout: number;
|
||||
/** Whether or not the embed is enabled in this server. */
|
||||
widgetEnabled: boolean;
|
||||
/** The channel id for the guild embed in this server. */
|
||||
widgetChannelID: string;
|
||||
/** The verification level for this server. */
|
||||
verificationLevel: number;
|
||||
/** The MFA level for this server. */
|
||||
mfaLevel: number;
|
||||
/** The system channel id for this server. */
|
||||
systemChannelID: string;
|
||||
/** The max presences for this server. */
|
||||
maxPresences: number;
|
||||
/** The maximum members in this server. */
|
||||
maxMembers: number;
|
||||
/** The vanity URL code for this server. */
|
||||
vanityURLCode: string;
|
||||
/** The premium tier for this server. */
|
||||
premiumTier: number;
|
||||
/** The subscription count for this server. */
|
||||
premiumSubscriptionCount: number;
|
||||
/** The preferred language in this server. */
|
||||
preferredLocale: string;
|
||||
/** The roles in the guild */
|
||||
roles: Collection<string, Role>;
|
||||
/** When this guild was joined at. */
|
||||
joinedAt: number;
|
||||
/** The presences of all the users in the guild. */
|
||||
presences: Collection<string, Presence>;
|
||||
/** The total number of members in this guild. This value is updated as members leave and join the server. However, if you do not have the intent enabled to be able to listen to these events, then this will not be accurate. */
|
||||
memberCount: number;
|
||||
/** The Voice State data for each user in a voice channel in this server. */
|
||||
voiceStates: Collection<string, CleanVoiceState>;
|
||||
|
||||
// GETTERS
|
||||
/** Members in this guild. */
|
||||
members: Collection<string, Member>;
|
||||
/** Channels in this guild. */
|
||||
channels: Collection<string, Channel>;
|
||||
/** The afk channel if one is set */
|
||||
afkChannel?: Channel;
|
||||
/** The public update channel if one is set */
|
||||
publicUpdatesChannel?: Channel;
|
||||
/** The rules channel in this guild if one is set */
|
||||
rulesChannel?: Channel;
|
||||
/** The system channel in this guild if one is set */
|
||||
systemChannel?: Channel;
|
||||
/** The bot member in this guild if cached */
|
||||
bot?: Member;
|
||||
/** The bot guild member in this guild if cached */
|
||||
botMember?: GuildMember;
|
||||
/** The bots voice state if there is one in this guild */
|
||||
botVoice?: CleanVoiceState;
|
||||
/** The owner member of this guild */
|
||||
owner?: Member;
|
||||
/** Whether or not this guild is partnered */
|
||||
partnered: boolean;
|
||||
/** Whether or not this guild is verified */
|
||||
verified: boolean;
|
||||
|
||||
// METHODS
|
||||
|
||||
/** The banner url for this server */
|
||||
bannerURL(size?: ImageSize, format?: ImageFormats): string | undefined;
|
||||
/** The full URL of the icon from Discords CDN. Undefined when no icon is set. */
|
||||
iconURL(size?: ImageSize, format?: ImageFormats): string | undefined;
|
||||
/** Delete a guild permanently. User must be owner. Returns 204 No Content on success. Fires a Guild Delete Gateway event. */
|
||||
delete(): ReturnType<typeof deleteServer>;
|
||||
/** Leave a guild */
|
||||
leave(): ReturnType<typeof leaveGuild>;
|
||||
/** Edit the server. Requires the MANAGE_GUILD permission. */
|
||||
edit(options: GuildEditOptions): ReturnType<typeof editGuild>;
|
||||
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
|
||||
auditLogs(options: GetAuditLogsOptions): ReturnType<typeof getAuditLogs>;
|
||||
/** Returns a ban object for the given user or a 404 not found if the ban cannot be found. Requires the BAN_MEMBERS permission. */
|
||||
getBan(memberID: string): ReturnType<typeof getBan>;
|
||||
/** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */
|
||||
bans(): ReturnType<typeof getBans>;
|
||||
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */
|
||||
ban(memberID: string, options: BanOptions): ReturnType<typeof ban>;
|
||||
/** Remove the ban for a user. Requires BAN_MEMBERS permission */
|
||||
unban(memberID: string): ReturnType<typeof unban>;
|
||||
/** Get all the invites for this guild. Requires MANAGE_GUILD permission */
|
||||
invites(): ReturnType<typeof getInvites>;
|
||||
}
|
||||
|
||||
export interface CleanVoiceState extends VoiceState {
|
||||
/** The guild id where this voice state is from */
|
||||
guildID: string;
|
||||
/** The channel id where this voice state is from */
|
||||
channelID: string;
|
||||
/** The user id */
|
||||
userID: string;
|
||||
/** The unique random session id for this voice session */
|
||||
sessionID: string;
|
||||
/** Whether the user has deafened themself */
|
||||
selfDeaf: boolean;
|
||||
/** Whether the user has muted themself */
|
||||
selfMute: boolean;
|
||||
/** Whether the user is streaming on go live */
|
||||
selfStream: boolean;
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import {
|
||||
BanOptions,
|
||||
EditMemberOptions,
|
||||
GuildMember,
|
||||
ImageFormats,
|
||||
ImageSize,
|
||||
MemberCreatePayload,
|
||||
MessageContent,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import { ban } from "../handlers/guild.ts";
|
||||
import {
|
||||
addRole,
|
||||
editMember,
|
||||
kick,
|
||||
rawAvatarURL,
|
||||
removeRole,
|
||||
sendDirectMessage,
|
||||
} from "../handlers/member.ts";
|
||||
import { Guild } from "./guild.ts";
|
||||
|
||||
const baseMember: Partial<Member> = {
|
||||
get avatarURL() {
|
||||
return rawAvatarURL(this.id!, this.discriminator!, this.avatar!);
|
||||
},
|
||||
get mention() {
|
||||
return `<@!${this.id!}>`;
|
||||
},
|
||||
get tag() {
|
||||
return `${this.username!}#${this.discriminator!}`;
|
||||
},
|
||||
|
||||
// METHODS
|
||||
makeAvatarURL(options) {
|
||||
return rawAvatarURL(
|
||||
this.id!,
|
||||
this.discriminator!,
|
||||
this.avatar!,
|
||||
options.size,
|
||||
options.format,
|
||||
);
|
||||
},
|
||||
guild(guildID) {
|
||||
return cache.guilds.get(guildID);
|
||||
},
|
||||
name(guildID) {
|
||||
return this.guildMember!(guildID)?.nick || this.username!;
|
||||
},
|
||||
guildMember(guildID) {
|
||||
return this.guilds?.get(guildID);
|
||||
},
|
||||
sendDM(content) {
|
||||
return sendDirectMessage(this.id!, content);
|
||||
},
|
||||
kick(guildID, reason) {
|
||||
return kick(guildID, this.id!, reason);
|
||||
},
|
||||
edit(guildID, options) {
|
||||
return editMember(guildID, this.id!, options);
|
||||
},
|
||||
ban(guildID, options) {
|
||||
return ban(guildID, this.id!, options);
|
||||
},
|
||||
addRole(guildID, roleID, reason) {
|
||||
return addRole(guildID, this.id!, roleID, reason);
|
||||
},
|
||||
removeRole(guildID, roleID, reason) {
|
||||
return removeRole(guildID, this.id!, roleID, reason);
|
||||
},
|
||||
};
|
||||
|
||||
export async function createMember(data: MemberCreatePayload, guildID: string) {
|
||||
const {
|
||||
joined_at: joinedAt,
|
||||
premium_since: premiumSince,
|
||||
user: userData,
|
||||
roles,
|
||||
deaf,
|
||||
mute,
|
||||
nick,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const { mfa_enabled: mfaEnabled, premium_type: premiumType, ...user } =
|
||||
data.user || {};
|
||||
|
||||
const restProps: Record<string, ReturnType<typeof createNewProp>> = {};
|
||||
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
for (const key of Object.keys(user)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(user[key]);
|
||||
}
|
||||
|
||||
const member = Object.create(baseMember, {
|
||||
...restProps,
|
||||
mfaEnabled: createNewProp(mfaEnabled),
|
||||
premiumType: createNewProp(premiumType),
|
||||
/** The guild related data mapped by guild id */
|
||||
guilds: createNewProp(new Collection<string, GuildMember>()),
|
||||
});
|
||||
|
||||
const cached = await cacheHandlers.get("members", user.id);
|
||||
if (cached) {
|
||||
for (const [id, guild] of cached.guilds.entries()) {
|
||||
member.guilds.set(id, guild);
|
||||
}
|
||||
}
|
||||
|
||||
// User was never cached before
|
||||
member.guilds.set(guildID, {
|
||||
nick: nick,
|
||||
roles: roles,
|
||||
joinedAt: Date.parse(joinedAt),
|
||||
premiumSince: premiumSince ? Date.parse(premiumSince) : undefined,
|
||||
deaf: deaf,
|
||||
mute: mute,
|
||||
});
|
||||
|
||||
return member as Member;
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
/** The user's id */
|
||||
id: string;
|
||||
/** the user's username, not unique across the platform */
|
||||
username: string;
|
||||
/** The user's 4 digit discord tag */
|
||||
discriminator: string;
|
||||
/** The user's avatar hash */
|
||||
avatar: string | null;
|
||||
/** Whether the user is a bot */
|
||||
bot?: boolean;
|
||||
/** Whether the user is an official discord system user (part of the urgent message system.) */
|
||||
system?: boolean;
|
||||
/** the user's chosen language option */
|
||||
locale?: string;
|
||||
/** Whether the email on this account has been verified */
|
||||
verified?: boolean;
|
||||
/** The user's email */
|
||||
email?: string;
|
||||
/** The flags on a user's account. */
|
||||
flags?: number;
|
||||
/** Whether or not this user has 2FA enabled. */
|
||||
mfaEnabled?: boolean;
|
||||
/** The premium type for this user */
|
||||
premiumType?: number;
|
||||
/** The guild related data mapped by guild id */
|
||||
guilds: Collection<string, GuildMember>;
|
||||
|
||||
// GETTERS
|
||||
/** The avatar url using the default format and size. */
|
||||
avatarURL: string;
|
||||
/** The mention string for this member */
|
||||
mention: string;
|
||||
/** The username#discriminator tag for this member */
|
||||
tag: string;
|
||||
|
||||
// METHODS
|
||||
|
||||
/** Returns the avatar url for this member and can be dynamically modified with a size or format */
|
||||
makeAvatarURL(options: { size?: ImageSize; format?: ImageFormats }): string;
|
||||
/** Returns the guild for this guildID */
|
||||
guild(guildID: string): Guild | undefined;
|
||||
/** Get the nickname or the username if no nickname */
|
||||
name(guildID: string): string;
|
||||
/** Get the nickname */
|
||||
guildMember(guildID: string): GuildMember | undefined;
|
||||
/** Send a direct message to the user is possible */
|
||||
sendDM(
|
||||
content: string | MessageContent,
|
||||
): ReturnType<typeof sendDirectMessage>;
|
||||
/** Kick the member from a guild */
|
||||
kick(guildID: string, reason?: string): ReturnType<typeof kick>;
|
||||
/** Edit the member in a guild */
|
||||
edit(
|
||||
guildID: string,
|
||||
options: EditMemberOptions,
|
||||
): ReturnType<typeof editMember>;
|
||||
/** Ban a member in a guild */
|
||||
ban(guildID: string, options: BanOptions): ReturnType<typeof ban>;
|
||||
/** Add a role to the member */
|
||||
addRole(
|
||||
guildID: string,
|
||||
roleID: string,
|
||||
reason?: string,
|
||||
): ReturnType<typeof addRole>;
|
||||
/** Remove a role from the member */
|
||||
removeRole(
|
||||
guildID: string,
|
||||
roleID: string,
|
||||
reason?: string,
|
||||
): ReturnType<typeof removeRole>;
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import {
|
||||
Activity,
|
||||
Application,
|
||||
Attachment,
|
||||
Embed,
|
||||
GuildMember,
|
||||
MessageContent,
|
||||
MessageCreateOptions,
|
||||
MessageSticker,
|
||||
Reaction,
|
||||
Reference,
|
||||
UserPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import { cacheHandlers } from "../controllers/cache.ts";
|
||||
import { sendMessage } from "../handlers/channel.ts";
|
||||
import { sendDirectMessage } from "../handlers/member.ts";
|
||||
import {
|
||||
addReaction,
|
||||
addReactions,
|
||||
deleteMessageByID,
|
||||
editMessage,
|
||||
pin,
|
||||
removeAllReactions,
|
||||
removeReaction,
|
||||
removeReactionEmoji,
|
||||
} from "../handlers/message.ts";
|
||||
import { Channel } from "./channel.ts";
|
||||
import { Guild } from "./guild.ts";
|
||||
import { Member } from "./member.ts";
|
||||
import { Role } from "./role.ts";
|
||||
|
||||
const baseMessage: Partial<Message> = {
|
||||
get channel() {
|
||||
if (this.guildID) return cache.channels.get(this.channelID!);
|
||||
return cache.channels.get(this.author?.id!);
|
||||
},
|
||||
get guild() {
|
||||
if (!this.guildID) return undefined;
|
||||
return cache.guilds.get(this.guildID);
|
||||
},
|
||||
get member() {
|
||||
if (!this.author?.id) return undefined;
|
||||
return cache.members.get(this.author?.id);
|
||||
},
|
||||
get guildMember() {
|
||||
if (!this.guildID) return undefined;
|
||||
return this.member?.guilds.get(this.guildID);
|
||||
},
|
||||
get link() {
|
||||
return `https://discord.com/channels/${this.guildID ||
|
||||
"@me"}/${this.channelID}/${this.id}`;
|
||||
},
|
||||
get mentionedRoles() {
|
||||
return this.mentionRoleIDs?.map((id) => this.guild?.roles.get(id)) || [];
|
||||
},
|
||||
get mentionedChannels() {
|
||||
return this.mentionChannelIDs?.map((id) => cache.channels.get(id)) || [];
|
||||
},
|
||||
get mentionedMembers() {
|
||||
return this.mentions?.map((id) => cache.members.get(id)) || [];
|
||||
},
|
||||
|
||||
// METHODS
|
||||
delete(reason, delayMilliseconds) {
|
||||
return deleteMessageByID(
|
||||
this.channelID!,
|
||||
this.id!,
|
||||
reason,
|
||||
delayMilliseconds,
|
||||
);
|
||||
},
|
||||
edit(content) {
|
||||
return editMessage(this as Message, content);
|
||||
},
|
||||
pin() {
|
||||
return pin(this.channelID!, this.id!);
|
||||
},
|
||||
addReaction(reaction) {
|
||||
return addReaction(this.channelID!, this.id!, reaction);
|
||||
},
|
||||
addReactions(reactions, ordered) {
|
||||
return addReactions(this.channelID!, this.id!, reactions, ordered);
|
||||
},
|
||||
reply(content) {
|
||||
const contentWithMention = typeof content === "string"
|
||||
? { content, mentions: { repliedUser: true }, replyMessageID: this.id }
|
||||
: {
|
||||
...content,
|
||||
mentions: { ...(content.mentions || {}), repliedUser: true },
|
||||
replyMessageID: this.id,
|
||||
};
|
||||
|
||||
if (this.guildID) return sendMessage(this.channelID!, contentWithMention);
|
||||
return sendDirectMessage(this.author!.id, contentWithMention);
|
||||
},
|
||||
send(content) {
|
||||
if (this.guildID) return sendMessage(this.channelID!, content);
|
||||
return sendDirectMessage(this.author!.id, content);
|
||||
},
|
||||
alert(content, timeout = 10, reason = "") {
|
||||
if (this.guildID) {
|
||||
return sendMessage(this.channelID!, content).then((response) => {
|
||||
response.delete(reason, timeout * 1000).catch(console.error);
|
||||
});
|
||||
}
|
||||
|
||||
return sendDirectMessage(this.author!.id, content).then((response) => {
|
||||
response.delete(reason, timeout * 1000).catch(console.error);
|
||||
});
|
||||
},
|
||||
alertReply(content, timeout = 10, reason = "") {
|
||||
return this.reply!(content).then((response) =>
|
||||
response.delete(reason, timeout * 1000).catch(console.error)
|
||||
);
|
||||
},
|
||||
removeAllReactions() {
|
||||
return removeAllReactions(this.channelID!, this.id!);
|
||||
},
|
||||
removeReactionEmoji(reaction) {
|
||||
return removeReactionEmoji(this.channelID!, this.id!, reaction);
|
||||
},
|
||||
removeReaction(reaction) {
|
||||
return removeReaction(this.channelID!, this.id!, reaction);
|
||||
},
|
||||
};
|
||||
|
||||
export async function createMessage(data: MessageCreateOptions) {
|
||||
const {
|
||||
guild_id: guildID = "",
|
||||
channel_id: channelID,
|
||||
mentions_everyone: mentionsEveryone,
|
||||
mention_channels: mentionChannelIDs = [],
|
||||
mention_roles: mentionRoleIDs,
|
||||
webhook_id: webhookID,
|
||||
message_reference: messageReference,
|
||||
edited_timestamp: editedTimestamp,
|
||||
referenced_message: referencedMessageID,
|
||||
member,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const restProps: Record<string, ReturnType<typeof createNewProp>> = {};
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
// Discord doesnt give guild id for getMessage() so this will fill it in
|
||||
const guildIDFinal = guildID ||
|
||||
(await cacheHandlers.get("channels", channelID))?.guildID || "";
|
||||
|
||||
const message = Object.create(baseMessage, {
|
||||
...restProps,
|
||||
/** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */
|
||||
referencedMessageID: createNewProp(referencedMessageID),
|
||||
channelID: createNewProp(channelID),
|
||||
guildID: createNewProp(guildID || guildIDFinal),
|
||||
mentions: createNewProp(data.mentions.map((m) => m.id)),
|
||||
mentionsEveryone: createNewProp(mentionsEveryone),
|
||||
mentionRoleIDs: createNewProp(mentionRoleIDs),
|
||||
mentionChannelIDs: createNewProp(mentionChannelIDs.map((m) => m.id)),
|
||||
webhookID: createNewProp(webhookID),
|
||||
messageReference: createNewProp(messageReference),
|
||||
timestamp: createNewProp(Date.parse(data.timestamp)),
|
||||
editedTimestamp: createNewProp(
|
||||
editedTimestamp ? Date.parse(editedTimestamp) : undefined,
|
||||
),
|
||||
});
|
||||
|
||||
return message as Message;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
/** The id of the message */
|
||||
id: string;
|
||||
/** The id of the channel the message was sent in */
|
||||
channelID: string;
|
||||
/** The id of the guild the message was sent in */
|
||||
guildID: string;
|
||||
/** The author of this message (not guaranteed to be a valid user such as a webhook.) */
|
||||
author: UserPayload;
|
||||
/** The contents of the message */
|
||||
content: string;
|
||||
/** When this message was sent */
|
||||
timestamp: number;
|
||||
/** When this message was edited (if it was not edited, null) */
|
||||
editedTimestamp?: number;
|
||||
/** Whether this was a TextToSpeech message. */
|
||||
tts: boolean;
|
||||
/** Whether this message mentions everyone */
|
||||
mentionsEveryone: boolean;
|
||||
/** Users specifically mentioned in the message. */
|
||||
mentions: string[];
|
||||
/** Roles specifically mentioned in this message */
|
||||
mentionRoleIDs: string[];
|
||||
/** Channels specifically mentioned in this message */
|
||||
mentionChannelIDs: string[];
|
||||
/** Any attached files */
|
||||
attachments: Attachment[];
|
||||
/** Any embedded content */
|
||||
embeds: Embed[];
|
||||
/** Reactions to the message */
|
||||
reactions?: Reaction[];
|
||||
/** Used for validating a message was sent */
|
||||
nonce?: number | string;
|
||||
/** Whether this message is pinned */
|
||||
pinned: boolean;
|
||||
/** If the message is generated by a webhook, this is the webhooks id */
|
||||
"webhook_id"?: string;
|
||||
/** The type of message */
|
||||
type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
/** The activities sent with Rich Presence-related chat embeds. */
|
||||
activity?: Activity;
|
||||
/** Applications that sent with Rich Presence related chat embeds. */
|
||||
applications?: Application;
|
||||
/** The reference data sent with crossposted messages */
|
||||
messageReference?: Reference;
|
||||
/** The message flags combined like permission bits describe extra features of the message */
|
||||
flags?: 1 | 2 | 4 | 8 | 16;
|
||||
/** the stickers sent with the message (bots currently can only receive messages with stickers, not send) */
|
||||
stickers?: MessageSticker[];
|
||||
/** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */
|
||||
referencedMessageID?: MessageCreateOptions | null;
|
||||
|
||||
// GETTERS
|
||||
|
||||
/** The channel where this message was sent. Can be undefined if uncached. */
|
||||
channel?: Channel;
|
||||
/** The guild of this message. Can be undefined if not in cache or in DM */
|
||||
guild?: Guild;
|
||||
/** The member for the user who sent the message. Can be undefined if not in cache or in dm. */
|
||||
member?: Member;
|
||||
/** The guild member details for this guild and member. Can be undefined if not in cache or in dm. */
|
||||
guildMember?: GuildMember;
|
||||
/** The url link to this message */
|
||||
link: string;
|
||||
/** The role objects for all the roles that were mentioned in this message */
|
||||
mentionedRoles: (Role | undefined)[];
|
||||
/** The channel objects for all the channels that were mentioned in this message. */
|
||||
mentionedChannels: (Channel | undefined)[];
|
||||
/** The member objects for all the members that were mentioned in this message. */
|
||||
mentionedMembers: (Member | undefined)[];
|
||||
|
||||
// METHODS
|
||||
|
||||
/** Delete the message */
|
||||
delete(
|
||||
reason?: string,
|
||||
delayMilliseconds?: number,
|
||||
): ReturnType<typeof deleteMessageByID>;
|
||||
/** Edit the message */
|
||||
edit(content: string | MessageContent): ReturnType<typeof editMessage>;
|
||||
/** Pins the message in the channel */
|
||||
pin(): ReturnType<typeof pin>;
|
||||
/** Add a reaction to the message */
|
||||
addReaction(reaction: string): ReturnType<typeof addReaction>;
|
||||
/** Add multiple reactions to the message without or without order. */
|
||||
addReactions(
|
||||
reactions: string[],
|
||||
ordered?: boolean,
|
||||
): ReturnType<typeof addReactions>;
|
||||
/** Send a inline reply to this message */
|
||||
reply(content: string | MessageContent): ReturnType<typeof sendMessage>;
|
||||
/** Send a message to this channel where this message is */
|
||||
send(content: string | MessageContent): ReturnType<typeof sendMessage>;
|
||||
/** Send a message to this channel and then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
|
||||
alert(
|
||||
content: string | MessageContent,
|
||||
timeout?: number,
|
||||
reason?: string,
|
||||
): Promise<void>;
|
||||
/** Send a inline reply to this message but then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
|
||||
alertReply(
|
||||
content: string | MessageContent,
|
||||
timeout?: number,
|
||||
reason?: string,
|
||||
): Promise<unknown>;
|
||||
/** Remove all reactions */
|
||||
removeAllReactions(): ReturnType<typeof removeAllReactions>;
|
||||
/** Remove all reactions */
|
||||
removeReactionEmoji(reaction: string): ReturnType<typeof removeReactionEmoji>;
|
||||
/** Remove all reactions */
|
||||
removeReaction(reaction: string): ReturnType<typeof removeReaction>;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { createChannel } from "./channel.ts";
|
||||
import { createGuild } from "./guild.ts";
|
||||
import { createMember } from "./member.ts";
|
||||
import { createMessage } from "./message.ts";
|
||||
import { createRole } from "./role.ts";
|
||||
import { createTemplate } from "./template.ts";
|
||||
|
||||
/** This is the placeholder where the structure creation functions are kept. */
|
||||
export let structures = {
|
||||
createChannel,
|
||||
createGuild,
|
||||
createMember,
|
||||
createMessage,
|
||||
createRole,
|
||||
createTemplate,
|
||||
};
|
||||
|
||||
export type Structures = typeof structures;
|
||||
|
||||
/** This function is used to update/reload/customize the internal structures of Discordeno.
|
||||
*
|
||||
* ⚠️ **ADVANCED USE ONLY: If you customize this incorrectly, you could potentially create many new errors/bugs.
|
||||
* Please take caution when using this.**
|
||||
*/
|
||||
export function updateStructures(newStructures: Structures) {
|
||||
structures = {
|
||||
...structures,
|
||||
...newStructures,
|
||||
};
|
||||
}
|
||||
|
||||
export type { Channel } from "./channel.ts";
|
||||
export type { Guild } from "./guild.ts";
|
||||
export type { Member } from "./member.ts";
|
||||
export type { Message } from "./message.ts";
|
||||
export type { Role } from "./role.ts";
|
||||
export type { Template } from "./template.ts";
|
||||
@@ -1,130 +0,0 @@
|
||||
import { CreateRoleOptions, RoleData } from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import { deleteRole, editRole } from "../handlers/guild.ts";
|
||||
import { Guild } from "./guild.ts";
|
||||
import { Member } from "./member.ts";
|
||||
|
||||
const baseRole: Partial<Role> = {
|
||||
get guild() {
|
||||
return cache.guilds.find((g) => g.roles.has(this.id!));
|
||||
},
|
||||
get hexColor() {
|
||||
return this.color!.toString(16);
|
||||
},
|
||||
get members() {
|
||||
return cache.members.filter((m) =>
|
||||
m.guilds.some((g) => g.roles.includes(this.id!))
|
||||
);
|
||||
},
|
||||
get mention() {
|
||||
return `<@&${this.id}>`;
|
||||
},
|
||||
|
||||
// METHODS
|
||||
delete(guildID?: string) {
|
||||
// If not guild id was provided try and find one
|
||||
if (!guildID) guildID = guildID || this.guild?.id;
|
||||
// If a guild id is still not available error out
|
||||
if (!guildID) {
|
||||
throw new Error(
|
||||
"role.delete() did not find a valid guild in cache. Please provide the guildID like role.delete(guildID)",
|
||||
);
|
||||
}
|
||||
|
||||
return deleteRole(guildID, this.id!).catch(console.error);
|
||||
},
|
||||
edit(options: CreateRoleOptions, guildID?: string) {
|
||||
// If not guild id was provided try and find one
|
||||
if (!guildID) guildID = guildID || this.guild?.id;
|
||||
// If a guild id is still not available error out
|
||||
if (!guildID) {
|
||||
throw new Error(
|
||||
"role.edit() did not find a valid guild in cache. Please provide the guildID like role.edit({}, guildID)",
|
||||
);
|
||||
}
|
||||
|
||||
return editRole(guildID, this.id!, options);
|
||||
},
|
||||
higherThanRoleID(roleID: string, position?: number) {
|
||||
// If no position try and find one from cache
|
||||
if (!position) position = this.guild?.roles.get(roleID)?.position;
|
||||
// If still none error out.
|
||||
if (!position) {
|
||||
throw new Error(
|
||||
"role.higherThanRoleID() did not have a position provided and the role or guild was not found in cache. Please provide a position like role.higherThanRoleID(roleID, position)",
|
||||
);
|
||||
}
|
||||
|
||||
// Rare edge case handling
|
||||
if (this.position === position) {
|
||||
return this.id! < roleID;
|
||||
}
|
||||
|
||||
return this.position! > position;
|
||||
},
|
||||
};
|
||||
|
||||
// deno-lint-ignore require-await
|
||||
export async function createRole({ tags = {}, ...rest }: RoleData) {
|
||||
const restProps: Record<string, ReturnType<typeof createNewProp>> = {};
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
const role = Object.create(baseRole, {
|
||||
...restProps,
|
||||
botID: createNewProp(tags.bot_id),
|
||||
isNitroBoostRole: createNewProp("premium_subscriber" in tags),
|
||||
integrationID: createNewProp(tags.integration_id),
|
||||
});
|
||||
|
||||
return role as Role;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
/** role id */
|
||||
id: string;
|
||||
/** role name */
|
||||
name: string;
|
||||
/** integer representation of hexadecimal color code */
|
||||
color: number;
|
||||
/** if this role is pinned in the user listing */
|
||||
hoist: boolean;
|
||||
/** position of this role */
|
||||
position: number;
|
||||
/** permission bit set */
|
||||
permissions: string;
|
||||
/** whether this role is managed by an integration */
|
||||
managed: boolean;
|
||||
/** whether this role is mentionable */
|
||||
mentionable: boolean;
|
||||
/** The bot id that is associated with this role. */
|
||||
botID?: string;
|
||||
/** If this role is the nitro boost role. */
|
||||
isNitroBoostRole: boolean;
|
||||
/** The integration id that is associated with this role */
|
||||
integrationID: string;
|
||||
|
||||
// GETTERS
|
||||
|
||||
/** The guild where this role is. If undefined, the guild is not cached */
|
||||
guild?: Guild;
|
||||
/** The hex color for this role. */
|
||||
hexColor: string;
|
||||
/** The cached members that have this role */
|
||||
members: Collection<string, Member>;
|
||||
/** The @ mention of the role in a string. */
|
||||
mention: string;
|
||||
|
||||
// METHODS
|
||||
|
||||
/** Delete the role */
|
||||
delete(guildID?: string): ReturnType<typeof deleteRole>;
|
||||
/** Edits the role */
|
||||
edit(options: CreateRoleOptions): ReturnType<typeof editRole>;
|
||||
/** Checks if this role is higher than another role. */
|
||||
higherThanRoleID(roleID: string, position?: number): boolean;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { GuildTemplate, UserPayload } from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { createNewProp } from "../../util/utils.ts";
|
||||
import { Guild } from "./guild.ts";
|
||||
|
||||
const baseTemplate: Partial<Template> = {
|
||||
get sourceGuild() {
|
||||
// deno-lint-ignore getter-return
|
||||
if (!this.sourceGuildID) return;
|
||||
return cache.guilds.get(this.sourceGuildID);
|
||||
},
|
||||
};
|
||||
|
||||
export function createTemplate(
|
||||
data: GuildTemplate,
|
||||
) {
|
||||
const {
|
||||
usage_count: usageCount,
|
||||
creator_id: creatorID,
|
||||
created_at: createdAt,
|
||||
updated_at: updatedAt,
|
||||
source_guild_id: sourceGuildID,
|
||||
serialized_source_guild: serializedSourceGuild,
|
||||
is_dirty: isDirty,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
const restProps: Record<string, Partial<PropertyDescriptor>> = {};
|
||||
for (const key of Object.keys(rest)) {
|
||||
// @ts-ignore index signature
|
||||
restProps[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
return Object.create(baseTemplate, {
|
||||
...restProps,
|
||||
usageCount: createNewProp(sourceGuildID),
|
||||
creatorID: createNewProp(creatorID),
|
||||
createdAt: createNewProp(createdAt),
|
||||
updatedAt: createNewProp(updatedAt),
|
||||
sourceGuildID: createNewProp(sourceGuildID),
|
||||
serializedSourceGuild: createNewProp(serializedSourceGuild),
|
||||
isDirty: createNewProp(isDirty),
|
||||
}) as Template;
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
/** the template code (unique ID) */
|
||||
code: string;
|
||||
/** template name */
|
||||
name: string;
|
||||
/** the description for the template */
|
||||
description: string | null;
|
||||
/** number of times this template has been used */
|
||||
usageCount: number;
|
||||
/** the ID of the user who created the template */
|
||||
createdID: string;
|
||||
/** the user who created the template */
|
||||
creator: UserPayload;
|
||||
/** when this template was created */
|
||||
createdAt: string;
|
||||
/** when this template was last synced to the source guild */
|
||||
updatedAt: string;
|
||||
/** the ID of the guild this template is based on */
|
||||
sourceGuildID: string;
|
||||
/** the guild snapshot this template contains */
|
||||
serializedSourceGuild: Partial<Guild>;
|
||||
/** whether the template has unsynced changes */
|
||||
isDirty: boolean | null;
|
||||
|
||||
// GETTERS
|
||||
|
||||
sourceGuild: Guild | undefined;
|
||||
}
|
||||
67
src/bot.ts
67
src/bot.ts
@@ -1,11 +1,6 @@
|
||||
import { getGatewayBot } from "./api/handlers/gateway.ts";
|
||||
import {
|
||||
BotConfig,
|
||||
DiscordBotGatewayData,
|
||||
DiscordIdentify,
|
||||
EventHandlers,
|
||||
Intents,
|
||||
} from "./types/mod.ts";
|
||||
import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts";
|
||||
import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts";
|
||||
import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts";
|
||||
import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts";
|
||||
import { spawnShards } from "./ws/shard_manager.ts";
|
||||
|
||||
@@ -16,10 +11,11 @@ export let applicationID = "";
|
||||
|
||||
export let eventHandlers: EventHandlers = {};
|
||||
|
||||
export let botGatewayData: DiscordBotGatewayData;
|
||||
export let botGatewayData: DiscordGetGatewayBot;
|
||||
export let proxyWSURL = `wss://gateway.discord.gg`;
|
||||
export let lastShardId = 0;
|
||||
|
||||
export const identifyPayload: DiscordIdentify = {
|
||||
export const identifyPayload = {
|
||||
token: "",
|
||||
compress: true,
|
||||
properties: {
|
||||
@@ -31,19 +27,6 @@ export const identifyPayload: DiscordIdentify = {
|
||||
shard: [0, 0],
|
||||
};
|
||||
|
||||
/** @deprecated Use "DiscordIdentify" instead */
|
||||
export interface IdentifyPayload {
|
||||
token: string;
|
||||
compress: boolean;
|
||||
properties: {
|
||||
$os: string;
|
||||
$browser: string;
|
||||
$device: string;
|
||||
};
|
||||
intents: number;
|
||||
shard: [number, number];
|
||||
}
|
||||
|
||||
export async function startBot(config: BotConfig) {
|
||||
if (config.eventHandlers) eventHandlers = config.eventHandlers;
|
||||
authorization = `Bot ${config.token}`;
|
||||
@@ -57,12 +40,18 @@ export async function startBot(config: BotConfig) {
|
||||
proxyWSURL = botGatewayData.url;
|
||||
identifyPayload.token = config.token;
|
||||
identifyPayload.intents = config.intents.reduce(
|
||||
(bits, next) => (bits |= typeof next === "string" ? Intents[next] : next),
|
||||
(
|
||||
bits,
|
||||
next,
|
||||
) => (bits |= typeof next === "string"
|
||||
? DiscordGatewayIntents[next]
|
||||
: next),
|
||||
0,
|
||||
);
|
||||
identifyPayload.shard = [0, botGatewayData.shards];
|
||||
lastShardId = botGatewayData.shards;
|
||||
identifyPayload.shard = [0, lastShardId];
|
||||
|
||||
await spawnShards(botGatewayData, identifyPayload, 0, botGatewayData.shards);
|
||||
await spawnShards(botGatewayData, identifyPayload, 0, lastShardId);
|
||||
}
|
||||
|
||||
/** Allows you to dynamically update the event handlers by passing in new eventHandlers */
|
||||
@@ -73,14 +62,14 @@ export function updateEventHandlers(newEventHandlers: EventHandlers) {
|
||||
};
|
||||
}
|
||||
|
||||
/** INTERNAL LIB function used to set the bot ID once the READY event is sent by Discord. */
|
||||
export function setBotID(id: string) {
|
||||
if (botID !== id) botID = id;
|
||||
/** INTERNAL LIB function used to set the bot Id once the READY event is sent by Discord. */
|
||||
export function setBotId(id: string) {
|
||||
if (botId !== id) botId = id;
|
||||
}
|
||||
|
||||
/** INTERNAL LIB function used to set the application ID once the READY event is sent by Discord. */
|
||||
export function setApplicationID(id: string) {
|
||||
if (applicationID !== id) applicationID = id;
|
||||
/** INTERNAL LIB function used to set the application Id once the READY event is sent by Discord. */
|
||||
export function setApplicationId(id: string) {
|
||||
if (applicationId !== id) applicationId = id;
|
||||
}
|
||||
|
||||
// BIG BRAIN BOT STUFF ONLY BELOW THIS
|
||||
@@ -104,7 +93,12 @@ export async function startBigBrainBot(data: BigBrainBotConfig) {
|
||||
}
|
||||
|
||||
identifyPayload.intents = data.intents.reduce(
|
||||
(bits, next) => (bits |= typeof next === "string" ? Intents[next] : next),
|
||||
(
|
||||
bits,
|
||||
next,
|
||||
) => (bits |= typeof next === "string"
|
||||
? DiscordGatewayIntents[next]
|
||||
: next),
|
||||
0,
|
||||
);
|
||||
|
||||
@@ -125,6 +119,13 @@ export async function startBigBrainBot(data: BigBrainBotConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BotConfig {
|
||||
token: string;
|
||||
compress?: boolean;
|
||||
intents: (DiscordGatewayIntents | keyof typeof DiscordGatewayIntents)[];
|
||||
eventHandlers?: EventHandlers;
|
||||
}
|
||||
|
||||
export interface BigBrainBotConfig extends BotConfig {
|
||||
/** The first shard to start at for this worker. Use this to control which shards to run in each worker. */
|
||||
firstShardID: number;
|
||||
|
||||
@@ -1,138 +1,58 @@
|
||||
// deno-lint-ignore-file require-await no-explicit-any prefer-const
|
||||
|
||||
import { PresenceUpdatePayload } from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { Channel, Guild, Member, Message } from "../structures/mod.ts";
|
||||
import { Channel, Guild, Member, Message } from "./structures/mod.ts";
|
||||
import { Emoji } from "./types/emojis/emoji.ts";
|
||||
import { Collection } from "./util/collection.ts";
|
||||
|
||||
export type TableName =
|
||||
| "guilds"
|
||||
| "unavailableGuilds"
|
||||
| "channels"
|
||||
| "messages"
|
||||
| "members"
|
||||
| "presences";
|
||||
|
||||
function set(
|
||||
table: "guilds",
|
||||
key: string,
|
||||
value: Guild,
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function set(
|
||||
table: "channels",
|
||||
key: string,
|
||||
value: Channel,
|
||||
): Promise<Collection<string, Channel>>;
|
||||
function set(
|
||||
table: "messages",
|
||||
key: string,
|
||||
value: Message,
|
||||
): Promise<Collection<string, Message>>;
|
||||
function set(
|
||||
table: "members",
|
||||
key: string,
|
||||
value: Member,
|
||||
): Promise<Collection<string, Member>>;
|
||||
function set(
|
||||
table: "presences",
|
||||
key: string,
|
||||
value: PresenceUpdatePayload,
|
||||
): Promise<Collection<string, PresenceUpdatePayload>>;
|
||||
function set(
|
||||
table: "unavailableGuilds",
|
||||
key: string,
|
||||
value: number,
|
||||
): Promise<Collection<string, number>>;
|
||||
async function set(table: TableName, key: string, value: any) {
|
||||
return cache[table].set(key, value);
|
||||
}
|
||||
|
||||
function get(table: "guilds", key: string): Promise<Guild | undefined>;
|
||||
function get(table: "channels", key: string): Promise<Channel | undefined>;
|
||||
function get(table: "messages", key: string): Promise<Message | undefined>;
|
||||
function get(table: "members", key: string): Promise<Member | undefined>;
|
||||
function get(
|
||||
table: "presences",
|
||||
key: string,
|
||||
): Promise<PresenceUpdatePayload | undefined>;
|
||||
function get(
|
||||
table: "unavailableGuilds",
|
||||
key: string,
|
||||
): Promise<Guild | undefined>;
|
||||
async function get(table: TableName, key: string) {
|
||||
return cache[table].get(key);
|
||||
}
|
||||
|
||||
function forEach(
|
||||
table: "guilds",
|
||||
callback: (value: Guild, key: string, map: Map<string, Guild>) => unknown,
|
||||
): void;
|
||||
function forEach(
|
||||
table: "unavailableGuilds",
|
||||
callback: (value: Guild, key: string, map: Map<string, Guild>) => unknown,
|
||||
): void;
|
||||
function forEach(
|
||||
table: "channels",
|
||||
callback: (value: Channel, key: string, map: Map<string, Channel>) => unknown,
|
||||
): void;
|
||||
function forEach(
|
||||
table: "messages",
|
||||
callback: (value: Message, key: string, map: Map<string, Message>) => unknown,
|
||||
): void;
|
||||
function forEach(
|
||||
table: "members",
|
||||
callback: (value: Member, key: string, map: Map<string, Member>) => unknown,
|
||||
): void;
|
||||
function forEach(
|
||||
table: TableName,
|
||||
callback: (value: any, key: string, map: Map<string, any>) => unknown,
|
||||
) {
|
||||
return cache[table].forEach(callback);
|
||||
}
|
||||
|
||||
function filter(
|
||||
table: "guilds",
|
||||
callback: (value: Guild, key: string) => boolean,
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function filter(
|
||||
table: "unavailableGuilds",
|
||||
callback: (value: Guild, key: string) => boolean,
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function filter(
|
||||
table: "channels",
|
||||
callback: (value: Channel, key: string) => boolean,
|
||||
): Promise<Collection<string, Channel>>;
|
||||
function filter(
|
||||
table: "messages",
|
||||
callback: (value: Message, key: string) => boolean,
|
||||
): Promise<Collection<string, Message>>;
|
||||
function filter(
|
||||
table: "members",
|
||||
callback: (value: Member, key: string) => boolean,
|
||||
): Promise<Collection<string, Member>>;
|
||||
async function filter(
|
||||
table: TableName,
|
||||
callback: (value: any, key: string) => boolean,
|
||||
) {
|
||||
return cache[table].filter(callback);
|
||||
}
|
||||
export const cache = {
|
||||
isReady: false,
|
||||
/** All of the guild objects the bot has access to, mapped by their Ids */
|
||||
guilds: new Collection<string, Guild>(),
|
||||
/** All of the channel objects the bot has access to, mapped by their Ids */
|
||||
channels: new Collection<string, Channel>(),
|
||||
/** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */
|
||||
messages: new Collection<string, Message>(),
|
||||
/** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */
|
||||
members: new Collection<string, Member>(),
|
||||
/** All of the unavailable guilds, mapped by their Ids (id, timestamp) */
|
||||
unavailableGuilds: new Collection<string, number>(),
|
||||
/** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */
|
||||
presences: new Collection<string, Presence>(),
|
||||
fetchAllMembersProcessingRequests: new Collection<
|
||||
string,
|
||||
(
|
||||
value:
|
||||
| Collection<string, Member>
|
||||
| PromiseLike<Collection<string, Member>>
|
||||
) => void
|
||||
>(),
|
||||
executedSlashCommands: new Collection<string, string>(),
|
||||
get emojis() {
|
||||
return new Collection<string, Emoji>(
|
||||
this.guilds.reduce(
|
||||
(a, b) => [...a, ...b.emojis.map((e) => [e.id, e])],
|
||||
[] as any[]
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export let cacheHandlers = {
|
||||
/** Deletes all items from the cache */
|
||||
clear: async function (table: TableName) {
|
||||
async clear(table: TableName) {
|
||||
return cache[table].clear();
|
||||
},
|
||||
/** Deletes 1 item from cache using the key */
|
||||
delete: async function (table: TableName, key: string) {
|
||||
async delete(table: TableName, key: string) {
|
||||
return cache[table].delete(key);
|
||||
},
|
||||
/** Check if something exists in cache with a key */
|
||||
has: async function (table: TableName, key: string) {
|
||||
async has(table: TableName, key: string) {
|
||||
return cache[table].has(key);
|
||||
},
|
||||
|
||||
/** Get the number of key-value pairs */
|
||||
size: async (table: TableName) => {
|
||||
async size(table: TableName) {
|
||||
return cache[table].size;
|
||||
},
|
||||
|
||||
@@ -146,3 +66,115 @@ export let cacheHandlers = {
|
||||
/** Allows you to filter our all items in this cache. */
|
||||
filter,
|
||||
};
|
||||
|
||||
export type TableName =
|
||||
| "guilds"
|
||||
| "unavailableGuilds"
|
||||
| "channels"
|
||||
| "messages"
|
||||
| "members"
|
||||
| "presences";
|
||||
|
||||
function set(
|
||||
table: "guilds",
|
||||
key: string,
|
||||
value: Guild
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function set(
|
||||
table: "channels",
|
||||
key: string,
|
||||
value: Channel
|
||||
): Promise<Collection<string, Channel>>;
|
||||
function set(
|
||||
table: "messages",
|
||||
key: string,
|
||||
value: Message
|
||||
): Promise<Collection<string, Message>>;
|
||||
function set(
|
||||
table: "members",
|
||||
key: string,
|
||||
value: Member
|
||||
): Promise<Collection<string, Member>>;
|
||||
function set(
|
||||
table: "presences",
|
||||
key: string,
|
||||
value: PresenceUpdatePayload
|
||||
): Promise<Collection<string, PresenceUpdatePayload>>;
|
||||
function set(
|
||||
table: "unavailableGuilds",
|
||||
key: string,
|
||||
value: number
|
||||
): Promise<Collection<string, number>>;
|
||||
async function set(table: TableName, key: string, value: any) {
|
||||
return cache[table].set(key, value);
|
||||
}
|
||||
|
||||
function get(table: "guilds", key: string): Promise<Guild | undefined>;
|
||||
function get(table: "channels", key: string): Promise<Channel | undefined>;
|
||||
function get(table: "messages", key: string): Promise<Message | undefined>;
|
||||
function get(table: "members", key: string): Promise<Member | undefined>;
|
||||
function get(
|
||||
table: "presences",
|
||||
key: string
|
||||
): Promise<PresenceUpdatePayload | undefined>;
|
||||
function get(
|
||||
table: "unavailableGuilds",
|
||||
key: string
|
||||
): Promise<Guild | undefined>;
|
||||
async function get(table: TableName, key: string) {
|
||||
return cache[table].get(key);
|
||||
}
|
||||
|
||||
function forEach(
|
||||
table: "guilds",
|
||||
callback: (value: Guild, key: string, map: Map<string, Guild>) => unknown
|
||||
): void;
|
||||
function forEach(
|
||||
table: "unavailableGuilds",
|
||||
callback: (value: Guild, key: string, map: Map<string, Guild>) => unknown
|
||||
): void;
|
||||
function forEach(
|
||||
table: "channels",
|
||||
callback: (value: Channel, key: string, map: Map<string, Channel>) => unknown
|
||||
): void;
|
||||
function forEach(
|
||||
table: "messages",
|
||||
callback: (value: Message, key: string, map: Map<string, Message>) => unknown
|
||||
): void;
|
||||
function forEach(
|
||||
table: "members",
|
||||
callback: (value: Member, key: string, map: Map<string, Member>) => unknown
|
||||
): void;
|
||||
function forEach(
|
||||
table: TableName,
|
||||
callback: (value: any, key: string, map: Map<string, any>) => unknown
|
||||
) {
|
||||
return cache[table].forEach(callback);
|
||||
}
|
||||
|
||||
function filter(
|
||||
table: "guilds",
|
||||
callback: (value: Guild, key: string) => boolean
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function filter(
|
||||
table: "unavailableGuilds",
|
||||
callback: (value: Guild, key: string) => boolean
|
||||
): Promise<Collection<string, Guild>>;
|
||||
function filter(
|
||||
table: "channels",
|
||||
callback: (value: Channel, key: string) => boolean
|
||||
): Promise<Collection<string, Channel>>;
|
||||
function filter(
|
||||
table: "messages",
|
||||
callback: (value: Message, key: string) => boolean
|
||||
): Promise<Collection<string, Message>>;
|
||||
function filter(
|
||||
table: "members",
|
||||
callback: (value: Member, key: string) => boolean
|
||||
): Promise<Collection<string, Member>>;
|
||||
async function filter(
|
||||
table: TableName,
|
||||
callback: (value: any, key: string) => boolean
|
||||
) {
|
||||
return cache[table].filter(callback);
|
||||
}
|
||||
14
src/handlers/channels/CHANNEL_CREATE.ts
Normal file
14
src/handlers/channels/CHANNEL_CREATE.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleChannelCreate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordChannel;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(payload);
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
eventHandlers.channelCreate?.(channelStruct);
|
||||
}
|
||||
40
src/handlers/channels/CHANNEL_DELETE.ts
Normal file
40
src/handlers/channels/CHANNEL_DELETE.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { DiscordChannelTypes } from "../../types/channels/channel_types.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleChannelDelete(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordChannel;
|
||||
|
||||
const cachedChannel = await cacheHandlers.get("channels", payload.id);
|
||||
if (!cachedChannel) return;
|
||||
|
||||
if (
|
||||
cachedChannel.type === DiscordChannelTypes.GUILD_VOICE && payload.guild_id
|
||||
) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
|
||||
if (guild) {
|
||||
return Promise.all(guild.voiceStates.map(async (vs, key) => {
|
||||
if (vs.channelId !== payload.id) return;
|
||||
|
||||
// Since this channel was deleted all voice states for this channel should be deleted
|
||||
guild.voiceStates.delete(key);
|
||||
|
||||
const member = await cacheHandlers.get("members", vs.userId);
|
||||
if (!member) return;
|
||||
|
||||
eventHandlers.voiceChannelLeave?.(member, vs.channelId);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
await cacheHandlers.delete("channels", payload.id);
|
||||
cacheHandlers.forEach("messages", (message) => {
|
||||
if (message.channelId === payload.id) {
|
||||
cacheHandlers.delete("messages", message.id);
|
||||
}
|
||||
});
|
||||
eventHandlers.channelDelete?.(cachedChannel);
|
||||
}
|
||||
17
src/handlers/channels/CHANNEL_PINS_UPDATE.ts
Normal file
17
src/handlers/channels/CHANNEL_PINS_UPDATE.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordChannelPinsUpdate } from "../../types/channels/channel_pins_update.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleChannelPinsUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordChannelPinsUpdate;
|
||||
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
const guild = payload.guild_id
|
||||
? await cacheHandlers.get("guilds", payload.guild_id)
|
||||
: undefined;
|
||||
|
||||
eventHandlers.channelPinsUpdate?.(channel, guild, payload.last_pin_timestamp);
|
||||
}
|
||||
17
src/handlers/channels/CHANNEL_UPDATE.ts
Normal file
17
src/handlers/channels/CHANNEL_UPDATE.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleChannelUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordChannel;
|
||||
const cachedChannel = await cacheHandlers.get("channels", payload.id);
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(payload);
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
if (!cachedChannel) return;
|
||||
|
||||
eventHandlers.channelUpdate?.(channelStruct, cachedChannel);
|
||||
}
|
||||
18
src/handlers/commands/APPLICATION_COMMAND_CREATE.ts
Normal file
18
src/handlers/commands/APPLICATION_COMMAND_CREATE.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleApplicationCommandCreate(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const {
|
||||
guild_id: guildId,
|
||||
application_id: applicationId,
|
||||
...rest
|
||||
} = data.d as DiscordApplicationCommandCreateUpdateDelete;
|
||||
|
||||
eventHandlers.applicationCommandCreate?.({
|
||||
...rest,
|
||||
guildId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
16
src/handlers/commands/APPLICATION_COMMAND_DELETE.ts
Normal file
16
src/handlers/commands/APPLICATION_COMMAND_DELETE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleApplicationCommandDelete(data: DiscordGatewayPayload) {
|
||||
const {
|
||||
application_id: applicationId,
|
||||
guild_id: guildId,
|
||||
...rest
|
||||
} = data.d as DiscordApplicationCommandCreateUpdateDelete;
|
||||
|
||||
eventHandlers.applicationCommandDelete?.({
|
||||
...rest,
|
||||
guildId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
16
src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts
Normal file
16
src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleApplicationCommandUpdate(data: DiscordGatewayPayload) {
|
||||
const {
|
||||
application_id: applicationId,
|
||||
guild_id: guildId,
|
||||
...rest
|
||||
} = data.d as DiscordApplicationCommandCreateUpdateDelete;
|
||||
|
||||
eventHandlers.applicationCommandUpdate?.({
|
||||
...rest,
|
||||
guildId,
|
||||
applicationId,
|
||||
});
|
||||
}
|
||||
24
src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts
Normal file
24
src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildEmojisUpdate } from "../../types/emojis/guild_emojis_update.ts";
|
||||
|
||||
export async function handleGuildEmojisUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildEmojisUpdate;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedEmojis = guild.emojis;
|
||||
guild.emojis = new Collection(
|
||||
payload.emojis.map((emoji) => [emoji.id ?? emoji.name, emoji]),
|
||||
);
|
||||
|
||||
cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
eventHandlers.guildEmojisUpdate?.(
|
||||
guild,
|
||||
guild.emojis,
|
||||
cachedEmojis,
|
||||
);
|
||||
}
|
||||
12
src/handlers/guilds/GUILD_BAN_ADD.ts
Normal file
12
src/handlers/guilds/GUILD_BAN_ADD.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleGuildBanAdd(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildBanAddRemove;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildBanAdd?.(guild, payload.user, member);
|
||||
}
|
||||
12
src/handlers/guilds/GUILD_BAN_REMOVE.ts
Normal file
12
src/handlers/guilds/GUILD_BAN_REMOVE.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleGuildBanRemove(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildBanAddRemove;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildBanRemove?.(guild, payload.user, member);
|
||||
}
|
||||
34
src/handlers/guilds/GUILD_CREATE.ts
Normal file
34
src/handlers/guilds/GUILD_CREATE.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cache, cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { basicShards } from "../../ws/shard.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuild } from "../../types/guilds/guild.ts";
|
||||
|
||||
export async function handleGuildCreate(
|
||||
data: DiscordGatewayPayload,
|
||||
shardId: number,
|
||||
) {
|
||||
const payload = data.d as DiscordGuild;
|
||||
// When shards resume they emit GUILD_CREATE again.
|
||||
if (await cacheHandlers.has("guilds", payload.id)) return;
|
||||
|
||||
const guildStruct = await structures.createGuildStruct(
|
||||
data.d,
|
||||
shardId,
|
||||
);
|
||||
await cacheHandlers.set("guilds", guildStruct.id, guildStruct);
|
||||
|
||||
const shard = basicShards.get(shardId);
|
||||
|
||||
if (shard?.unavailableGuildIds.has(payload.id)) {
|
||||
await cacheHandlers.delete("unavailableGuilds", payload.id);
|
||||
|
||||
shard.unavailableGuildIds.delete(payload.id);
|
||||
|
||||
return eventHandlers.guildAvailable?.(guildStruct);
|
||||
}
|
||||
|
||||
if (!cache.isReady) return eventHandlers.guildLoaded?.(guildStruct);
|
||||
eventHandlers.guildCreate?.(guildStruct);
|
||||
}
|
||||
52
src/handlers/guilds/GUILD_DELETE.ts
Normal file
52
src/handlers/guilds/GUILD_DELETE.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { basicShards } from "../../ws/shard.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordUnavailableGuild } from "../../types/guilds/unavailable_guild.ts";
|
||||
|
||||
export async function handleGuildDelete(
|
||||
data: DiscordGatewayPayload,
|
||||
shardId: number,
|
||||
) {
|
||||
const payload = data.d as DiscordUnavailableGuild;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.id);
|
||||
if (!guild) return;
|
||||
|
||||
await cacheHandlers.delete("guilds", payload.id);
|
||||
|
||||
if (payload.unavailable) {
|
||||
const shard = basicShards.get(shardId);
|
||||
if (shard) shard.unavailableGuildIds.add(payload.id);
|
||||
|
||||
await cacheHandlers.set("unavailableGuilds", payload.id, Date.now());
|
||||
|
||||
eventHandlers.guildUnavailable?.(guild);
|
||||
} else {
|
||||
eventHandlers.guildDelete?.(guild);
|
||||
}
|
||||
|
||||
cacheHandlers.forEach("messages", (message) => {
|
||||
if (message.guildId === payload.id) {
|
||||
cacheHandlers.delete("messages", message.id);
|
||||
}
|
||||
});
|
||||
|
||||
cacheHandlers.forEach("channels", (channel) => {
|
||||
if (channel.guildId === payload.id) {
|
||||
cacheHandlers.delete("channels", channel.id);
|
||||
}
|
||||
});
|
||||
|
||||
cacheHandlers.forEach("members", (member) => {
|
||||
if (!member.guilds.has(payload.id)) return;
|
||||
|
||||
member.guilds.delete(payload.id);
|
||||
|
||||
if (!member.guilds.size) {
|
||||
return cacheHandlers.delete("members", member.id);
|
||||
}
|
||||
|
||||
cacheHandlers.set("members", member.id, member);
|
||||
});
|
||||
}
|
||||
15
src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts
Normal file
15
src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildIntegrationsUpdate } from "../../types/guilds/guild_integrations_update.ts";
|
||||
|
||||
export async function handleGuildIntegrationsUpdate(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const payload = data.d as DiscordGuildIntegrationsUpdate;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
eventHandlers.guildIntegrationsUpdate?.(guild);
|
||||
}
|
||||
45
src/handlers/guilds/GUILD_UPDATE.ts
Normal file
45
src/handlers/guilds/GUILD_UPDATE.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuild } from "../../types/guilds/guild.ts";
|
||||
|
||||
export async function handleGuildUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuild;
|
||||
const cachedGuild = await cacheHandlers.get("guilds", payload.id);
|
||||
if (!cachedGuild) return;
|
||||
|
||||
const keysToSkip = [
|
||||
"roles",
|
||||
"guild_hashes",
|
||||
"guild_id",
|
||||
"max_members",
|
||||
"emojis",
|
||||
];
|
||||
|
||||
const changes = Object.entries(payload)
|
||||
.map(([key, value]) => {
|
||||
if (keysToSkip.includes(key)) return;
|
||||
|
||||
// @ts-ignore index signature
|
||||
const cachedValue = cachedGuild[key];
|
||||
if (cachedValue !== value) {
|
||||
// Guild create sends undefined and update sends false.
|
||||
if (!cachedValue && !value) return;
|
||||
|
||||
if (Array.isArray(cachedValue) && Array.isArray(value)) {
|
||||
const different = (cachedValue.length !== value.length) ||
|
||||
cachedValue.find((val) => !value.includes(val)) ||
|
||||
value.find((val) => !cachedValue.includes(val));
|
||||
if (!different) return;
|
||||
}
|
||||
|
||||
// @ts-ignore index signature
|
||||
cachedGuild[key] = value;
|
||||
return { key, oldValue: cachedValue, value };
|
||||
}
|
||||
}).filter((change) => change) as GuildUpdateChange[];
|
||||
|
||||
await cacheHandlers.set("guilds", payload.id, cachedGuild);
|
||||
|
||||
eventHandlers.guildUpdate?.(cachedGuild, changes);
|
||||
}
|
||||
28
src/handlers/integrations/INTEGRATION_CREATE.ts
Normal file
28
src/handlers/integrations/INTEGRATION_CREATE.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleIntegrationCreate(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const {
|
||||
guild_id: guildId,
|
||||
enable_emoticons: enableEmoticons,
|
||||
expire_behavior: expireBehavior,
|
||||
expire_grace_period: expireGracePeriod,
|
||||
subscriber_count: subscriberCount,
|
||||
role_id: roleId,
|
||||
synced_at: syncedAt,
|
||||
...rest
|
||||
} = data.d as IntegrationCreateUpdateEvent;
|
||||
|
||||
eventHandlers.integrationCreate?.({
|
||||
...rest,
|
||||
guildId,
|
||||
enableEmoticons,
|
||||
expireBehavior,
|
||||
expireGracePeriod,
|
||||
syncedAt,
|
||||
subscriberCount,
|
||||
roleId,
|
||||
});
|
||||
}
|
||||
16
src/handlers/integrations/INTEGRATION_DELETE.ts
Normal file
16
src/handlers/integrations/INTEGRATION_DELETE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleIntegrationDelete(data: DiscordGatewayPayload) {
|
||||
const {
|
||||
guild_id: guildId,
|
||||
application_id: applicationId,
|
||||
...rest
|
||||
} = data.d as IntegrationDeleteEvent;
|
||||
|
||||
eventHandlers.integrationDelete?.({
|
||||
...rest,
|
||||
applicationId,
|
||||
guildId,
|
||||
});
|
||||
}
|
||||
26
src/handlers/integrations/INTEGRATION_UPDATE.ts
Normal file
26
src/handlers/integrations/INTEGRATION_UPDATE.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleIntegrationUpdate(data: DiscordGatewayPayload) {
|
||||
const {
|
||||
enable_emoticons: enableEmoticons,
|
||||
expire_behavior: expireBehavior,
|
||||
expire_grace_period: expireGracePeriod,
|
||||
role_id: roleId,
|
||||
subscriber_count: subscriberCount,
|
||||
synced_at: syncedAt,
|
||||
guild_id: guildId,
|
||||
...rest
|
||||
} = data.d as IntegrationCreateUpdateEvent;
|
||||
|
||||
eventHandlers.integrationUpdate?.({
|
||||
...rest,
|
||||
guildId,
|
||||
subscriberCount,
|
||||
enableEmoticons,
|
||||
expireGracePeriod,
|
||||
roleId,
|
||||
expireBehavior,
|
||||
syncedAt,
|
||||
});
|
||||
}
|
||||
20
src/handlers/interactions/INTERACTION_CREATE.ts
Normal file
20
src/handlers/interactions/INTERACTION_CREATE.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleInteractionCreate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as InteractionCommandPayload;
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
payload.member,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
eventHandlers.interactionCreate?.(
|
||||
{
|
||||
...payload,
|
||||
member: memberStruct,
|
||||
},
|
||||
);
|
||||
}
|
||||
28
src/handlers/invites/INVITE_CREATE.ts
Normal file
28
src/handlers/invites/INVITE_CREATE.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordInviteCreate } from "../../types/invites/invite_create.ts";
|
||||
|
||||
export function handleInviteCreate(payload: DiscordGatewayPayload) {
|
||||
// TODO: replace with tocamelcase
|
||||
const {
|
||||
channel_id: channelId,
|
||||
created_at: createdAt,
|
||||
max_age: maxAge,
|
||||
guild_id: guildId,
|
||||
target_user: targetUser,
|
||||
target_user_type: targetUserType,
|
||||
max_uses: maxUses,
|
||||
...rest
|
||||
} = payload.d as DiscordInviteCreate;
|
||||
|
||||
eventHandlers.inviteCreate?.({
|
||||
...rest,
|
||||
channelId,
|
||||
guildId,
|
||||
maxAge,
|
||||
targetUser,
|
||||
targetUserType,
|
||||
maxUses,
|
||||
createdAt,
|
||||
});
|
||||
}
|
||||
17
src/handlers/invites/INVITE_DELETE.ts
Normal file
17
src/handlers/invites/INVITE_DELETE.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordInviteDelete } from "../../types/invites/invite_delete.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export function handleInviteDelete(payload: DiscordGatewayPayload) {
|
||||
const {
|
||||
channel_id: channelId,
|
||||
guild_id: guildId,
|
||||
...rest
|
||||
} = payload.d as DiscordInviteDelete;
|
||||
|
||||
eventHandlers.inviteDelete?.({
|
||||
...rest,
|
||||
channelId,
|
||||
guildId,
|
||||
});
|
||||
}
|
||||
44
src/handlers/members/GUILD_MEMBERS_CHUNK.ts
Normal file
44
src/handlers/members/GUILD_MEMBERS_CHUNK.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { cache, cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildMembersChunk } from "../../types/members/guild_members_chunk.ts";
|
||||
|
||||
export async function handleGuildMembersChunk(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildMembersChunk;
|
||||
|
||||
const members = await Promise.all(
|
||||
payload.members.map(async (member) => {
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
member,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
return memberStruct;
|
||||
}),
|
||||
);
|
||||
|
||||
// Check if its necessary to resolve the fetchmembers promise for this chunk or if more chunks will be coming
|
||||
if (
|
||||
payload.nonce
|
||||
) {
|
||||
const resolve = cache.fetchAllMembersProcessingRequests.get(payload.nonce);
|
||||
if (!resolve) return;
|
||||
|
||||
if (payload.chunk_index + 1 === payload.chunk_count) {
|
||||
cache.fetchAllMembersProcessingRequests.delete(payload.nonce);
|
||||
// Only 1 chunk most likely is all members or users only request a small amount of users
|
||||
if (payload.chunk_count === 1) {
|
||||
return resolve(new Collection(members.map((m) => [m.id, m])));
|
||||
}
|
||||
|
||||
return resolve(
|
||||
await cacheHandlers.filter(
|
||||
"members",
|
||||
(m) => m.guilds.has(payload.guild_id),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/handlers/members/GUILD_MEMBER_ADD.ts
Normal file
20
src/handlers/members/GUILD_MEMBER_ADD.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildMemberAdd } from "../../types/members/guild_member_add.ts";
|
||||
|
||||
export async function handleGuildMemberAdd(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildMemberAdd;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
guild.memberCount++;
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
payload,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
eventHandlers.guildMemberAdd?.(guild, memberStruct);
|
||||
}
|
||||
19
src/handlers/members/GUILD_MEMBER_REMOVE.ts
Normal file
19
src/handlers/members/GUILD_MEMBER_REMOVE.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildMemberRemove } from "../../types/members/guild_member_remove.ts";
|
||||
|
||||
export async function handleGuildMemberRemove(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildMemberRemove;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
guild.memberCount--;
|
||||
const member = await cacheHandlers.get("members", payload.user.id);
|
||||
eventHandlers.guildMemberRemove?.(guild, payload.user, member);
|
||||
|
||||
member?.guilds.delete(guild.id);
|
||||
if (member && !member.guilds.size) {
|
||||
await cacheHandlers.delete("members", member.id);
|
||||
}
|
||||
}
|
||||
62
src/handlers/members/GUILD_MEMBER_UPDATE.ts
Normal file
62
src/handlers/members/GUILD_MEMBER_UPDATE.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildMemberUpdate } from "../../types/members/guild_member_update.ts";
|
||||
|
||||
export async function handleGuildMemberUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildMemberUpdate;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedMember = await cacheHandlers.get("members", payload.user.id);
|
||||
const guildMember = cachedMember?.guilds.get(payload.guild_id);
|
||||
|
||||
const newMemberData = {
|
||||
...payload,
|
||||
// deno-lint-ignore camelcase
|
||||
premium_since: payload.premium_since || undefined,
|
||||
// deno-lint-ignore camelcase
|
||||
joined_at: new Date(guildMember?.joinedAt || Date.now())
|
||||
.toISOString(),
|
||||
deaf: guildMember?.deaf || false,
|
||||
mute: guildMember?.mute || false,
|
||||
roles: payload.roles,
|
||||
};
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
newMemberData,
|
||||
payload.guild_id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
|
||||
if (guildMember) {
|
||||
if (guildMember.nick !== payload.nick) {
|
||||
eventHandlers.nicknameUpdate?.(
|
||||
guild,
|
||||
memberStruct,
|
||||
payload.nick!,
|
||||
guildMember.nick,
|
||||
);
|
||||
}
|
||||
|
||||
if (payload.pending === false && guildMember.pending === true) {
|
||||
eventHandlers.membershipScreeningPassed?.(guild, memberStruct);
|
||||
}
|
||||
|
||||
const roleIds = guildMember.roles || [];
|
||||
|
||||
roleIds.forEach((id) => {
|
||||
if (!payload.roles.includes(id)) {
|
||||
eventHandlers.roleLost?.(guild, memberStruct, id);
|
||||
}
|
||||
});
|
||||
|
||||
payload.roles.forEach((id) => {
|
||||
if (!roleIds.includes(id)) {
|
||||
eventHandlers.roleGained?.(guild, memberStruct, id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
eventHandlers.guildMemberUpdate?.(guild, memberStruct, cachedMember);
|
||||
}
|
||||
42
src/handlers/messages/MESSAGE_CREATE.ts
Normal file
42
src/handlers/messages/MESSAGE_CREATE.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
|
||||
export async function handleMessageCreate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordMessage;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (channel) channel.lastMessageId = payload.id;
|
||||
|
||||
const guild = payload.guild_id
|
||||
? await cacheHandlers.get("guilds", payload.guild_id)
|
||||
: undefined;
|
||||
|
||||
if (payload.member && guild) {
|
||||
// If in a guild cache the author as a member
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
{ ...payload.member, user: payload.author },
|
||||
guild.id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
|
||||
await Promise.all(payload.mentions.map(async (mention) => {
|
||||
// Cache the member if its a valid member
|
||||
if (mention.member && guild) {
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
{ ...mention.member, user: mention },
|
||||
guild.id,
|
||||
);
|
||||
|
||||
return cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}));
|
||||
|
||||
const message = await structures.createMessageStruct(payload);
|
||||
// Cache the message
|
||||
await cacheHandlers.set("messages", payload.id, message);
|
||||
|
||||
eventHandlers.messageCreate?.(message);
|
||||
}
|
||||
17
src/handlers/messages/MESSAGE_DELETE.ts
Normal file
17
src/handlers/messages/MESSAGE_DELETE.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageDelete } from "../../types/messages/message_delete.ts";
|
||||
|
||||
export async function handleMessageDelete(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordMessageDelete;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
eventHandlers.messageDelete?.(
|
||||
{ id: payload.id, channel },
|
||||
await cacheHandlers.get("messages", payload.id),
|
||||
);
|
||||
|
||||
await cacheHandlers.delete("messages", payload.id);
|
||||
}
|
||||
18
src/handlers/messages/MESSAGE_DELETE_BULK.ts
Normal file
18
src/handlers/messages/MESSAGE_DELETE_BULK.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageDeleteBulk } from "../../types/messages/message_delete_bulk.ts";
|
||||
|
||||
export async function handleMessageDeleteBulk(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordMessageDeleteBulk;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
return Promise.all(payload.ids.map(async (id) => {
|
||||
eventHandlers.messageDelete?.(
|
||||
{ id, channel },
|
||||
await cacheHandlers.get("messages", id),
|
||||
);
|
||||
await cacheHandlers.delete("messages", id);
|
||||
}));
|
||||
}
|
||||
57
src/handlers/messages/MESSAGE_REACTION_ADD.ts
Normal file
57
src/handlers/messages/MESSAGE_REACTION_ADD.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { botId, eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageReactionAdd } from "../../types/messages/message_reaction_add.ts";
|
||||
|
||||
export async function handleMessageReactionAdd(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordMessageReactionAdd;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message) {
|
||||
const reactionExisted = message.reactions?.find(
|
||||
(reaction) =>
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name,
|
||||
);
|
||||
|
||||
if (reactionExisted) reactionExisted.count++;
|
||||
else {
|
||||
const newReaction = {
|
||||
count: 1,
|
||||
me: payload.user_id === botId,
|
||||
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
|
||||
};
|
||||
message.reactions = message.reactions
|
||||
? [...message.reactions, newReaction]
|
||||
: [newReaction];
|
||||
}
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
if (payload.member && payload.guild_id) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (guild) {
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
payload.member,
|
||||
guild.id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}
|
||||
|
||||
const uncachedOptions = {
|
||||
...payload,
|
||||
id: payload.message_id,
|
||||
channelId: payload.channel_id,
|
||||
guildId: payload.guild_id || "",
|
||||
};
|
||||
|
||||
eventHandlers.reactionAdd?.(
|
||||
uncachedOptions,
|
||||
payload.emoji,
|
||||
payload.user_id,
|
||||
message,
|
||||
);
|
||||
}
|
||||
59
src/handlers/messages/MESSAGE_REACTION_REMOVE.ts
Normal file
59
src/handlers/messages/MESSAGE_REACTION_REMOVE.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { botId, eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageReactionRemove } from "../../types/messages/message_reaction_remove.ts";
|
||||
|
||||
export async function handleMessageReactionRemove(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const payload = data.d as DiscordMessageReactionRemove;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message) {
|
||||
const reactionExisted = message.reactions?.find(
|
||||
(reaction) =>
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name,
|
||||
);
|
||||
|
||||
if (reactionExisted) reactionExisted.count--;
|
||||
else {
|
||||
const newReaction = {
|
||||
count: 1,
|
||||
me: payload.user_id === botId,
|
||||
emoji: { ...payload.emoji, id: payload.emoji.id || undefined },
|
||||
};
|
||||
message.reactions = message.reactions
|
||||
? [...message.reactions, newReaction]
|
||||
: [newReaction];
|
||||
}
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
if (payload.member && payload.guild_id) {
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (guild) {
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
payload.member,
|
||||
guild.id,
|
||||
);
|
||||
await cacheHandlers.set("members", memberStruct.id, memberStruct);
|
||||
}
|
||||
}
|
||||
|
||||
const uncachedOptions = {
|
||||
...payload,
|
||||
id: payload.message_id,
|
||||
channelId: payload.channel_id,
|
||||
guildId: payload.guild_id,
|
||||
};
|
||||
|
||||
eventHandlers.reactionRemove?.(
|
||||
uncachedOptions,
|
||||
payload.emoji,
|
||||
payload.user_id,
|
||||
message,
|
||||
);
|
||||
}
|
||||
19
src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts
Normal file
19
src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageReactionRemoveAll } from "../../types/messages/message_reaction_remove_all.ts";
|
||||
|
||||
export async function handleMessageReactionRemoveAll(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const payload = data.d as DiscordMessageReactionRemoveAll;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message?.reactions) {
|
||||
message.reactions = undefined;
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
eventHandlers.reactionRemoveAll?.(data.d);
|
||||
}
|
||||
27
src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts
Normal file
27
src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessageReactionRemoveEmoji } from "../../types/messages/message_reaction_remove_emoji.ts";
|
||||
|
||||
export async function handleMessageReactionRemoveEmoji(
|
||||
data: DiscordGatewayPayload,
|
||||
) {
|
||||
const payload = data.d as DiscordMessageReactionRemoveEmoji;
|
||||
const message = await cacheHandlers.get("messages", payload.message_id);
|
||||
|
||||
if (message?.reactions) {
|
||||
message.reactions = message.reactions?.filter(
|
||||
(reaction) =>
|
||||
!(
|
||||
reaction.emoji.id === payload.emoji.id &&
|
||||
reaction.emoji.name === payload.emoji.name
|
||||
),
|
||||
);
|
||||
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
eventHandlers.reactionRemoveEmoji?.(
|
||||
data.d,
|
||||
);
|
||||
}
|
||||
37
src/handlers/messages/MESSAGE_UPDATE.ts
Normal file
37
src/handlers/messages/MESSAGE_UPDATE.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
|
||||
export async function handleMessageUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordMessage;
|
||||
const channel = await cacheHandlers.get("channels", payload.channel_id);
|
||||
if (!channel) return;
|
||||
|
||||
const cachedMessage = await cacheHandlers.get("messages", payload.id);
|
||||
if (!cachedMessage) return;
|
||||
|
||||
const oldMessage = {
|
||||
attachments: cachedMessage.attachments,
|
||||
content: cachedMessage.content,
|
||||
embeds: cachedMessage.embeds,
|
||||
editedTimestamp: cachedMessage.editedTimestamp,
|
||||
tts: cachedMessage.tts,
|
||||
pinned: cachedMessage.pinned,
|
||||
};
|
||||
|
||||
// Messages with embeds can trigger update but they wont have edited_timestamp
|
||||
if (
|
||||
!payload.edited_timestamp ||
|
||||
(cachedMessage.content === payload.content)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await structures.createMessageStruct(payload);
|
||||
|
||||
await cacheHandlers.set("messages", payload.id, message);
|
||||
|
||||
eventHandlers.messageUpdate?.(message, oldMessage);
|
||||
}
|
||||
12
src/handlers/misc/PRESENCE_UPDATE.ts
Normal file
12
src/handlers/misc/PRESENCE_UPDATE.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordPresenceUpdate } from "../../types/misc/presence_update.ts";
|
||||
|
||||
export async function handlePresenceUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordPresenceUpdate;
|
||||
const oldPresence = await cacheHandlers.get("presences", payload.user.id);
|
||||
await cacheHandlers.set("presences", payload.user.id, payload);
|
||||
|
||||
eventHandlers.presenceUpdate?.(payload, oldPresence);
|
||||
}
|
||||
103
src/handlers/misc/READY.ts
Normal file
103
src/handlers/misc/READY.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
eventHandlers,
|
||||
lastShardId,
|
||||
setApplicationId,
|
||||
setBotId,
|
||||
} from "../../bot.ts";
|
||||
import { cache, cacheHandlers } from "../../cache.ts";
|
||||
import { initialMemberLoadQueue } from "../../structures/guild.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { delay } from "../../util/utils.ts";
|
||||
import { allowNextShard, basicShards } from "../../ws/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordReady } from "../../types/gateway/ready.ts";
|
||||
|
||||
export async function handleReady(
|
||||
data: DiscordGatewayPayload,
|
||||
shardId: number,
|
||||
) {
|
||||
// The bot has already started, the last shard is resumed, however.
|
||||
if (cache.isReady) return;
|
||||
|
||||
const payload = data.d as DiscordReady;
|
||||
setBotId(payload.user.id);
|
||||
setApplicationId(payload.application.id);
|
||||
|
||||
// Triggered on each shard
|
||||
eventHandlers.shardReady?.(shardId);
|
||||
// Save when the READY event was received to prevent infinite load loops
|
||||
const now = Date.now();
|
||||
|
||||
const shard = basicShards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
// Set ready to false just to go sure
|
||||
shard.ready = false;
|
||||
// All guilds are unavailable at first
|
||||
shard.unavailableGuildIds = new Set(payload.guilds.map((g) => g.id));
|
||||
|
||||
// Start ready check in 2 seconds
|
||||
setTimeout(() => checkReady(payload, shardId, now), 2000);
|
||||
|
||||
// Wait 5 seconds to spawn next shard
|
||||
await delay(5000);
|
||||
allowNextShard();
|
||||
}
|
||||
|
||||
// Don't pass the shard itself because unavailableGuilds won't be updated by the GUILD_CREATE event
|
||||
/** This function checks if the shard is fully loaded */
|
||||
function checkReady(payload: DiscordReady, shardId: number, now: number) {
|
||||
const shard = basicShards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
// Check if all guilds were loaded
|
||||
if (shard.unavailableGuildIds.size) {
|
||||
if (Date.now() - now > 10000) {
|
||||
eventHandlers.shardFailedToLoad?.(shardId, shard.unavailableGuildIds);
|
||||
// Force execute the loaded function to prevent infinite loop
|
||||
loaded(shardId);
|
||||
} else {
|
||||
// Not all guilds were loaded but 10 seconds haven't passed so check again
|
||||
setTimeout(() => checkReady(payload, shardId, now), 2000);
|
||||
}
|
||||
} else {
|
||||
// All guilds were loaded
|
||||
loaded(shardId);
|
||||
}
|
||||
}
|
||||
|
||||
async function loaded(shardId: number) {
|
||||
const shard = basicShards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
shard.ready = true;
|
||||
|
||||
// If it is the last shard we can go full ready
|
||||
if (shardId === lastShardId - 1) {
|
||||
// Still some shards are loading so wait another 2 seconds for them
|
||||
if (basicShards.some((shard) => !shard.ready)) {
|
||||
setTimeout(() => loaded(shardId), 2000);
|
||||
} else {
|
||||
cache.isReady = true;
|
||||
eventHandlers.ready?.();
|
||||
|
||||
// All the members that came in on guild creates should now be processed 1 by 1
|
||||
for (const [guildId, members] of initialMemberLoadQueue.entries()) {
|
||||
await Promise.allSettled(
|
||||
members.map(async (member) => {
|
||||
const memberStruct = await structures.createMemberStruct(
|
||||
member,
|
||||
guildId,
|
||||
);
|
||||
|
||||
return cacheHandlers.set(
|
||||
"members",
|
||||
memberStruct.id,
|
||||
memberStruct,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/handlers/misc/TYPING_START.ts
Normal file
7
src/handlers/misc/TYPING_START.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordTypingStart } from "../../types/misc/typing_start.ts";
|
||||
|
||||
export function handleTypingStart(data: DiscordGatewayPayload) {
|
||||
eventHandlers.typingStart?.(data.d as DiscordTypingStart);
|
||||
}
|
||||
20
src/handlers/misc/USER_UPDATE.ts
Normal file
20
src/handlers/misc/USER_UPDATE.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordUser } from "../../types/users/user.ts";
|
||||
|
||||
export async function handleUserUpdate(data: DiscordGatewayPayload) {
|
||||
const userData = data.d as DiscordUser;
|
||||
|
||||
const member = await cacheHandlers.get("members", userData.id);
|
||||
if (!member) return;
|
||||
|
||||
Object.entries(userData).forEach(([key, value]) => {
|
||||
// @ts-ignore index signatures
|
||||
if (member[key] !== value) return member[key] = value;
|
||||
});
|
||||
|
||||
await cacheHandlers.set("members", userData.id, member);
|
||||
|
||||
eventHandlers.botUpdate?.(userData);
|
||||
}
|
||||
150
src/handlers/mod.ts
Normal file
150
src/handlers/mod.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { handleChannelCreate } from "./channels/CHANNEL_CREATE.ts";
|
||||
import { handleChannelDelete } from "./channels/CHANNEL_DELETE.ts";
|
||||
import { handleChannelPinsUpdate } from "./channels/CHANNEL_PINS_UPDATE.ts";
|
||||
import { handleChannelUpdate } from "./channels/CHANNEL_UPDATE.ts";
|
||||
import { handleApplicationCommandCreate } from "./commands/APPLICATION_COMMAND_CREATE.ts";
|
||||
import { handleApplicationCommandDelete } from "./commands/APPLICATION_COMMAND_DELETE.ts";
|
||||
import { handleApplicationCommandUpdate } from "./commands/APPLICATION_COMMAND_UPDATE.ts";
|
||||
import { handleGuildEmojisUpdate } from "./emojis/GUILD_EMOJIS_UPDATE.ts";
|
||||
import { handleGuildBanAdd } from "./guilds/GUILD_BAN_ADD.ts";
|
||||
import { handleGuildBanRemove } from "./guilds/GUILD_BAN_REMOVE.ts";
|
||||
import { handleGuildCreate } from "./guilds/GUILD_CREATE.ts";
|
||||
import { handleGuildDelete } from "./guilds/GUILD_DELETE.ts";
|
||||
import { handleGuildIntegrationsUpdate } from "./guilds/GUILD_INTEGRATIONS_UPDATE.ts";
|
||||
import { handleGuildUpdate } from "./guilds/GUILD_UPDATE.ts";
|
||||
import { handleIntegrationCreate } from "./integrations/INTEGRATION_CREATE.ts";
|
||||
import { handleIntegrationDelete } from "./integrations/INTEGRATION_DELETE.ts";
|
||||
import { handleIntegrationUpdate } from "./integrations/INTEGRATION_UPDATE.ts";
|
||||
import { handleInteractionCreate } from "./interactions/INTERACTION_CREATE.ts";
|
||||
import { handleInviteCreate } from "./invites/INVITE_CREATE.ts";
|
||||
import { handleGuildMembersChunk } from "./members/GUILD_MEMBERS_CHUNK.ts";
|
||||
import { handleGuildMemberAdd } from "./members/GUILD_MEMBER_ADD.ts";
|
||||
import { handleGuildMemberRemove } from "./members/GUILD_MEMBER_REMOVE.ts";
|
||||
import { handleGuildMemberUpdate } from "./members/GUILD_MEMBER_UPDATE.ts";
|
||||
import { handleMessageCreate } from "./messages/MESSAGE_CREATE.ts";
|
||||
import { handleMessageDelete } from "./messages/MESSAGE_DELETE.ts";
|
||||
import { handleMessageDeleteBulk } from "./messages/MESSAGE_DELETE_BULK.ts";
|
||||
import { handleMessageReactionAdd } from "./messages/MESSAGE_REACTION_ADD.ts";
|
||||
import { handleMessageReactionRemove } from "./messages/MESSAGE_REACTION_REMOVE.ts";
|
||||
import { handleMessageReactionRemoveAll } from "./messages/MESSAGE_REACTION_REMOVE_ALL.ts";
|
||||
import { handleMessageReactionRemoveEmoji } from "./messages/MESSAGE_REACTION_REMOVE_EMOJI.ts";
|
||||
import { handleMessageUpdate } from "./messages/MESSAGE_UPDATE.ts";
|
||||
import { handlePresenceUpdate } from "./misc/PRESENCE_UPDATE.ts";
|
||||
import { handleReady } from "./misc/READY.ts";
|
||||
import { handleTypingStart } from "./misc/TYPING_START.ts";
|
||||
import { handleUserUpdate } from "./misc/USER_UPDATE.ts";
|
||||
import { handleGuildRoleCreate } from "./roles/GUILD_ROLE_CREATE.ts";
|
||||
import { handleGuildRoleDelete } from "./roles/GUILD_ROLE_DELETE.ts";
|
||||
import { handleGuildRoleUpdate } from "./roles/GUILD_ROLE_UPDATE.ts";
|
||||
import { handleVoiceServerUpdate } from "./voice/VOICE_SERVER_UPDATE.ts";
|
||||
import { handleVoiceStateUpdate } from "./voice/VOICE_STATE_UPDATE.ts";
|
||||
import { handleWebhooksUpdate } from "./webhooks/WEBHOOKS_UPDATE.ts";
|
||||
|
||||
export {
|
||||
handleApplicationCommandCreate,
|
||||
handleApplicationCommandDelete,
|
||||
handleApplicationCommandUpdate,
|
||||
handleChannelCreate,
|
||||
handleChannelDelete,
|
||||
handleChannelPinsUpdate,
|
||||
handleChannelUpdate,
|
||||
handleGuildBanAdd,
|
||||
handleGuildBanRemove,
|
||||
handleGuildCreate,
|
||||
handleGuildDelete,
|
||||
handleGuildEmojisUpdate,
|
||||
handleGuildIntegrationsUpdate,
|
||||
handleGuildMemberAdd,
|
||||
handleGuildMemberRemove,
|
||||
handleGuildMembersChunk,
|
||||
handleGuildMemberUpdate,
|
||||
handleGuildRoleCreate,
|
||||
handleGuildRoleDelete,
|
||||
handleGuildRoleUpdate,
|
||||
handleGuildUpdate,
|
||||
handleIntegrationCreate,
|
||||
handleIntegrationDelete,
|
||||
handleIntegrationUpdate,
|
||||
handleInteractionCreate,
|
||||
handleInviteCreate,
|
||||
handleMessageCreate,
|
||||
handleMessageDelete,
|
||||
handleMessageDeleteBulk,
|
||||
handleMessageReactionAdd,
|
||||
handleMessageReactionRemove,
|
||||
handleMessageReactionRemoveAll,
|
||||
handleMessageReactionRemoveEmoji,
|
||||
handleMessageUpdate,
|
||||
handlePresenceUpdate,
|
||||
handleReady,
|
||||
handleTypingStart,
|
||||
handleUserUpdate,
|
||||
handleVoiceServerUpdate,
|
||||
handleVoiceStateUpdate,
|
||||
handleWebhooksUpdate,
|
||||
};
|
||||
|
||||
export let handlers = {
|
||||
// misc
|
||||
READY: handleReady,
|
||||
// channels
|
||||
CHANNEL_CREATE: handleChannelCreate,
|
||||
CHANNEL_DELETE: handleChannelDelete,
|
||||
CHANNEL_PINS_UPDATE: handleChannelPinsUpdate,
|
||||
CHANNEL_UPDATE: handleChannelUpdate,
|
||||
// commands
|
||||
APPLICATION_COMMAND_CREATE: handleApplicationCommandCreate,
|
||||
APPLICATION_COMMAND_DELETE: handleApplicationCommandDelete,
|
||||
APPLICATION_COMMAND_UPDATE: handleApplicationCommandUpdate,
|
||||
// guilds
|
||||
GUILD_BAN_ADD: handleGuildBanAdd,
|
||||
GUILD_BAN_REMOVE: handleGuildBanRemove,
|
||||
GUILD_CREATE: handleGuildCreate,
|
||||
GUILD_DELETE: handleGuildDelete,
|
||||
GUILD_EMOJIS_UPDATE: handleGuildEmojisUpdate,
|
||||
GUILD_INTEGRATIONS_UPDATE: handleGuildIntegrationsUpdate,
|
||||
GUILD_MEMBER_ADD: handleGuildMemberAdd,
|
||||
GUILD_MEMBER_REMOVE: handleGuildMemberRemove,
|
||||
GUILD_MEMBER_UPDATE: handleGuildMemberUpdate,
|
||||
GUILD_MEMBERS_CHUNK: handleGuildMembersChunk,
|
||||
GUILD_ROLE_CREATE: handleGuildRoleCreate,
|
||||
GUILD_ROLE_DELETE: handleGuildRoleDelete,
|
||||
GUILD_ROLE_UPDATE: handleGuildRoleUpdate,
|
||||
GUILD_UPDATE: handleGuildUpdate,
|
||||
// interactions
|
||||
INTERACTION_CREATE: handleInteractionCreate,
|
||||
// invites
|
||||
INVITE_CREATE: handleInviteCreate,
|
||||
INVITE_DELETE: handleInviteCreate,
|
||||
// messages
|
||||
MESSAGE_CREATE: handleMessageCreate,
|
||||
MESSAGE_DELETE_BULK: handleMessageDeleteBulk,
|
||||
MESSAGE_DELETE: handleMessageDelete,
|
||||
MESSAGE_REACTION_ADD: handleMessageReactionAdd,
|
||||
MESSAGE_REACTION_REMOVE_ALL: handleMessageReactionRemoveAll,
|
||||
MESSAGE_REACTION_REMOVE_EMOJI: handleMessageReactionRemoveEmoji,
|
||||
MESSAGE_REACTION_REMOVE: handleMessageReactionRemove,
|
||||
MESSAGE_UPDATE: handleMessageUpdate,
|
||||
// presence
|
||||
PRESENCE_UPDATE: handlePresenceUpdate,
|
||||
TYPING_START: handleTypingStart,
|
||||
USER_UPDATE: handleUserUpdate,
|
||||
// voice
|
||||
VOICE_SERVER_UPDATE: handleVoiceServerUpdate,
|
||||
VOICE_STATE_UPDATE: handleVoiceStateUpdate,
|
||||
// webhooks
|
||||
WEBHOOKS_UPDATE: handleWebhooksUpdate,
|
||||
// integrations
|
||||
INTEGRATION_CREATE: handleIntegrationCreate,
|
||||
INTEGRATION_UPDATE: handleIntegrationUpdate,
|
||||
INTEGRATION_DELETE: handleIntegrationDelete,
|
||||
};
|
||||
|
||||
export type Handlers = typeof handlers;
|
||||
|
||||
export function updateHandlers(newHandlers: Handlers) {
|
||||
handlers = {
|
||||
...handlers,
|
||||
...newHandlers,
|
||||
};
|
||||
}
|
||||
16
src/handlers/roles/GUILD_ROLE_CREATE.ts
Normal file
16
src/handlers/roles/GUILD_ROLE_CREATE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleGuildRoleCreate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildRoleCreateUpdate;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const role = await structures.createRoleStruct(payload.role);
|
||||
guild.roles = guild.roles.set(payload.role.id, role);
|
||||
await cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
eventHandlers.roleCreate?.(guild, role);
|
||||
}
|
||||
29
src/handlers/roles/GUILD_ROLE_DELETE.ts
Normal file
29
src/handlers/roles/GUILD_ROLE_DELETE.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordGuildRoleDelete } from "../../types/guilds/guild_role_delete.ts";
|
||||
|
||||
export async function handleGuildRoleDelete(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildRoleDelete;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedRole = guild.roles.get(payload.role_id)!;
|
||||
guild.roles.delete(payload.role_id);
|
||||
|
||||
if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole);
|
||||
|
||||
// For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking.
|
||||
cacheHandlers.forEach("members", (member) => {
|
||||
// Not in the relevant guild so just skip.
|
||||
if (!member.guilds.has(guild.id)) return;
|
||||
|
||||
member.guilds.forEach((g) => {
|
||||
// Member does not have this role
|
||||
if (!g.roles.includes(payload.role_id)) return;
|
||||
// Remove this role from the members cache
|
||||
g.roles = g.roles.filter((id) => id !== payload.role_id);
|
||||
cacheHandlers.set("members", member.id, member);
|
||||
});
|
||||
});
|
||||
}
|
||||
19
src/handlers/roles/GUILD_ROLE_UPDATE.ts
Normal file
19
src/handlers/roles/GUILD_ROLE_UPDATE.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
|
||||
export async function handleGuildRoleUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordGuildRoleCreateUpdate;
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const cachedRole = guild.roles.get(payload.role.id);
|
||||
if (!cachedRole) return;
|
||||
|
||||
const role = await structures.createRoleStruct(payload.role);
|
||||
guild.roles.set(payload.role.id, role);
|
||||
await cacheHandlers.set("guilds", guild.id, guild);
|
||||
|
||||
eventHandlers.roleUpdate?.(guild, role, cachedRole);
|
||||
}
|
||||
11
src/handlers/voice/VOICE_SERVER_UPDATE.ts
Normal file
11
src/handlers/voice/VOICE_SERVER_UPDATE.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleVoiceServerUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as DiscordVoiceServerUpdate;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
eventHandlers.voiceServerUpdate?.(payload.token, guild, payload.endpoint);
|
||||
}
|
||||
55
src/handlers/voice/VOICE_STATE_UPDATE.ts
Normal file
55
src/handlers/voice/VOICE_STATE_UPDATE.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordVoiceState } from "../../types/voice/voice_state.ts";
|
||||
|
||||
export async function handleVoiceStateUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as DiscordVoiceState;
|
||||
if (!payload.guild_id) return;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
const member = payload.member
|
||||
? await structures.createMemberStruct(payload.member, guild.id)
|
||||
: await cacheHandlers.get("members", payload.user_id);
|
||||
if (!member) return;
|
||||
|
||||
// No cached state before so lets make one for em
|
||||
const cachedState = guild.voiceStates.get(payload.user_id);
|
||||
|
||||
guild.voiceStates.set(payload.user_id, {
|
||||
...payload,
|
||||
guildId: payload.guild_id,
|
||||
channelId: payload.channel_id || "",
|
||||
userId: payload.user_id,
|
||||
sessionId: payload.session_id,
|
||||
selfDeaf: payload.self_deaf,
|
||||
selfMute: payload.self_mute,
|
||||
selfStream: payload.self_stream || false,
|
||||
});
|
||||
|
||||
await cacheHandlers.set("guilds", payload.guild_id, guild);
|
||||
|
||||
if (cachedState?.channelId !== payload.channel_id) {
|
||||
// Either joined or moved channels
|
||||
if (payload.channel_id) {
|
||||
if (cachedState?.channelId) { // Was in a channel before
|
||||
eventHandlers.voiceChannelSwitch?.(
|
||||
member,
|
||||
payload.channel_id,
|
||||
cachedState.channelId,
|
||||
);
|
||||
} else { // Was not in a channel before so user just joined
|
||||
eventHandlers.voiceChannelJoin?.(member, payload.channel_id);
|
||||
}
|
||||
} // Left the channel
|
||||
else if (cachedState?.channelId) {
|
||||
guild.voiceStates.delete(payload.user_id);
|
||||
eventHandlers.voiceChannelLeave?.(member, cachedState.channelId);
|
||||
}
|
||||
}
|
||||
|
||||
eventHandlers.voiceStateUpdate?.(member, payload);
|
||||
}
|
||||
11
src/handlers/webhooks/WEBHOOKS_UPDATE.ts
Normal file
11
src/handlers/webhooks/WEBHOOKS_UPDATE.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordWebhooksUpdate } from "../../types/webhooks/webhooks_update.ts";
|
||||
|
||||
export function handleWebhooksUpdate(data: DiscordGatewayPayload) {
|
||||
const options = data.d as DiscordWebhooksUpdate;
|
||||
eventHandlers.webhooksUpdate?.(
|
||||
options.channel_id,
|
||||
options.guild_id,
|
||||
);
|
||||
}
|
||||
9
src/helpers/channels/category_children_ids.ts
Normal file
9
src/helpers/channels/category_children_ids.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
/** Gets an array of all the channels ids that are the children of this category. */
|
||||
export function categoryChildrenIds(guildId: string, id: string) {
|
||||
return cacheHandlers.filter(
|
||||
"channels",
|
||||
(channel) => channel.parentId === id && channel.guildId === guildId,
|
||||
);
|
||||
}
|
||||
32
src/helpers/channels/channel_overwrite_has_permission.ts
Normal file
32
src/helpers/channels/channel_overwrite_has_permission.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { DiscordOverwrite } from "../../types/channels/overwrite.ts";
|
||||
import { DiscordBitwisePermissionFlags } from "../../types/permissions/bitwise_permission_flags.ts";
|
||||
import { PermissionStrings } from "../../types/permissions/permission_strings.ts";
|
||||
|
||||
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
|
||||
export function channelOverwriteHasPermission(
|
||||
guildId: string,
|
||||
id: string,
|
||||
overwrites: DiscordOverwrite[],
|
||||
permissions: PermissionStrings[],
|
||||
) {
|
||||
const overwrite = overwrites.find((perm) => perm.id === id) ||
|
||||
overwrites.find((perm) => perm.id === guildId);
|
||||
|
||||
return permissions.every((perm) => {
|
||||
if (overwrite) {
|
||||
const allowBits = overwrite.allow;
|
||||
const denyBits = overwrite.deny;
|
||||
if (
|
||||
BigInt(denyBits) & BigInt(DiscordBitwisePermissionFlags[perm])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
BigInt(allowBits) & BigInt(DiscordBitwisePermissionFlags[perm])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
49
src/helpers/channels/create_channel.ts
Normal file
49
src/helpers/channels/create_channel.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { DiscordChannelTypes } from "../../types/channels/channel_types.ts";
|
||||
import { CreateGuildChannel } from "../../types/guilds/create_guild_channel.ts";
|
||||
import { PermissionStrings } from "../../types/permissions/permission_strings.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
calculateBits,
|
||||
requireBotGuildPermissions,
|
||||
} from "../../util/permissions.ts";
|
||||
|
||||
/** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
|
||||
export async function createChannel(
|
||||
guildId: string,
|
||||
name: string,
|
||||
options?: CreateGuildChannel
|
||||
) {
|
||||
const requiredPerms: Set<PermissionStrings> = new Set(["MANAGE_CHANNELS"]);
|
||||
|
||||
options?.permissionOverwrites?.forEach((overwrite) => {
|
||||
overwrite.allow.forEach(requiredPerms.add, requiredPerms);
|
||||
overwrite.deny.forEach(requiredPerms.add, requiredPerms);
|
||||
});
|
||||
|
||||
await requireBotGuildPermissions(guildId, [...requiredPerms]);
|
||||
|
||||
const result = (await rest.runMethod(
|
||||
"post",
|
||||
endpoints.GUILD_CHANNELS(guildId),
|
||||
{
|
||||
...options,
|
||||
name,
|
||||
permission_overwrites: options?.permissionOverwrites?.map((perm) => ({
|
||||
...perm,
|
||||
|
||||
allow: calculateBits(perm.allow),
|
||||
deny: calculateBits(perm.deny),
|
||||
})),
|
||||
type: options?.type || DiscordChannelTypes.GUILD_TEXT,
|
||||
}
|
||||
)) as DiscordChannel;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(result);
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
return channelStruct;
|
||||
}
|
||||
33
src/helpers/channels/delete_channel.ts
Normal file
33
src/helpers/channels/delete_channel.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { Errors } from "../../types/misc/errors.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Delete a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
|
||||
export async function deleteChannel(
|
||||
guildId: string,
|
||||
channelId: string,
|
||||
reason?: string,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildId, ["MANAGE_CHANNELS"]);
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", guildId);
|
||||
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
|
||||
|
||||
if (guild?.rulesChannelId === channelId) {
|
||||
throw new Error(Errors.RULES_CHANNEL_CANNOT_BE_DELETED);
|
||||
}
|
||||
|
||||
if (guild?.publicUpdatesChannelId === channelId) {
|
||||
throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED);
|
||||
}
|
||||
|
||||
const result = await rest.runMethod(
|
||||
"delete",
|
||||
endpoints.CHANNEL_BASE(channelId),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
19
src/helpers/channels/delete_channel_overwrite.ts
Normal file
19
src/helpers/channels/delete_channel_overwrite.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Delete the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */
|
||||
export async function deleteChannelOverwrite(
|
||||
guildId: string,
|
||||
channelId: string,
|
||||
overwriteId: string,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]);
|
||||
|
||||
const result = await rest.runMethod(
|
||||
"delete",
|
||||
endpoints.CHANNEL_OVERWRITE(channelId, overwriteId),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
102
src/helpers/channels/edit_channel.ts
Normal file
102
src/helpers/channels/edit_channel.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
calculateBits,
|
||||
requireBotChannelPermissions,
|
||||
} from "../../util/permissions.ts";
|
||||
|
||||
/** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */
|
||||
export async function editChannel(
|
||||
channelId: string,
|
||||
options: ChannelEditOptions,
|
||||
reason?: string,
|
||||
) {
|
||||
await requireBotChannelPermissions(channelId, ["MANAGE_CHANNELS"]);
|
||||
|
||||
if (options.name || options.topic) {
|
||||
const request = editChannelNameTopicQueue.get(channelId);
|
||||
if (!request) {
|
||||
// If this hasnt been done before simply add 1 for it
|
||||
editChannelNameTopicQueue.set(channelId, {
|
||||
channelId: channelId,
|
||||
amount: 1,
|
||||
// 10 minutes from now
|
||||
timestamp: Date.now() + 600000,
|
||||
items: [],
|
||||
});
|
||||
} else if (request.amount === 1) {
|
||||
// Start queuing future requests to this channel
|
||||
request.amount = 2;
|
||||
request.timestamp = Date.now() + 600000;
|
||||
} else {
|
||||
// 2 have already been used add to queue
|
||||
request.items.push({ channelId, options });
|
||||
if (editChannelProcessing) return;
|
||||
editChannelProcessing = true;
|
||||
processEditChannelQueue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
...options,
|
||||
// deno-lint-ignore camelcase
|
||||
rate_limit_per_user: options.rateLimitPerUser,
|
||||
// deno-lint-ignore camelcase
|
||||
parent_id: options.parentId,
|
||||
// deno-lint-ignore camelcase
|
||||
user_limit: options.userLimit,
|
||||
// deno-lint-ignore camelcase
|
||||
permission_overwrites: options.overwrites?.map((overwrite) => {
|
||||
return {
|
||||
...overwrite,
|
||||
allow: calculateBits(overwrite.allow),
|
||||
deny: calculateBits(overwrite.deny),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const result = await rest.runMethod(
|
||||
"patch",
|
||||
endpoints.CHANNEL_BASE(channelId),
|
||||
{
|
||||
...payload,
|
||||
reason,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const editChannelNameTopicQueue = new Map<string, EditChannelRequest>();
|
||||
let editChannelProcessing = false;
|
||||
|
||||
function processEditChannelQueue() {
|
||||
if (!editChannelProcessing) return;
|
||||
|
||||
const now = Date.now();
|
||||
editChannelNameTopicQueue.forEach((request) => {
|
||||
if (now > request.timestamp) return;
|
||||
// 10 minutes have passed so we can reset this channel again
|
||||
if (!request.items.length) {
|
||||
return editChannelNameTopicQueue.delete(request.channelId);
|
||||
}
|
||||
request.amount = 0;
|
||||
// There are items to process for this request
|
||||
const details = request.items.shift();
|
||||
|
||||
if (!details) return;
|
||||
|
||||
editChannel(details.channelId, details.options);
|
||||
const secondDetails = request.items.shift();
|
||||
if (!secondDetails) return;
|
||||
|
||||
return editChannel(secondDetails.channelId, secondDetails.options);
|
||||
});
|
||||
|
||||
if (editChannelNameTopicQueue.size) {
|
||||
setTimeout(() => processEditChannelQueue(), 600000);
|
||||
} else {
|
||||
editChannelProcessing = false;
|
||||
}
|
||||
}
|
||||
29
src/helpers/channels/edit_channel_overwrite.ts
Normal file
29
src/helpers/channels/edit_channel_overwrite.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { Overwrite } from "../../types/channels/overwrite.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
calculateBits,
|
||||
requireBotGuildPermissions,
|
||||
} from "../../util/permissions.ts";
|
||||
|
||||
/** Edit the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */
|
||||
export async function editChannelOverwrite(
|
||||
guildId: string,
|
||||
channelId: string,
|
||||
overwriteId: string,
|
||||
options: Omit<Overwrite, "id">,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]);
|
||||
|
||||
const result = await rest.runMethod(
|
||||
"put",
|
||||
endpoints.CHANNEL_OVERWRITE(channelId, overwriteId),
|
||||
{
|
||||
allow: calculateBits(options.allow),
|
||||
deny: calculateBits(options.deny),
|
||||
type: options.type,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
19
src/helpers/channels/follow_channel.ts
Normal file
19
src/helpers/channels/follow_channel.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { DiscordFollowedChannel } from "../../types/channels/followed_channel.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Follow a News Channel to send messages to a target channel. Requires the `MANAGE_WEBHOOKS` permission in the target channel. Returns the webhook id. */
|
||||
export async function followChannel(
|
||||
sourceChannelId: string,
|
||||
targetChannelId: string,
|
||||
) {
|
||||
await requireBotChannelPermissions(targetChannelId, ["MANAGE_WEBHOOKS"]);
|
||||
|
||||
const data =
|
||||
(await rest.runMethod("post", endpoints.CHANNEL_FOLLOW(sourceChannelId), {
|
||||
webhook_channel_id: targetChannelId,
|
||||
})) as DiscordFollowedChannel;
|
||||
|
||||
return data.webhook_id;
|
||||
}
|
||||
26
src/helpers/channels/get_channel.ts
Normal file
26
src/helpers/channels/get_channel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Fetches a single channel object from the api.
|
||||
*
|
||||
* ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your channels will be cached in your guild.**
|
||||
*/
|
||||
export async function getChannel(channelId: string, addToCache = true) {
|
||||
const result = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.CHANNEL_BASE(channelId),
|
||||
)) as DiscordChannel;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(
|
||||
result,
|
||||
result.guild_id,
|
||||
);
|
||||
if (addToCache) {
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
}
|
||||
|
||||
return channelStruct;
|
||||
}
|
||||
16
src/helpers/channels/get_channel_webhooks.ts
Normal file
16
src/helpers/channels/get_channel_webhooks.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { DiscordWebhook } from "../../types/webhooks/webhook.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */
|
||||
export async function getChannelWebhooks(channelId: string) {
|
||||
await requireBotChannelPermissions(channelId, ["MANAGE_WEBHOOKS"]);
|
||||
|
||||
const result = await rest.runMethod(
|
||||
"get",
|
||||
endpoints.CHANNEL_WEBHOOKS(channelId),
|
||||
);
|
||||
|
||||
return result as DiscordWebhook[];
|
||||
}
|
||||
25
src/helpers/channels/get_channels.ts
Normal file
25
src/helpers/channels/get_channels.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannel } from "../../types/channels/channel.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Returns a list of guild channel objects.
|
||||
*
|
||||
* ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your channels will be cached in your guild.**
|
||||
*/
|
||||
export async function getChannels(guildId: string, addToCache = true) {
|
||||
const result = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.GUILD_CHANNELS(guildId),
|
||||
) as DiscordChannel[]);
|
||||
|
||||
return Promise.all(result.map(async (res) => {
|
||||
const channelStruct = await structures.createChannelStruct(res, guildId);
|
||||
if (addToCache) {
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
}
|
||||
|
||||
return channelStruct;
|
||||
}));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user