mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-03 17:30:07 +00:00
Merge remote-tracking branch 'upstream/main' into main
This commit is contained in:
6
.github/CONTRIBUTING.md
vendored
6
.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:
|
||||
|
||||
|
||||
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.
|
||||
@@ -70,7 +70,7 @@ libraries. If you are a beginner, you can check out these awesome official and
|
||||
unofficial boilerplates:
|
||||
|
||||
- [Discordeno Boilerplate (official)](https://github.com/discordeno/boilerplate)
|
||||
- [Serverless Slash Commands Template
|
||||
- [Serverless Slash Commands Boilerplate
|
||||
(official)](https://github.com/discordeno/slash-commands-boilerplate)
|
||||
- [Add Your Own!](https://github.com/discordeno/discordeno/pulls)
|
||||
|
||||
|
||||
2
deps.ts
2
deps.ts
@@ -1 +1 @@
|
||||
export { encode } from "https://deno.land/std@0.88.0/encoding/base64.ts";
|
||||
export { encode } from "https://deno.land/std@0.90.0/encoding/base64.ts";
|
||||
|
||||
@@ -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!
|
||||
|
||||
31
mod.ts
31
mod.ts
@@ -1,27 +1,14 @@
|
||||
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/message.ts";
|
||||
export * from "./src/structures/mod.ts";
|
||||
export * from "./src/structures/role.ts";
|
||||
export * from "./src/types/mod.ts";
|
||||
export * from "./src/util/cache.ts";
|
||||
export * from "./src/util/collection.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,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,565 +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);
|
||||
}
|
||||
|
||||
if (options.max_age && (options.max_age > 604800 || options.max_age < 0)) {
|
||||
console.log(
|
||||
`The max age for invite created in ${channelID} was not between 0-604800. Using default values instead.`,
|
||||
);
|
||||
options.max_age = undefined;
|
||||
}
|
||||
|
||||
if (options.max_uses && (options.max_uses > 100 || options.max_uses < 0)) {
|
||||
console.log(
|
||||
`The max uses for invite created in ${channelID} was not between 0-100. Using default values instead.`,
|
||||
);
|
||||
options.max_uses = undefined;
|
||||
}
|
||||
|
||||
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,337 +0,0 @@
|
||||
import { botID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
DiscordGetReactionsParams,
|
||||
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,
|
||||
options?: DiscordGetReactionsParams,
|
||||
) {
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.CHANNEL_MESSAGE_REACTION(message.channelID, message.id, reaction),
|
||||
options,
|
||||
)) 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,610 +0,0 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import {
|
||||
CreateSlashCommandOptions,
|
||||
EditSlashCommandOptions,
|
||||
EditSlashResponseOptions,
|
||||
EditWebhookMessageOptions,
|
||||
Errors,
|
||||
ExecuteWebhookOptions,
|
||||
MessageCreateOptions,
|
||||
SlashCommand,
|
||||
SlashCommandOption,
|
||||
SlashCommandOptionChoice,
|
||||
SlashCommandOptionType,
|
||||
SlashCommandResponseOptions,
|
||||
UpsertSlashCommandOptions,
|
||||
UpsertSlashCommandsOptions,
|
||||
WebhookCreateOptions,
|
||||
WebhookEditOptions,
|
||||
WebhookPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { endpoints, SLASH_COMMANDS_NAME_REGEX } 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 },
|
||||
) as MessageCreateOptions;
|
||||
|
||||
const message = await structures.createMessage(result);
|
||||
return message;
|
||||
}
|
||||
|
||||
export async function deleteWebhookMessage(
|
||||
webhookID: string,
|
||||
webhookToken: string,
|
||||
messageID: string,
|
||||
) {
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken, messageID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateSlashOptionChoices(
|
||||
choices: SlashCommandOptionChoice[],
|
||||
optionType: SlashCommandOptionType,
|
||||
) {
|
||||
for (const choice of choices) {
|
||||
if ([...choice.name].length < 1 || [...choice.name].length > 100) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
}
|
||||
|
||||
if (
|
||||
(optionType === SlashCommandOptionType.STRING &&
|
||||
(typeof choice.value !== "string" || choice.value.length < 1 ||
|
||||
choice.value.length > 100)) ||
|
||||
(optionType === SlashCommandOptionType.INTEGER &&
|
||||
typeof choice.value !== "number")
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateSlashOptions(options: SlashCommandOption[]) {
|
||||
for (const option of options) {
|
||||
if (
|
||||
(option.choices?.length && option.choices.length > 25) ||
|
||||
option.type !== SlashCommandOptionType.STRING &&
|
||||
option.type !== SlashCommandOptionType.INTEGER
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
}
|
||||
|
||||
if (
|
||||
([...option.name].length < 1 || [...option.name].length > 32) ||
|
||||
([...option.description].length < 1 ||
|
||||
[...option.description].length > 100)
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
}
|
||||
|
||||
if (option.choices) {
|
||||
validateSlashOptionChoices(option.choices, option.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateSlashCommands(
|
||||
commands: UpsertSlashCommandOptions[],
|
||||
create = false,
|
||||
) {
|
||||
for (const command of commands) {
|
||||
if (
|
||||
(command.name && !SLASH_COMMANDS_NAME_REGEX.test(command.name)) ||
|
||||
(create && !command.name)
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_NAME);
|
||||
}
|
||||
|
||||
if (
|
||||
(command.description &&
|
||||
([...command.description].length < 1 ||
|
||||
[...command.description].length > 100)) ||
|
||||
(create && !command.description)
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
|
||||
}
|
||||
|
||||
if (command.options?.length) {
|
||||
if (command.options.length > 25) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS);
|
||||
}
|
||||
|
||||
validateSlashOptions(command.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
validateSlashCommands([options], true);
|
||||
|
||||
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,
|
||||
) {
|
||||
validateSlashCommands([options]);
|
||||
|
||||
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,
|
||||
) {
|
||||
validateSlashCommands(options);
|
||||
|
||||
const result = await RequestManager.put(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
options,
|
||||
);
|
||||
|
||||
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,
|
||||
) {
|
||||
if (!SLASH_COMMANDS_NAME_REGEX.test(options.name)) {
|
||||
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: SlashCommandResponseOptions,
|
||||
) {
|
||||
// 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),
|
||||
900000,
|
||||
);
|
||||
|
||||
// If the user wants this as a private message mark it ephemeral
|
||||
if (options.private) {
|
||||
options.data.flags = 64;
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
// If the original message was edited, this will not return a message
|
||||
if (!options.messageID) return result;
|
||||
|
||||
const message = await structures.createMessage(
|
||||
result as MessageCreateOptions,
|
||||
);
|
||||
return message;
|
||||
}
|
||||
@@ -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,4 +1,4 @@
|
||||
import { getGatewayBot } from "./api/handlers/gateway.ts";
|
||||
import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts";
|
||||
import {
|
||||
BotConfig,
|
||||
DiscordBotGatewayData,
|
||||
@@ -18,6 +18,7 @@ export let eventHandlers: EventHandlers = {};
|
||||
|
||||
export let botGatewayData: DiscordBotGatewayData;
|
||||
export let proxyWSURL = `wss://gateway.discord.gg`;
|
||||
export let lastShardID = 0;
|
||||
|
||||
export const identifyPayload: DiscordIdentify = {
|
||||
token: "",
|
||||
@@ -60,9 +61,10 @@ export async function startBot(config: BotConfig) {
|
||||
(bits, next) => (bits |= typeof next === "string" ? Intents[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 */
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// 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 { 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";
|
||||
|
||||
export type TableName =
|
||||
| "guilds"
|
||||
103
src/handlers/READY.ts
Normal file
103
src/handlers/READY.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
eventHandlers,
|
||||
lastShardID,
|
||||
setApplicationID,
|
||||
setBotID,
|
||||
} from "../bot.ts";
|
||||
import { DiscordPayload, ReadyPayload } from "../types/discord.ts";
|
||||
import { cache } from "../util/cache.ts";
|
||||
import { delay } from "../util/utils.ts";
|
||||
import { allowNextShard, basicShards } from "../ws/mod.ts";
|
||||
import { initialMemberLoadQueue } from "../structures/guild.ts";
|
||||
import { structures } from "../structures/mod.ts";
|
||||
import { cacheHandlers } from "../cache.ts";
|
||||
|
||||
export async function handleReady(
|
||||
data: DiscordPayload,
|
||||
shardID: number,
|
||||
) {
|
||||
// The bot has already started, the last shard is resumed, however.
|
||||
if (cache.isReady) return;
|
||||
|
||||
const payload = data.d as ReadyPayload;
|
||||
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: ReadyPayload, 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,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/handlers/channels/CHANNEL_CREATE.ts
Normal file
13
src/handlers/channels/CHANNEL_CREATE.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { ChannelCreatePayload, DiscordPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleChannelCreate(data: DiscordPayload) {
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(payload);
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
eventHandlers.channelCreate?.(channelStruct);
|
||||
}
|
||||
@@ -4,23 +4,9 @@ import {
|
||||
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;
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleChannelDelete(data: DiscordPayload) {
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
|
||||
const cachedChannel = await cacheHandlers.get("channels", payload.id);
|
||||
@@ -52,17 +38,3 @@ export async function handleInternalChannelDelete(data: DiscordPayload) {
|
||||
});
|
||||
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);
|
||||
}
|
||||
19
src/handlers/channels/CHANNEL_PINS_UPDATE.ts
Normal file
19
src/handlers/channels/CHANNEL_PINS_UPDATE.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordChannelPinsUpdateEvent,
|
||||
DiscordPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleChannelPinsUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as DiscordChannelPinsUpdateEvent;
|
||||
|
||||
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);
|
||||
}
|
||||
16
src/handlers/channels/CHANNEL_UPDATE.ts
Normal file
16
src/handlers/channels/CHANNEL_UPDATE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { ChannelCreatePayload, DiscordPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleChannelUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as ChannelCreatePayload;
|
||||
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 { ApplicationCommandEvent, DiscordPayload } from "../../types/mod.ts";
|
||||
|
||||
export function handleApplicationCommandCreate(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
const {
|
||||
guild_id: guildID,
|
||||
application_id: applicationID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
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 { ApplicationCommandEvent, DiscordPayload } from "../../types/mod.ts";
|
||||
|
||||
export function handleApplicationCommandDelete(data: DiscordPayload) {
|
||||
const {
|
||||
application_id: applicationID,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
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 { ApplicationCommandEvent, DiscordPayload } from "../../types/mod.ts";
|
||||
|
||||
export function handleApplicationCommandUpdate(data: DiscordPayload) {
|
||||
const {
|
||||
application_id: applicationID,
|
||||
guild_id: guildID,
|
||||
...rest
|
||||
} = data.d as ApplicationCommandEvent;
|
||||
|
||||
eventHandlers.applicationCommandUpdate?.({
|
||||
...rest,
|
||||
guildID,
|
||||
applicationID,
|
||||
});
|
||||
}
|
||||
23
src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts
Normal file
23
src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildEmojisUpdatePayload } from "../../types/mod.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildEmojisUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as GuildEmojisUpdatePayload;
|
||||
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 { DiscordPayload, GuildBanPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildBanAdd(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
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 { DiscordPayload, GuildBanPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildBanRemove(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
32
src/handlers/guilds/GUILD_CREATE.ts
Normal file
32
src/handlers/guilds/GUILD_CREATE.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { CreateGuildPayload, DiscordPayload } from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { basicShards } from "../../ws/shard.ts";
|
||||
|
||||
export async function handleGuildCreate(
|
||||
data: DiscordPayload,
|
||||
shardID: number,
|
||||
) {
|
||||
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.createGuildStruct(
|
||||
data.d as CreateGuildPayload,
|
||||
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);
|
||||
}
|
||||
|
||||
if (!cache.isReady) return eventHandlers.guildLoaded?.(guildStruct);
|
||||
eventHandlers.guildCreate?.(guildStruct);
|
||||
}
|
||||
49
src/handlers/guilds/GUILD_DELETE.ts
Normal file
49
src/handlers/guilds/GUILD_DELETE.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildDeletePayload } from "../../types/mod.ts";
|
||||
import { basicShards } from "../../ws/shard.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildDelete(
|
||||
data: DiscordPayload,
|
||||
shardID: number,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
cacheHandlers.forEach("members", async (member) => {
|
||||
if (!member.guilds.has(payload.id)) return;
|
||||
|
||||
member.guilds.delete(payload.id);
|
||||
|
||||
if (!member.guilds.size) {
|
||||
await cacheHandlers.delete("members", member.id);
|
||||
return;
|
||||
}
|
||||
|
||||
await cacheHandlers.set("members", member.id, member);
|
||||
});
|
||||
|
||||
if (payload.unavailable) {
|
||||
const shard = basicShards.get(shardID);
|
||||
if (shard) shard.unavailableGuildIDs.add(payload.id);
|
||||
|
||||
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);
|
||||
}
|
||||
17
src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts
Normal file
17
src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordGuildIntegrationsUpdateEvent,
|
||||
DiscordPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildIntegrationsUpdate(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
const payload = data.d as DiscordGuildIntegrationsUpdateEvent;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
eventHandlers.guildIntegrationsUpdate?.(guild);
|
||||
}
|
||||
48
src/handlers/guilds/GUILD_UPDATE.ts
Normal file
48
src/handlers/guilds/GUILD_UPDATE.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
GuildUpdateChange,
|
||||
UpdateGuildPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildUpdate(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
31
src/handlers/integrations/INTEGRATION_CREATE.ts
Normal file
31
src/handlers/integrations/INTEGRATION_CREATE.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
IntegrationCreateUpdateEvent,
|
||||
} from "../../types/mod.ts";
|
||||
|
||||
export function handleIntegrationCreate(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
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 { DiscordPayload, IntegrationDeleteEvent } from "../../types/mod.ts";
|
||||
|
||||
export function handleIntegrationDelete(data: DiscordPayload) {
|
||||
const {
|
||||
guild_id: guildID,
|
||||
application_id: applicationID,
|
||||
...rest
|
||||
} = data.d as IntegrationDeleteEvent;
|
||||
|
||||
eventHandlers.integrationDelete?.({
|
||||
...rest,
|
||||
applicationID,
|
||||
guildID,
|
||||
});
|
||||
}
|
||||
29
src/handlers/integrations/INTEGRATION_UPDATE.ts
Normal file
29
src/handlers/integrations/INTEGRATION_UPDATE.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
IntegrationCreateUpdateEvent,
|
||||
} from "../../types/mod.ts";
|
||||
|
||||
export function handleIntegrationUpdate(data: DiscordPayload) {
|
||||
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 { DiscordPayload, InteractionCommandPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleInteractionCreate(data: DiscordPayload) {
|
||||
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 { DiscordPayload, InviteCreateEvent } from "../../types/mod.ts";
|
||||
|
||||
export function handleInviteCreate(payload: DiscordPayload) {
|
||||
if (payload.t !== "INVITE_CREATE") return;
|
||||
//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 InviteCreateEvent;
|
||||
|
||||
eventHandlers.inviteCreate?.({
|
||||
...rest,
|
||||
channelID,
|
||||
guildID,
|
||||
maxAge,
|
||||
targetUser,
|
||||
targetUserType,
|
||||
maxUses,
|
||||
createdAt,
|
||||
});
|
||||
}
|
||||
18
src/handlers/invites/INVITE_DELETE.ts
Normal file
18
src/handlers/invites/INVITE_DELETE.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, InviteDeleteEvent } from "../../types/mod.ts";
|
||||
|
||||
export function handleInviteDelete(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,
|
||||
});
|
||||
}
|
||||
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 { DiscordPayload, GuildMemberChunkPayload } 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 handleGuildMembersChunk(data: DiscordPayload) {
|
||||
const payload = data.d as GuildMemberChunkPayload;
|
||||
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/handlers/members/GUILD_MEMBER_ADD.ts
Normal file
19
src/handlers/members/GUILD_MEMBER_ADD.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildMemberAddPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildMemberAdd(data: DiscordPayload) {
|
||||
const payload = data.d as GuildMemberAddPayload;
|
||||
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);
|
||||
}
|
||||
18
src/handlers/members/GUILD_MEMBER_REMOVE.ts
Normal file
18
src/handlers/members/GUILD_MEMBER_REMOVE.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildBanPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildMemberRemove(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
59
src/handlers/members/GUILD_MEMBER_UPDATE.ts
Normal file
59
src/handlers/members/GUILD_MEMBER_UPDATE.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildMemberUpdatePayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildMemberUpdate(data: DiscordPayload) {
|
||||
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.createMemberStruct(
|
||||
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);
|
||||
}
|
||||
41
src/handlers/messages/MESSAGE_CREATE.ts
Normal file
41
src/handlers/messages/MESSAGE_CREATE.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageCreateOptions } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageCreate(data: DiscordPayload) {
|
||||
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.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);
|
||||
}
|
||||
16
src/handlers/messages/MESSAGE_DELETE.ts
Normal file
16
src/handlers/messages/MESSAGE_DELETE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageDeletePayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageDelete(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
17
src/handlers/messages/MESSAGE_DELETE_BULK.ts
Normal file
17
src/handlers/messages/MESSAGE_DELETE_BULK.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageDeleteBulkPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageDeleteBulk(data: DiscordPayload) {
|
||||
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);
|
||||
}));
|
||||
}
|
||||
56
src/handlers/messages/MESSAGE_REACTION_ADD.ts
Normal file
56
src/handlers/messages/MESSAGE_REACTION_ADD.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { botID, eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageReactionPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageReactionAdd(data: DiscordPayload) {
|
||||
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.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,
|
||||
);
|
||||
}
|
||||
58
src/handlers/messages/MESSAGE_REACTION_REMOVE.ts
Normal file
58
src/handlers/messages/MESSAGE_REACTION_REMOVE.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { botID, eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageReactionPayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageReactionRemove(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
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.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,
|
||||
);
|
||||
}
|
||||
18
src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts
Normal file
18
src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { BaseMessageReactionPayload, DiscordPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageReactionRemoveAll(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
29
src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts
Normal file
29
src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { botID, eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
MessageReactionRemoveEmojiPayload,
|
||||
} from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageReactionRemoveEmoji(
|
||||
data: DiscordPayload,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
36
src/handlers/messages/MESSAGE_UPDATE.ts
Normal file
36
src/handlers/messages/MESSAGE_UPDATE.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, MessageCreateOptions } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleMessageUpdate(data: DiscordPayload) {
|
||||
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.createMessageStruct(payload);
|
||||
|
||||
await cacheHandlers.set("messages", payload.id, message);
|
||||
|
||||
eventHandlers.messageUpdate?.(message, oldMessage);
|
||||
}
|
||||
149
src/handlers/mod.ts
Normal file
149
src/handlers/mod.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
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 "./presence/PRESENCE_UPDATE.ts";
|
||||
import { handleTypingStart } from "./presence/TYPING_START.ts";
|
||||
import { handleUserUpdate } from "./presence/USER_UPDATE.ts";
|
||||
import { handleReady } from "./READY.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 = {
|
||||
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,
|
||||
};
|
||||
}
|
||||
11
src/handlers/presence/PRESENCE_UPDATE.ts
Normal file
11
src/handlers/presence/PRESENCE_UPDATE.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, PresenceUpdatePayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handlePresenceUpdate(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
6
src/handlers/presence/TYPING_START.ts
Normal file
6
src/handlers/presence/TYPING_START.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, TypingStartPayload } from "../../types/mod.ts";
|
||||
|
||||
export function handleTypingStart(data: DiscordPayload) {
|
||||
eventHandlers.typingStart?.(data.d as TypingStartPayload);
|
||||
}
|
||||
19
src/handlers/presence/USER_UPDATE.ts
Normal file
19
src/handlers/presence/USER_UPDATE.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, UserPayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleUserUpdate(data: DiscordPayload) {
|
||||
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);
|
||||
}
|
||||
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 { DiscordPayload, GuildRolePayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildRoleCreate(data: DiscordPayload) {
|
||||
const payload = data.d as GuildRolePayload;
|
||||
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);
|
||||
}
|
||||
28
src/handlers/roles/GUILD_ROLE_DELETE.ts
Normal file
28
src/handlers/roles/GUILD_ROLE_DELETE.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, GuildRoleDeletePayload } from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildRoleDelete(data: DiscordPayload) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
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 { DiscordPayload, GuildRolePayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleGuildRoleUpdate(data: DiscordPayload) {
|
||||
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.createRoleStruct(payload.role);
|
||||
guild.roles.set(payload.role.id, role);
|
||||
await cacheHandlers.set("guilds", guild.id, guild);
|
||||
|
||||
eventHandlers.roleUpdate?.(guild, role, cachedRole);
|
||||
}
|
||||
15
src/handlers/voice/VOICE_SERVER_UPDATE.ts
Normal file
15
src/handlers/voice/VOICE_SERVER_UPDATE.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import {
|
||||
DiscordPayload,
|
||||
DiscordVoiceServerUpdateEvent,
|
||||
} from "../../types/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleVoiceServerUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as DiscordVoiceServerUpdateEvent;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
if (!guild) return;
|
||||
|
||||
eventHandlers.voiceServerUpdate?.(payload.token, guild, payload.endpoint);
|
||||
}
|
||||
54
src/handlers/voice/VOICE_STATE_UPDATE.ts
Normal file
54
src/handlers/voice/VOICE_STATE_UPDATE.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, VoiceStateUpdatePayload } from "../../types/mod.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
export async function handleVoiceStateUpdate(data: DiscordPayload) {
|
||||
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.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);
|
||||
}
|
||||
10
src/handlers/webhooks/WEBHOOKS_UPDATE.ts
Normal file
10
src/handlers/webhooks/WEBHOOKS_UPDATE.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordPayload, WebhookUpdatePayload } from "../../types/mod.ts";
|
||||
|
||||
export function handleWebhooksUpdate(data: DiscordPayload) {
|
||||
const options = data.d as WebhookUpdatePayload;
|
||||
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,
|
||||
);
|
||||
}
|
||||
22
src/helpers/channels/channel_overwrite_has_permission.ts
Normal file
22
src/helpers/channels/channel_overwrite_has_permission.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Permission, Permissions, RawOverwrite } from "../../types/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;
|
||||
});
|
||||
}
|
||||
50
src/helpers/channels/create_guild_channel.ts
Normal file
50
src/helpers/channels/create_guild_channel.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import {
|
||||
ChannelCreateOptions,
|
||||
ChannelCreatePayload,
|
||||
ChannelTypes,
|
||||
Permission,
|
||||
} from "../../types/mod.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 createGuildChannel(
|
||||
guildID: string,
|
||||
name: string,
|
||||
options?: ChannelCreateOptions,
|
||||
) {
|
||||
const requiredPerms: Set<Permission> = 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 RequestManager.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 || ChannelTypes.GUILD_TEXT,
|
||||
},
|
||||
)) as ChannelCreatePayload;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(result);
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
|
||||
return channelStruct;
|
||||
}
|
||||
32
src/helpers/channels/delete_channel.ts
Normal file
32
src/helpers/channels/delete_channel.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { Errors } from "../../types/mod.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 RequestManager.delete(
|
||||
endpoints.CHANNEL_BASE(channelID),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
18
src/helpers/channels/delete_channel_overwrite.ts
Normal file
18
src/helpers/channels/delete_channel_overwrite.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { RequestManager } from "../../rest/request_manager.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 RequestManager.delete(
|
||||
endpoints.CHANNEL_OVERWRITE(channelID, overwriteID),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
109
src/helpers/channels/edit_channel.ts
Normal file
109
src/helpers/channels/edit_channel.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { ChannelEditOptions } from "../../types/mod.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 RequestManager.patch(endpoints.CHANNEL_BASE(channelID), {
|
||||
...payload,
|
||||
reason,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
28
src/helpers/channels/edit_channel_overwrite.ts
Normal file
28
src/helpers/channels/edit_channel_overwrite.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { Overwrite } from "../../types/mod.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 RequestManager.put(
|
||||
endpoints.CHANNEL_OVERWRITE(channelID, overwriteID),
|
||||
{
|
||||
allow: calculateBits(options.allow),
|
||||
deny: calculateBits(options.deny),
|
||||
type: options.type,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
21
src/helpers/channels/follow_channel.ts
Normal file
21
src/helpers/channels/follow_channel.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { FollowedChannelPayload } from "../../types/mod.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 RequestManager.post(
|
||||
endpoints.CHANNEL_FOLLOW(sourceChannelID),
|
||||
{
|
||||
webhook_channel_id: targetChannelID,
|
||||
},
|
||||
)) as FollowedChannelPayload;
|
||||
|
||||
return data.webhook_id;
|
||||
}
|
||||
25
src/helpers/channels/get_channel.ts
Normal file
25
src/helpers/channels/get_channel.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { ChannelCreatePayload } from "../../types/mod.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 RequestManager.get(
|
||||
endpoints.CHANNEL_BASE(channelID),
|
||||
)) as ChannelCreatePayload;
|
||||
|
||||
const channelStruct = await structures.createChannelStruct(
|
||||
result,
|
||||
result.guild_id,
|
||||
);
|
||||
if (addToCache) {
|
||||
await cacheHandlers.set("channels", channelStruct.id, channelStruct);
|
||||
}
|
||||
|
||||
return channelStruct;
|
||||
}
|
||||
15
src/helpers/channels/get_channel_webhooks.ts
Normal file
15
src/helpers/channels/get_channel_webhooks.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { WebhookPayload } from "../../types/mod.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 RequestManager.get(
|
||||
endpoints.CHANNEL_WEBHOOKS(channelID),
|
||||
);
|
||||
|
||||
return result as WebhookPayload[];
|
||||
}
|
||||
24
src/helpers/channels/get_channels.ts
Normal file
24
src/helpers/channels/get_channels.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { ChannelCreatePayload } from "../../types/mod.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 RequestManager.get(
|
||||
endpoints.GUILD_CHANNELS(guildID),
|
||||
) as ChannelCreatePayload[]);
|
||||
|
||||
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;
|
||||
}));
|
||||
}
|
||||
15
src/helpers/channels/get_pins.ts
Normal file
15
src/helpers/channels/get_pins.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { MessageCreateOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** 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.createMessageStruct(res)),
|
||||
);
|
||||
}
|
||||
20
src/helpers/channels/is_channel_synced.ts
Normal file
20
src/helpers/channels/is_channel_synced.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
|
||||
/** Checks whether a channel is synchronized with its parent/category channel or not. */
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
40
src/helpers/channels/start_typing.ts
Normal file
40
src/helpers/channels/start_typing.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { ChannelTypes, Errors } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { botHasChannelPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/**
|
||||
* 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 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 result = await RequestManager.post(endpoints.CHANNEL_TYPING(channelID));
|
||||
|
||||
return result;
|
||||
}
|
||||
20
src/helpers/channels/swap_channels.ts
Normal file
20
src/helpers/channels/swap_channels.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { PositionSwap } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */
|
||||
export async function swapChannels(
|
||||
guildID: string,
|
||||
channelPositions: PositionSwap[],
|
||||
) {
|
||||
if (channelPositions.length < 2) {
|
||||
throw "You must provide at least two channels to be swapped.";
|
||||
}
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.GUILD_CHANNELS(guildID),
|
||||
channelPositions,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
31
src/helpers/commands/create_slash_command.ts
Normal file
31
src/helpers/commands/create_slash_command.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { CreateSlashCommandOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { validateSlashCommands } from "../../util/utils.ts";
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
validateSlashCommands([options], true);
|
||||
|
||||
const result = await RequestManager.post(
|
||||
options.guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, options.guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
{
|
||||
...options,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
13
src/helpers/commands/delete_slash_command.ts
Normal file
13
src/helpers/commands/delete_slash_command.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** 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),
|
||||
);
|
||||
}
|
||||
18
src/helpers/commands/delete_slash_response.ts
Normal file
18
src/helpers/commands/delete_slash_response.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** 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;
|
||||
}
|
||||
70
src/helpers/commands/edit_slash_response.ts
Normal file
70
src/helpers/commands/edit_slash_response.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import {
|
||||
EditSlashResponseOptions,
|
||||
Errors,
|
||||
MessageCreateOptions,
|
||||
} from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** 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,
|
||||
);
|
||||
|
||||
// If the original message was edited, this will not return a message
|
||||
if (!options.messageID) return result;
|
||||
|
||||
const message = await structures.createMessageStruct(
|
||||
result as MessageCreateOptions,
|
||||
);
|
||||
return message;
|
||||
}
|
||||
48
src/helpers/commands/execute_slash_command.ts
Normal file
48
src/helpers/commands/execute_slash_command.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { SlashCommandResponseOptions } from "../../types/mod.ts";
|
||||
import { cache } from "../../util/cache.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/**
|
||||
* 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: SlashCommandResponseOptions,
|
||||
) {
|
||||
// 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),
|
||||
900000,
|
||||
);
|
||||
|
||||
// If the user wants this as a private message mark it ephemeral
|
||||
if (options.private) {
|
||||
options.data.flags = 64;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
15
src/helpers/commands/get_slash_command.ts
Normal file
15
src/helpers/commands/get_slash_command.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { SlashCommand } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** 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;
|
||||
}
|
||||
16
src/helpers/commands/get_slash_commands.ts
Normal file
16
src/helpers/commands/get_slash_commands.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { SlashCommand } from "../../types/mod.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Fetch all of the global commands for your application. */
|
||||
export async function getSlashCommands(guildID?: string) {
|
||||
const result = (await RequestManager.get(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
)) as SlashCommand[];
|
||||
|
||||
return new Collection(result.map((command) => [command.name, command]));
|
||||
}
|
||||
25
src/helpers/commands/upsert_slash_command.ts
Normal file
25
src/helpers/commands/upsert_slash_command.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { UpsertSlashCommandOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { validateSlashCommands } from "../../util/utils.ts";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
) {
|
||||
validateSlashCommands([options]);
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD_ID(applicationID, guildID, commandID)
|
||||
: endpoints.COMMANDS_ID(applicationID, commandID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
26
src/helpers/commands/upsert_slash_commands.ts
Normal file
26
src/helpers/commands/upsert_slash_commands.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { applicationID } from "../../bot.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { UpsertSlashCommandsOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { validateSlashCommands } from "../../util/utils.ts";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
) {
|
||||
validateSlashCommands(options);
|
||||
|
||||
const result = await RequestManager.put(
|
||||
guildID
|
||||
? endpoints.COMMANDS_GUILD(applicationID, guildID)
|
||||
: endpoints.COMMANDS(applicationID),
|
||||
options,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
27
src/helpers/emojis/create_emoji.ts
Normal file
27
src/helpers/emojis/create_emoji.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { CreateEmojisOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
import { urlToBase64 } from "../../util/utils.ts";
|
||||
|
||||
/** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. */
|
||||
export async function createEmoji(
|
||||
guildID: string,
|
||||
name: string,
|
||||
image: string,
|
||||
options: CreateEmojisOptions,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildID, ["MANAGE_EMOJIS"]);
|
||||
|
||||
if (image && !image.startsWith("data:image/")) {
|
||||
image = await urlToBase64(image);
|
||||
}
|
||||
|
||||
const result = await RequestManager.post(endpoints.GUILD_EMOJIS(guildID), {
|
||||
...options,
|
||||
name,
|
||||
image,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
19
src/helpers/emojis/delete_emoji.ts
Normal file
19
src/helpers/emojis/delete_emoji.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */
|
||||
export async function deleteEmoji(
|
||||
guildID: string,
|
||||
id: string,
|
||||
reason?: string,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildID, ["MANAGE_EMOJIS"]);
|
||||
|
||||
const result = await RequestManager.delete(
|
||||
endpoints.GUILD_EMOJI(guildID, id),
|
||||
{ reason },
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
23
src/helpers/emojis/edit_emoji.ts
Normal file
23
src/helpers/emojis/edit_emoji.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { EditEmojisOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
/** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */
|
||||
export async function editEmoji(
|
||||
guildID: string,
|
||||
id: string,
|
||||
options: EditEmojisOptions,
|
||||
) {
|
||||
await requireBotGuildPermissions(guildID, ["MANAGE_EMOJIS"]);
|
||||
|
||||
const result = await RequestManager.patch(
|
||||
endpoints.GUILD_EMOJI(guildID, id),
|
||||
{
|
||||
name: options.name,
|
||||
roles: options.roles,
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
4
src/helpers/emojis/emoji_url.ts
Normal file
4
src/helpers/emojis/emoji_url.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/** Creates a url to the emoji from the Discord CDN. */
|
||||
export function emojiURL(id: string, animated = false) {
|
||||
return `https://cdn.discordapp.com/emojis/${id}.${animated ? "gif" : "png"}`;
|
||||
}
|
||||
32
src/helpers/emojis/get_emoji.ts
Normal file
32
src/helpers/emojis/get_emoji.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { Emoji, Errors } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/**
|
||||
* Returns an emoji for the given guild and emoji ID.
|
||||
*
|
||||
* ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis
|
||||
*/
|
||||
export async function getEmoji(
|
||||
guildID: string,
|
||||
emojiID: string,
|
||||
addToCache = true,
|
||||
) {
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.GUILD_EMOJI(guildID, emojiID),
|
||||
)) as Emoji;
|
||||
|
||||
if (addToCache) {
|
||||
const guild = await cacheHandlers.get("guilds", guildID);
|
||||
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
|
||||
guild.emojis.set(result.id ?? result.name, result);
|
||||
cacheHandlers.set(
|
||||
"guilds",
|
||||
guildID,
|
||||
guild,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
26
src/helpers/emojis/get_emojis.ts
Normal file
26
src/helpers/emojis/get_emojis.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { Emoji, Errors } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/**
|
||||
* Returns a list of emojis for the given guild.
|
||||
*
|
||||
* ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis
|
||||
*/
|
||||
export async function getEmojis(guildID: string, addToCache = true) {
|
||||
const result = (await RequestManager.get(
|
||||
endpoints.GUILD_EMOJIS(guildID),
|
||||
)) as Emoji[];
|
||||
|
||||
if (addToCache) {
|
||||
const guild = await cacheHandlers.get("guilds", guildID);
|
||||
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
|
||||
|
||||
result.forEach((emoji) => guild.emojis.set(emoji.id ?? emoji.name, emoji));
|
||||
|
||||
cacheHandlers.set("guilds", guildID, guild);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
14
src/helpers/guilds/create_guild.ts
Normal file
14
src/helpers/guilds/create_guild.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { RequestManager } from "../../rest/request_manager.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { CreateGuildPayload, CreateServerOptions } from "../../types/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Create a new guild. Returns a guild object on success. Fires a Guild Create Gateway event. This endpoint can be used only by bots in less than 10 guilds. */
|
||||
export async function createGuild(options: CreateServerOptions) {
|
||||
const guild = (await RequestManager.post(
|
||||
endpoints.GUILDS,
|
||||
options,
|
||||
)) as CreateGuildPayload;
|
||||
|
||||
return structures.createGuildStruct(guild, 0);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user