diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml
deleted file mode 100644
index f34093188..000000000
--- a/.github/workflows/deno.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-# This is a basic workflow to help you get started with Actions
-
-name: Testing/Linting
-
-# Controls when the action will run. Triggers the workflow on push or pull request
-# events but only for the master branch
-on:
- push:
- branches: [master]
- pull_request:
- branches: [master]
-
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
-jobs:
- # This workflow contains a single job called "build"
- build:
- # The type of runner that the job will run on
- runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
- steps:
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - uses: actions/checkout@v2
-
- - name: Setup Deno environment
- uses: denolib/setup-deno@master
-
- - name: Deno Fetch
- run: deno cache mod.ts
-
- - name: Deno Format Check
- run: deno fmt *.ts --check
-
- - name: Deno Format Check src/
- run: deno fmt src/* --check
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 000000000..f16d35b38
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,16 @@
+name: Lint
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: denolib/setup-deno@master
+ - name: Cache the dependencies
+ run: deno cache mod.ts
+ - name: Run format script with --check
+ run: deno fmt --ignore=./docs --check
diff --git a/.github/workflows/nestland.yml b/.github/workflows/nestland.yml
index 6625cc71c..fbb97d5d9 100644
--- a/.github/workflows/nestland.yml
+++ b/.github/workflows/nestland.yml
@@ -1,8 +1,8 @@
name: Ship Nest.Land
on:
- release:
- types: [published]
+ create:
+ ref_type: "tag"
jobs:
release:
@@ -16,5 +16,6 @@ jobs:
- name: Publish module
run: |
- deno run -A --unstable https://x.nest.land/eggs@0.2.1/mod.ts link ${{ secrets.NESTAPIKEY }}
- deno run -A --unstable https://x.nest.land/eggs@0.2.1/mod.ts publish --version ${{ github.event.inputs.tags }}
+ deno install -A --unstable https://x.nest.land/eggs@0.3.2/eggs.ts
+ eggs link ${{ secrets.NESTAPIKEY }}
+ eggs publish --yes --no-check --version $(git describe --tags $(git rev-list --tags --max-count=1))
diff --git a/README.md b/README.md
index 98f27eb16..1f59e2281 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
> Discord API library wrapper in Deno
[](https://discord.gg/J4NqJ72)
-
+

[](https://nest.land/package/Discordeno)
@@ -27,7 +27,7 @@ The instructions below are meant for advanced developers!
Starting with Discordeno is very simple, you can start from scratch without any boilerplates/frameworks: Add this snippet of code into a new TypeScript file:
```typescript
-import StartBot, { sendMessage, Intents } from "https://x.nest.land/Discordeno@9.0.1/mod.ts";
+import StartBot, { sendMessage, Intents } from "https://x.nest.land/Discordeno@9.0.15/mod.ts";
import config from "./config.ts";
StartBot({
@@ -57,4 +57,4 @@ Alternatively, you can use boilerplate template repositories that were created b
## License
-MIT © Skillz4Killz
+[MIT © Skillz4Killz](https://github.com/Skillz4Killz/Discordeno/blob/master/LICENSE)
diff --git a/docs/content/gettingstarted.md b/docs/content/gettingstarted.md
index 6ee051f24..4892df5f2 100644
--- a/docs/content/gettingstarted.md
+++ b/docs/content/gettingstarted.md
@@ -90,7 +90,7 @@ Web-Mystery Tutorials:
- Running a Discord bot written in Deno in Docker
YouTube Tutorials:
-- Coming soon to [NTM Development](https://www.youtube.com/channel/UCkOFck-WCQtolha4NJuK7zA/)
+- [Discordeno Bot Tutorials YouTube series](https://youtu.be/rIph9-BGsuQ)
---
diff --git a/egg.yml b/egg.yml
index 79b0633d1..c25ed8734 100644
--- a/egg.yml
+++ b/egg.yml
@@ -2,7 +2,6 @@ name: Discordeno
description: >-
Discord Deno TypeScript API library wrapper(Officially vetted library by
Discord Team) https://discordeno.netlify.app
-version: 9.0.15
stable: true
entry: mod.ts
repository: 'https://github.com/Skillz4Killz/Discordeno'
diff --git a/src/controllers/cache.ts b/src/controllers/cache.ts
index 55829c14a..a0588c6ac 100644
--- a/src/controllers/cache.ts
+++ b/src/controllers/cache.ts
@@ -93,6 +93,11 @@ export let cacheHandlers = {
return cache[table].has(key);
},
+ /** Get the number of key-value pairs */
+ size: async (table: TableName) => {
+ return cache[table].size;
+ },
+
// Done differently to have overloads
/** Add a key value pair to the cache */
set,
diff --git a/src/controllers/misc.ts b/src/controllers/misc.ts
index 54f60991d..66eb2d94b 100644
--- a/src/controllers/misc.ts
+++ b/src/controllers/misc.ts
@@ -26,8 +26,8 @@ export async function handleInternalReady(
// Triggered on each shard
eventHandlers.shardReady?.(shardID);
if (payload.shard && shardID === payload.shard[1] - 1) {
- // Wait 10 seconds to allow all guild create events to be processed
- await delay(10000);
+ // Wait for 5 seconds to allow all guild create events to be processed
+ await delay(5000);
cache.isReady = true;
eventHandlers.ready?.();
}
diff --git a/src/handlers/channel.ts b/src/handlers/channel.ts
index 37f2e7a9f..fedba0bd1 100644
--- a/src/handlers/channel.ts
+++ b/src/handlers/channel.ts
@@ -1,4 +1,3 @@
-import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { RequestManager } from "../module/requestManager.ts";
import { structures } from "../structures/mod.ts";
@@ -17,10 +16,8 @@ import { Errors } from "../types/errors.ts";
import { PermissionOverwrite } from "../types/guild.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Permissions } from "../types/permission.ts";
-import {
- botHasChannelPermissions,
- calculateBits,
-} from "../utils/permissions.ts";
+import { endpoints } from "../utils/constants.ts";
+import { botHasChannelPermissions } from "../utils/permissions.ts";
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
export function channelOverwriteHasPermission(
@@ -48,16 +45,22 @@ export async function getMessage(
channelID: string,
id: string,
) {
+ const hasViewChannelPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.VIEW_CHANNEL],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.VIEW_CHANNEL])
+ !hasViewChannelPerm
) {
throw new Error(Errors.MISSING_VIEW_CHANNEL);
}
+
+ const hasReadMessageHistoryPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.READ_MESSAGE_HISTORY],
+ );
if (
- !botHasChannelPermissions(
- channelID,
- [Permissions.READ_MESSAGE_HISTORY],
- )
+ !hasReadMessageHistoryPerm
) {
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
}
@@ -77,16 +80,22 @@ export async function getMessages(
| GetMessagesAround
| GetMessages,
) {
+ const hasViewChannelPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.VIEW_CHANNEL],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.VIEW_CHANNEL])
+ !hasViewChannelPerm
) {
throw new Error(Errors.MISSING_VIEW_CHANNEL);
}
+
+ const hasReadMessageHistoryPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.READ_MESSAGE_HISTORY],
+ );
if (
- !botHasChannelPermissions(
- channelID,
- [Permissions.READ_MESSAGE_HISTORY],
- )
+ !hasReadMessageHistoryPerm
) {
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
}
@@ -114,24 +123,34 @@ export async function sendMessage(
content: string | MessageContent,
) {
if (typeof content === "string") content = { content };
+ const hasSendMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.SEND_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.SEND_MESSAGES])
+ !hasSendMessagesPerm
) {
throw new Error(Errors.MISSING_SEND_MESSAGES);
}
+
+ const hasSendTtsMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.SEND_TTS_MESSAGES],
+ );
if (
content.tts &&
- !botHasChannelPermissions(
- channelID,
- [Permissions.SEND_TTS_MESSAGES],
- )
+ !hasSendTtsMessagesPerm
) {
throw new Error(Errors.MISSING_SEND_TTS_MESSAGE);
}
+ const hasEmbedLinksPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.EMBED_LINKS],
+ );
if (
content.embed &&
- !botHasChannelPermissions(channelID, [Permissions.EMBED_LINKS])
+ !hasEmbedLinksPerm
) {
throw new Error(Errors.MISSING_EMBED_LINKS);
}
@@ -165,6 +184,17 @@ export async function sendMessage(
content.mentions.roles = content.mentions.roles.slice(0, 100);
}
}
+
+ if (content.mentions.repliedUser) {
+ if (
+ !(await botHasChannelPermissions(
+ channelID,
+ [Permissions.READ_MESSAGE_HISTORY],
+ ))
+ ) {
+ throw new Error(Errors.MISSING_SEND_MESSAGES);
+ }
+ }
}
const channel = await cacheHandlers.get("channels", channelID);
@@ -180,7 +210,15 @@ export async function sendMessage(
endpoints.CHANNEL_MESSAGES(channelID),
{
...content,
- allowed_mentions: content.mentions,
+ allowed_mentions: content.mentions
+ ? {
+ ...content.mentions,
+ replied_user: content.mentions.repliedUser !== false,
+ }
+ : undefined,
+ message_reference: {
+ message_id: content.replyMessageID,
+ },
},
);
@@ -188,13 +226,17 @@ export async function sendMessage(
}
/** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */
-export function deleteMessages(
+export async function deleteMessages(
channelID: string,
ids: string[],
reason?: string,
) {
+ const hasManageMessages = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])
+ !hasManageMessages
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -215,9 +257,13 @@ export function deleteMessages(
}
/** Gets the invites for this channel. Requires MANAGE_CHANNEL */
-export function getChannelInvites(channelID: string) {
+export async function getChannelInvites(channelID: string) {
+ const hasManagaChannels = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_CHANNELS],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_CHANNELS])
+ !hasManagaChannels
) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -225,12 +271,16 @@ export function getChannelInvites(channelID: string) {
}
/** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */
-export function createInvite(channelID: string, options: CreateInviteOptions) {
+export async function createInvite(
+ channelID: string,
+ options: CreateInviteOptions,
+) {
+ const hasCreateInstantInvitePerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.CREATE_INSTANT_INVITE],
+ );
if (
- !botHasChannelPermissions(
- channelID,
- [Permissions.CREATE_INSTANT_INVITE],
- )
+ !hasCreateInstantInvitePerm
) {
throw new Error(Errors.MISSING_CREATE_INSTANT_INVITE);
}
@@ -238,9 +288,13 @@ export function createInvite(channelID: string, options: CreateInviteOptions) {
}
/** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */
-export function getChannelWebhooks(channelID: string) {
+export async function getChannelWebhooks(channelID: string) {
+ const hasManageWebhooksPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_WEBHOOKS],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_WEBHOOKS])
+ !hasManageWebhooksPerm
) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
@@ -293,12 +347,17 @@ function processEditChannelQueue() {
}
}
-export function editChannel(
+export async function editChannel(
channelID: string,
options: ChannelEditOptions,
+ reason?: string,
) {
+ const hasManageChannelsPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_CHANNELS],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_CHANNELS])
+ !hasManageChannelsPerm
) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -352,7 +411,10 @@ export function editChannel(
return RequestManager.patch(
endpoints.GUILD_CHANNEL(channelID),
- payload,
+ {
+ ...payload,
+ reason,
+ },
);
}
@@ -361,8 +423,12 @@ export async function followChannel(
sourceChannelID: string,
targetChannelID: string,
) {
+ const hasManageWebhooksPerm = await botHasChannelPermissions(
+ targetChannelID,
+ [Permissions.MANAGE_WEBHOOKS],
+ );
if (
- !botHasChannelPermissions(targetChannelID, [Permissions.MANAGE_WEBHOOKS])
+ !hasManageWebhooksPerm
) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -376,3 +442,30 @@ export async function followChannel(
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.permission_overwrites?.every((overwrite) => {
+ const permission = parentChannel.permission_overwrites?.find((ow) =>
+ ow.id === overwrite.id
+ );
+ if (!permission) return false;
+ if (
+ overwrite.allow !== permission.allow || overwrite.deny !== permission.deny
+ ) {
+ return false;
+ }
+
+ return true;
+ });
+}
diff --git a/src/handlers/guild.ts b/src/handlers/guild.ts
index f80780993..ebcdb7e6e 100644
--- a/src/handlers/guild.ts
+++ b/src/handlers/guild.ts
@@ -1,4 +1,3 @@
-import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { identifyPayload } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
@@ -6,6 +5,7 @@ import { requestAllMembers } from "../module/shardingManager.ts";
import { Guild } from "../structures/guild.ts";
import { Member } from "../structures/member.ts";
import { structures } from "../structures/mod.ts";
+import { Template } from "../structures/template.ts";
import { ImageFormats, ImageSize } from "../types/cdn.ts";
import { ChannelCreatePayload, ChannelTypes } from "../types/channel.ts";
import { Errors } from "../types/errors.ts";
@@ -14,16 +14,22 @@ import {
BanOptions,
ChannelCreateOptions,
CreateEmojisOptions,
+ CreateGuildFromTemplate,
+ CreateGuildPayload,
+ CreateGuildTemplate,
CreateRoleOptions,
CreateServerOptions,
EditEmojisOptions,
+ EditGuildTemplate,
EditIntegrationOptions,
FetchMembersOptions,
GetAuditLogsOptions,
GuildEditOptions,
+ GuildTemplate,
PositionSwap,
PruneOptions,
PrunePayload,
+ UpdateGuildPayload,
UserPayload,
} from "../types/guild.ts";
import { MemberCreatePayload } from "../types/member.ts";
@@ -32,6 +38,7 @@ import { Permissions } from "../types/permission.ts";
import { RoleData } from "../types/role.ts";
import { formatImageURL } from "../utils/cdn.ts";
import { Collection } from "../utils/collection.ts";
+import { endpoints } from "../utils/constants.ts";
import { botHasPermission, calculateBits } from "../utils/permissions.ts";
import { urlToBase64 } from "../utils/utils.ts";
@@ -97,7 +104,11 @@ export async function createGuildChannel(
name: string,
options?: ChannelCreateOptions,
) {
- if (!botHasPermission(guild.id, [Permissions.MANAGE_CHANNELS])) {
+ const hasPerm = await botHasPermission(
+ guild.id,
+ [Permissions.MANAGE_CHANNELS],
+ );
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -126,12 +137,16 @@ export async function createGuildChannel(
}
/** Delete a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
-export function deleteChannel(
+export async function deleteChannel(
guildID: string,
channelID: string,
reason?: string,
) {
- if (!botHasPermission(guildID, [Permissions.MANAGE_CHANNELS])) {
+ const hasPerm = await botHasPermission(
+ guildID,
+ [Permissions.MANAGE_CHANNELS],
+ );
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
@@ -186,16 +201,20 @@ export function swapChannels(
*
* ⚠️ **ADVANCED USE ONLY: Your members will be cached in your guild most likely. Only use this when you are absolutely sure the member is not cached.**
*/
-export async function getMember(guildID: string, id: string) {
+export async function getMember(
+ guildID: string,
+ id: string,
+ options?: { force?: boolean },
+) {
const guild = await cacheHandlers.get("guilds", guildID);
- if (!guild) return;
+ if (!guild && !options?.force) return;
const data = await RequestManager.get(
endpoints.GUILD_MEMBER(guildID, id),
) as MemberCreatePayload;
- const member = await structures.createMember(data, guild.id);
- guild.members.set(id, member);
+ const member = await structures.createMember(data, guildID);
+ guild?.members.set(id, member);
return member;
}
@@ -223,9 +242,8 @@ export async function createEmoji(
image: string,
options: CreateEmojisOptions,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_EMOJIS])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_EMOJIS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
@@ -241,16 +259,16 @@ export async function createEmoji(
}
/** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */
-export function editEmoji(
+export async function editEmoji(
guildID: string,
id: string,
options: EditEmojisOptions,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_EMOJIS])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_EMOJIS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
+
return RequestManager.patch(endpoints.GUILD_EMOJI(guildID, id), {
name: options.name,
roles: options.roles,
@@ -258,12 +276,16 @@ export function editEmoji(
}
/** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */
-export function deleteEmoji(guildID: string, id: string, reason?: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_EMOJIS])
- ) {
+export async function deleteEmoji(
+ guildID: string,
+ id: string,
+ reason?: string,
+) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_EMOJIS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
+
return RequestManager.delete(
endpoints.GUILD_EMOJI(guildID, id),
{ reason },
@@ -281,11 +303,11 @@ export async function createGuildRole(
options: CreateRoleOptions,
reason?: string,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
const result = await RequestManager.post(
endpoints.GUILD_ROLES(guildID),
{
@@ -307,16 +329,16 @@ export async function createGuildRole(
}
/** Edit a guild role. Requires the MANAGE_ROLES permission. */
-export function editRole(
+export async function editRole(
guildID: string,
id: string,
options: CreateRoleOptions,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
return RequestManager.patch(endpoints.GUILD_ROLE(guildID, id), {
...options,
permissions: options.permissions
@@ -326,12 +348,12 @@ export function editRole(
}
/** Delete a guild role. Requires the MANAGE_ROLES permission. */
-export function deleteRole(guildID: string, id: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
- ) {
+export async function deleteRole(guildID: string, id: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
return RequestManager.delete(endpoints.GUILD_ROLE(guildID, id));
}
@@ -339,22 +361,22 @@ export function deleteRole(guildID: string, id: string) {
*
* ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your roles will be cached in your guild.**
*/
-export function getRoles(guildID: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
- ) {
+export async function getRoles(guildID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
return RequestManager.get(endpoints.GUILD_ROLES(guildID));
}
/** Modify the positions of a set of role objects for the guild. Requires the MANAGE_ROLES permission. */
-export function swapRoles(guildID: string, rolePositons: PositionSwap) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
- ) {
+export async function swapRoles(guildID: string, rolePositons: PositionSwap) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
return RequestManager.patch(endpoints.GUILD_ROLES(guildID), rolePositons);
}
@@ -363,9 +385,9 @@ export async function getPruneCount(guildID: string, options: PruneOptions) {
if (options.days < 1) {
throw new Error(Errors.PRUNE_MIN_DAYS);
}
- if (
- !botHasPermission(guildID, [Permissions.KICK_MEMBERS])
- ) {
+
+ const hasPerm = await botHasPermission(guildID, [Permissions.KICK_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
@@ -378,13 +400,13 @@ export async function getPruneCount(guildID: string, options: PruneOptions) {
}
/** Begin pruning all members in the given time period */
-export function pruneMembers(guildID: string, options: PruneOptions) {
+export async function pruneMembers(guildID: string, options: PruneOptions) {
if (options.days < 1) {
throw new Error(Errors.PRUNE_MIN_DAYS);
}
- if (
- !botHasPermission(guildID, [Permissions.KICK_MEMBERS])
- ) {
+
+ const hasPerm = await botHasPermission(guildID, [Permissions.KICK_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
@@ -405,8 +427,12 @@ export function fetchMembers(guild: Guild, options?: FetchMembersOptions) {
}
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
-export function getAuditLogs(guildID: string, options: GetAuditLogsOptions) {
- if (!botHasPermission(guildID, [Permissions.VIEW_AUDIT_LOG])) {
+export async function getAuditLogs(
+ guildID: string,
+ options: GetAuditLogsOptions,
+) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.VIEW_AUDIT_LOG]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_VIEW_AUDIT_LOG);
}
@@ -419,26 +445,26 @@ export function getAuditLogs(guildID: string, options: GetAuditLogsOptions) {
}
/** Returns the guild embed object. Requires the MANAGE_GUILD permission. */
-export function getEmbed(guildID: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+export async function getEmbed(guildID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.get(endpoints.GUILD_EMBED(guildID));
}
/** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */
-export function editEmbed(
+export async function editEmbed(
guildID: string,
enabled: boolean,
channelID?: string | null,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.patch(
endpoints.GUILD_EMBED(guildID),
{ enabled, channel_id: channelID },
@@ -451,26 +477,26 @@ export function getVanityURL(guildID: string) {
}
/** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */
-export function getIntegrations(guildID: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+export async function getIntegrations(guildID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.get(endpoints.GUILD_INTEGRATIONS(guildID));
}
/** Modify the behavior and settings of an integration object for the guild. Requires the MANAGE_GUILD permission. */
-export function editIntegration(
+export async function editIntegration(
guildID: string,
id: string,
options: EditIntegrationOptions,
) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.patch(
endpoints.GUILD_INTEGRATION(guildID, id),
options,
@@ -478,30 +504,29 @@ export function editIntegration(
}
/** Delete the attached integration object for the guild with this id. Requires MANAGE_GUILD permission. */
-export function deleteIntegration(guildID: string, id: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+export async function deleteIntegration(guildID: string, id: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.delete(endpoints.GUILD_INTEGRATION(guildID, id));
}
/** Sync an integration. Requires the MANAGE_GUILD permission. */
-export function syncIntegration(guildID: string, id: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+export async function syncIntegration(guildID: string, id: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.post(endpoints.GUILD_INTEGRATION_SYNC(guildID, id));
}
/** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */
export async function getBans(guildID: string) {
- if (
- !botHasPermission(guildID, [Permissions.BAN_MEMBERS])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.BAN_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -515,10 +540,9 @@ export async function getBans(guildID: string) {
}
/** Returns a ban object for the given user or a 404 not found if the ban cannot be found. Requires the BAN_MEMBERS permission. */
-export function getBan(guildID: string, memberID: string) {
- if (
- !botHasPermission(guildID, [Permissions.BAN_MEMBERS])
- ) {
+export async function getBan(guildID: string, memberID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.BAN_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -528,10 +552,9 @@ export function getBan(guildID: string, memberID: string) {
}
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */
-export function ban(guildID: string, id: string, options: BanOptions) {
- if (
- !botHasPermission(guildID, [Permissions.BAN_MEMBERS])
- ) {
+export async function ban(guildID: string, id: string, options: BanOptions) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.BAN_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
@@ -542,10 +565,9 @@ export function ban(guildID: string, id: string, options: BanOptions) {
}
/** Remove the ban for a user. REquires BAN_MEMBERS permission */
-export function unban(guildID: string, id: string) {
- if (
- !botHasPermission(guildID, [Permissions.BAN_MEMBERS])
- ) {
+export async function unban(guildID: string, id: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.BAN_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
return RequestManager.delete(endpoints.GUILD_BAN(guildID, id));
@@ -553,9 +575,8 @@ export function unban(guildID: string, id: string) {
/** Modify a guilds settings. Requires the MANAGE_GUILD permission. */
export async function editGuild(guildID: string, options: GuildEditOptions) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
@@ -575,12 +596,12 @@ export async function editGuild(guildID: string, options: GuildEditOptions) {
}
/** Get all the invites for this guild. Requires MANAGE_GUILD permission */
-export function getInvites(guildID: string) {
- if (
- !botHasPermission(guildID, [Permissions.MANAGE_GUILD])
- ) {
+export async function getInvites(guildID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
+
return RequestManager.get(endpoints.GUILD_INVITES(guildID));
}
@@ -595,8 +616,12 @@ export function getVoiceRegions(guildID: string) {
}
/** Returns a list of guild webhooks objects. Requires the MANAGE_WEBHOOKs permission. */
-export function getWebhooks(guildID: string) {
- if (!botHasPermission(guildID, [Permissions.MANAGE_WEBHOOKS])) {
+export async function getWebhooks(guildID: string) {
+ const hasPerm = await botHasPermission(
+ guildID,
+ [Permissions.MANAGE_WEBHOOKS],
+ );
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
@@ -607,3 +632,158 @@ export function getWebhooks(guildID: string) {
export function getUser(userID: string) {
return RequestManager.get(endpoints.USER(userID)) as Promise;
}
+
+/**
+ * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()
+ *
+ * Advanced Devs:
+ * This function fetches a guild's data. This is not the same data as a GUILD_CREATE.
+ * So it does not cache the guild, you must do it manually.
+ * */
+export function getGuild(guildID: string, counts = true) {
+ return RequestManager.get(
+ endpoints.GUILD(guildID),
+ { with_counts: counts },
+ ) as Promise;
+}
+
+/** Returns the guild template if it exists */
+export function getGuildTemplate(
+ guildID: string,
+ templateCode: string,
+) {
+ return RequestManager.get(
+ `${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
+ ) as Promise;
+}
+
+/**
+ * Create a new guild based on a template
+ * NOTE: This endpoint can be used only by bots in less than 10 guilds.
+ */
+export async function createGuildFromTemplate(
+ templateCode: string,
+ data: CreateGuildFromTemplate,
+) {
+ if (await cacheHandlers.size("guilds") >= 10) {
+ throw new Error(
+ "This function can only be used by bots in less than 10 guilds.",
+ );
+ }
+
+ if (data.icon) {
+ data.icon = await urlToBase64(data.icon);
+ }
+
+ const guild = await RequestManager.post(
+ endpoints.GUILD_TEMPLATE(templateCode),
+ data,
+ ) as Promise;
+ return guild;
+}
+
+/**
+ * Returns an array of templates.
+ * Requires the `MANAGE_GUILD` permission.
+ */
+export async function getGuildTemplates(guildID: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) throw new Error(Errors.MISSING_MANAGE_GUILD);
+
+ const templates = await RequestManager.get(
+ endpoints.GUILD_TEMPLATES(guildID),
+ ) as GuildTemplate[];
+ return templates.map((template) => structures.createTemplate(template));
+}
+
+/**
+ * Deletes a template from a guild.
+ * Requires the `MANAGE_GUILD` permission.
+ */
+export async function deleteGuildTemplate(
+ guildID: string,
+ templateCode: string,
+) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) throw new Error(Errors.MISSING_MANAGE_GUILD);
+
+ const deletedTemplate = await RequestManager.delete(
+ `${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
+ ) as GuildTemplate;
+ return structures.createTemplate(deletedTemplate);
+}
+
+/**
+ * Creates a template for the guild.
+ * Requires the `MANAGE_GUILD` permission.
+ * @param name name of the template (1-100 characters)
+ * @param description description for the template (0-120 characters
+ */
+export async function createGuildTemplate(
+ guildID: string,
+ data: CreateGuildTemplate,
+) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) throw new Error(Errors.MISSING_MANAGE_GUILD);
+
+ if (data.name.length < 1 || data.name.length > 100) {
+ throw new Error("The name can only be in between 1-100 characters.");
+ }
+
+ if (
+ data.description?.length &&
+ data.description.length > 120
+ ) {
+ throw new Error("The description can only be in between 0-120 characters.");
+ }
+
+ const template = await RequestManager.post(
+ endpoints.GUILD_TEMPLATES(guildID),
+ data,
+ ) as GuildTemplate;
+ return structures.createTemplate(template);
+}
+
+/**
+ * Syncs the template to the guild's current state.
+ * Requires the `MANAGE_GUILD` permission.
+ */
+export async function syncGuildTemplate(guildID: string, templateCode: string) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) throw new Error(Errors.MISSING_MANAGE_GUILD);
+
+ const template = await RequestManager.put(
+ `${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
+ ) as GuildTemplate;
+ return structures.createTemplate(template);
+}
+
+/**
+ * Edit a template's metadata.
+ * Requires the `MANAGE_GUILD` permission.
+ */
+export async function editGuildTemplate(
+ guildID: string,
+ templateCode: string,
+ data: EditGuildTemplate,
+) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_GUILD]);
+ if (!hasPerm) throw new Error(Errors.MISSING_MANAGE_GUILD);
+
+ if (data.name?.length && (data.name.length < 1 || data.name.length > 100)) {
+ throw new Error("The name can only be in between 1-100 characters.");
+ }
+
+ if (
+ data.description?.length &&
+ data.description.length > 120
+ ) {
+ throw new Error("The description can only be in between 0-120 characters.");
+ }
+
+ const template = await RequestManager.patch(
+ `${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
+ data,
+ ) as GuildTemplate;
+ return structures.createTemplate(template);
+}
diff --git a/src/handlers/member.ts b/src/handlers/member.ts
index 803a83cec..ad3aa8931 100644
--- a/src/handlers/member.ts
+++ b/src/handlers/member.ts
@@ -1,4 +1,3 @@
-import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { botID } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
@@ -10,11 +9,13 @@ import { Errors } from "../types/errors.ts";
import { EditMemberOptions } from "../types/member.ts";
import { Permissions } from "../types/permission.ts";
import { formatImageURL } from "../utils/cdn.ts";
+import { endpoints } from "../utils/constants.ts";
import {
botHasPermission,
higherRolePosition,
highestRole,
} from "../utils/permissions.ts";
+import { urlToBase64 } from "../utils/utils.ts";
import { sendMessage } from "./channel.ts";
/** The users custom avatar or the default avatar if you don't have a member object. */
@@ -53,14 +54,19 @@ export async function addRole(
reason?: string,
) {
const botsHighestRole = await highestRole(guildID, botID);
- if (
- botsHighestRole &&
- !higherRolePosition(guildID, botsHighestRole.id, roleID)
- ) {
- throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
+ if (botsHighestRole) {
+ const hasHigherRolePosition = await higherRolePosition(
+ guildID,
+ botsHighestRole.id,
+ roleID,
+ );
+ if (!hasHigherRolePosition) {
+ throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
+ }
}
- if (!botHasPermission(guildID, [Permissions.MANAGE_ROLES])) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
@@ -78,16 +84,23 @@ export async function removeRole(
reason?: string,
) {
const botsHighestRole = await highestRole(guildID, botID);
- if (
- botsHighestRole &&
- !higherRolePosition(guildID, botsHighestRole.id, roleID)
- ) {
- throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
+
+ if (botsHighestRole) {
+ const hasHigherRolePosition = await higherRolePosition(
+ guildID,
+ botsHighestRole.id,
+ roleID,
+ );
+ if (!hasHigherRolePosition) {
+ throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
+ }
}
- if (!botHasPermission(guildID, [Permissions.MANAGE_ROLES])) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.MANAGE_ROLES]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
+
return RequestManager.delete(
endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID),
{ reason },
@@ -129,9 +142,11 @@ export async function kick(guildID: string, memberID: string, reason?: string) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
- if (!botHasPermission(guildID, [Permissions.KICK_MEMBERS])) {
+ const hasPerm = await botHasPermission(guildID, [Permissions.KICK_MEMBERS]);
+ if (!hasPerm) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
+
return RequestManager.delete(
endpoints.GUILD_MEMBER(guildID, memberID),
{ reason },
@@ -139,7 +154,7 @@ export async function kick(guildID: string, memberID: string, reason?: string) {
}
/** Edit the member */
-export function editMember(
+export async function editMember(
guildID: string,
memberID: string,
options: EditMemberOptions,
@@ -148,30 +163,47 @@ export function editMember(
if (options.nick.length > 32) {
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
}
- if (!botHasPermission(guildID, [Permissions.MANAGE_NICKNAMES])) {
+
+ const hasManageNickPerm = await botHasPermission(
+ guildID,
+ [Permissions.MANAGE_NICKNAMES],
+ );
+ if (!hasManageNickPerm) {
throw new Error(Errors.MISSING_MANAGE_NICKNAMES);
}
}
+ const hasManageRolesPerm = await botHasPermission(
+ guildID,
+ [Permissions.MANAGE_ROLES],
+ );
if (
options.roles &&
- !botHasPermission(guildID, [Permissions.MANAGE_ROLES])
+ !hasManageRolesPerm
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
if (options.mute) {
+ const hasMuteMembersPerm = await botHasPermission(
+ guildID,
+ [Permissions.MUTE_MEMBERS],
+ );
// TODO: This should check if the member is in a voice channel
if (
- !botHasPermission(guildID, [Permissions.MUTE_MEMBERS])
+ !hasMuteMembersPerm
) {
throw new Error(Errors.MISSING_MUTE_MEMBERS);
}
}
+ const hasDeafenMembersPerm = await botHasPermission(
+ guildID,
+ [Permissions.DEAFEN_MEMBERS],
+ );
if (
options.deaf &&
- !botHasPermission(guildID, [Permissions.DEAFEN_MEMBERS])
+ !hasDeafenMembersPerm
) {
throw new Error(Errors.MISSING_DEAFEN_MEMBERS);
}
@@ -184,7 +216,7 @@ export function editMember(
);
}
-/**
+/**
* 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.
@@ -197,3 +229,34 @@ export function moveMember(
) {
return editMember(guildID, memberID, { channel_id: channelID });
}
+
+/** Modifies the bot's username or avatar.
+ * NOTE: username: if changed may cause the bot's discriminator to be randomized.
+ */
+export function editBotProfile(username?: string, avatarURL?: string) {
+ // Nothing was edited
+ if (!username && !avatarURL) 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);
+ }
+ }
+
+ RequestManager.patch(
+ endpoints.USER_BOT,
+ {
+ username: username?.trim(),
+ avatar: avatarURL ? urlToBase64(avatarURL) : undefined,
+ },
+ );
+}
diff --git a/src/handlers/message.ts b/src/handlers/message.ts
index 243404125..f5f8f5971 100644
--- a/src/handlers/message.ts
+++ b/src/handlers/message.ts
@@ -1,5 +1,4 @@
import { delay } from "../../deps.ts";
-import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { botID } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
@@ -10,6 +9,7 @@ import { Errors } from "../types/errors.ts";
import { UserPayload } from "../types/guild.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Permissions } from "../types/permission.ts";
+import { endpoints } from "../utils/constants.ts";
import { botHasChannelPermissions } from "../utils/permissions.ts";
/** Delete a message with the channel id and message id only. */
@@ -38,11 +38,12 @@ export async function deleteMessage(
) {
if (message.author.id !== botID) {
// This needs to check the channels permission not the guild permission
+ const hasManageMessages = await botHasChannelPermissions(
+ message.channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(
- message.channelID,
- [Permissions.MANAGE_MESSAGES],
- )
+ !hasManageMessages
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -57,9 +58,13 @@ export async function deleteMessage(
}
/** Pin a message in a channel. Requires MANAGE_MESSAGES. Max pins allowed in a channel = 50. */
-export function pin(channelID: string, messageID: string) {
+export async function pin(channelID: string, messageID: string) {
+ const hasManageMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])
+ !hasManageMessagesPerm
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -67,9 +72,13 @@ export function pin(channelID: string, messageID: string) {
}
/** Unpin a message in a channel. Requires MANAGE_MESSAGES. */
-export function unpin(channelID: string, messageID: string) {
+export async function unpin(channelID: string, messageID: string) {
+ const hasManageMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])
+ !hasManageMessagesPerm
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -79,17 +88,25 @@ export function unpin(channelID: string, messageID: string) {
}
/** 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 function addReaction(
+export async function addReaction(
channelID: string,
messageID: string,
reaction: string,
) {
- if (!botHasChannelPermissions(channelID, [Permissions.ADD_REACTIONS])) {
+ const hasAddReactionsPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.ADD_REACTIONS],
+ );
+ if (!hasAddReactionsPerm) {
throw new Error(Errors.MISSING_ADD_REACTIONS);
}
+ const hasReadMessageHistoryPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.READ_MESSAGE_HISTORY],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.READ_MESSAGE_HISTORY])
+ !hasReadMessageHistoryPerm
) {
throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY);
}
@@ -143,13 +160,17 @@ export function removeReaction(
}
/** 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 function removeUserReaction(
+export async function removeUserReaction(
channelID: string,
messageID: string,
reaction: string,
userID: string,
) {
- if (!botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])) {
+ const hasManageMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
+ if (!hasManageMessagesPerm) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -164,9 +185,13 @@ export function removeUserReaction(
}
/** Removes all reactions for all emojis on this message. */
-export function removeAllReactions(channelID: string, messageID: string) {
+export async function removeAllReactions(channelID: string, messageID: string) {
+ const hasManageMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])
+ !hasManageMessagesPerm
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -176,13 +201,17 @@ export function removeAllReactions(channelID: string, messageID: string) {
}
/** 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 function removeReactionEmoji(
+export async function removeReactionEmoji(
channelID: string,
messageID: string,
reaction: string,
) {
+ const hasManageMessagesPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_MESSAGES],
+ );
if (
- !botHasChannelPermissions(channelID, [Permissions.MANAGE_MESSAGES])
+ !hasManageMessagesPerm
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
@@ -216,18 +245,23 @@ export async function editMessage(
if (typeof content === "string") content = { content };
+ const hasSendMessagesPerm = await botHasChannelPermissions(
+ message.channelID,
+ [Permissions.SEND_MESSAGES],
+ );
if (
- !botHasChannelPermissions(message.channelID, [Permissions.SEND_MESSAGES])
+ !hasSendMessagesPerm
) {
throw new Error(Errors.MISSING_SEND_MESSAGES);
}
+ const hasSendTtsMessagesPerm = await botHasChannelPermissions(
+ message.channelID,
+ [Permissions.SEND_TTS_MESSAGES],
+ );
if (
content.tts &&
- !botHasChannelPermissions(
- message.channelID,
- [Permissions.SEND_TTS_MESSAGES],
- )
+ !hasSendTtsMessagesPerm
) {
throw new Error(Errors.MISSING_SEND_TTS_MESSAGE);
}
diff --git a/src/handlers/mod.ts b/src/handlers/mod.ts
new file mode 100644
index 000000000..23ecc45b2
--- /dev/null
+++ b/src/handlers/mod.ts
@@ -0,0 +1,203 @@
+import {
+ channelOverwriteHasPermission,
+ createInvite,
+ deleteMessages,
+ editChannel,
+ followChannel,
+ getChannelInvites,
+ getChannelWebhooks,
+ getMessage,
+ getMessages,
+ getPins,
+ isChannelSynced,
+ sendMessage,
+} from "./channel.ts";
+import {
+ 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,
+ getGuild,
+ getGuildTemplate,
+ getGuildTemplates,
+ getIntegrations,
+ getInvites,
+ getMember,
+ getMembersByQuery,
+ getPruneCount,
+ getRoles,
+ getUser,
+ getVanityURL,
+ getVoiceRegions,
+ getWebhooks,
+ guildBannerURL,
+ guildIconURL,
+ guildSplashURL,
+ leaveGuild,
+ pruneMembers,
+ swapChannels,
+ swapRoles,
+ syncGuildTemplate,
+ syncIntegration,
+ unban,
+} from "./guild.ts";
+import {
+ addRole,
+ avatarURL,
+ editBotProfile,
+ editMember,
+ kick,
+ 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 { createWebhook, executeWebhook, getWebhook } from "./webhook.ts";
+
+export let handlers = {
+ // Channel handler
+ channelOverwriteHasPermission,
+ createInvite,
+ deleteMessages,
+ editChannel,
+ followChannel,
+ getChannelInvites,
+ getChannelWebhooks,
+ getMessage,
+ getMessages,
+ getPins,
+ isChannelSynced,
+ sendMessage,
+
+ // 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,
+ getGuild,
+ getGuildTemplate,
+ getGuildTemplates,
+ getIntegrations,
+ getInvites,
+ getMember,
+ getMembersByQuery,
+ getPruneCount,
+ getRoles,
+ getUser,
+ getVanityURL,
+ getVoiceRegions,
+ getWebhooks,
+ guildBannerURL,
+ guildIconURL,
+ guildSplashURL,
+ leaveGuild,
+ pruneMembers,
+ swapChannels,
+ swapRoles,
+ syncGuildTemplate,
+ syncIntegration,
+ unban,
+
+ // Member handler
+ addRole,
+ avatarURL,
+ editBotProfile,
+ editMember,
+ kick,
+ moveMember,
+ rawAvatarURL,
+ removeRole,
+ sendDirectMessage,
+
+ // Message handler
+ addReaction,
+ addReactions,
+ deleteMessage,
+ deleteMessageByID,
+ editMessage,
+ getReactions,
+ pin,
+ publishMessage,
+ removeAllReactions,
+ removeReaction,
+ removeReactionEmoji,
+ removeUserReaction,
+ unpin,
+
+ // Webhook handler
+ createWebhook,
+ executeWebhook,
+ getWebhook,
+};
+
+export type Handlers = typeof handlers;
+
+export function updateHandlers(newHandlers: Partial) {
+ handlers = {
+ ...handlers,
+ ...newHandlers,
+ };
+}
diff --git a/src/handlers/webhook.ts b/src/handlers/webhook.ts
index 7bb9400b3..12fae6e77 100644
--- a/src/handlers/webhook.ts
+++ b/src/handlers/webhook.ts
@@ -1,4 +1,3 @@
-import { endpoints } from "../constants/discord.ts";
import { RequestManager } from "../module/requestManager.ts";
import { structures } from "../structures/mod.ts";
import { Errors } from "../types/errors.ts";
@@ -9,6 +8,7 @@ import {
WebhookCreateOptions,
WebhookPayload,
} from "../types/webhook.ts";
+import { endpoints } from "../utils/constants.ts";
import { botHasChannelPermissions } from "../utils/permissions.ts";
import { urlToBase64 } from "../utils/utils.ts";
@@ -20,11 +20,12 @@ export async function createWebhook(
channelID: string,
options: WebhookCreateOptions,
) {
+ const hasManageWebhooksPerm = await botHasChannelPermissions(
+ channelID,
+ [Permissions.MANAGE_WEBHOOKS],
+ );
if (
- !botHasChannelPermissions(
- channelID,
- [Permissions.MANAGE_WEBHOOKS],
- )
+ !hasManageWebhooksPerm
) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
diff --git a/src/module/client.ts b/src/module/client.ts
index 01f311460..6b0104da3 100644
--- a/src/module/client.ts
+++ b/src/module/client.ts
@@ -1,6 +1,6 @@
-import { endpoints } from "../constants/discord.ts";
import { DiscordBotGatewayData } from "../types/discord.ts";
import { ClientOptions, EventHandlers } from "../types/options.ts";
+import { endpoints } from "../utils/constants.ts";
import { RequestManager } from "./requestManager.ts";
import { spawnShards } from "./shardingManager.ts";
diff --git a/src/module/requestManager.ts b/src/module/requestManager.ts
index dbbdabd15..80a53fb54 100644
--- a/src/module/requestManager.ts
+++ b/src/module/requestManager.ts
@@ -1,8 +1,8 @@
import { delay } from "../../deps.ts";
-import { baseEndpoints } from "../constants/discord.ts";
import { HttpResponseCode } from "../types/discord.ts";
import { Errors } from "../types/errors.ts";
import { RequestMethods } from "../types/fetch.ts";
+import { baseEndpoints } from "../utils/constants.ts";
import { authorization, eventHandlers } from "./client.ts";
const pathQueues: { [key: string]: QueuedRequest[] } = {};
@@ -64,56 +64,60 @@ async function cleanupQueues() {
}
async function processQueue() {
- if (
- (Object.keys(pathQueues).length) && !globallyRateLimited
- ) {
- await Promise.allSettled(
- Object.values(pathQueues).map(async (pathQueue) => {
- const request = pathQueue.shift();
- if (!request) return;
+ // Putting this code inside a function like this allows us to use tail recursion like a while loop without hitting the max stack error.
+ async function avoidMaxStackError() {
+ if (
+ (Object.keys(pathQueues).length) && !globallyRateLimited
+ ) {
+ await Promise.allSettled(
+ Object.values(pathQueues).map(async (pathQueue) => {
+ const request = pathQueue.shift();
+ if (!request) return;
- const rateLimitedURLResetIn = await checkRatelimits(request.url);
+ const rateLimitedURLResetIn = await checkRatelimits(request.url);
- if (request.bucketID) {
- const rateLimitResetIn = await checkRatelimits(request.bucketID);
- if (rateLimitResetIn) {
- // This request is still rate limited readd to queue
- addToQueue(request);
- } else if (rateLimitedURLResetIn) {
- // This URL is rate limited readd to queue
- addToQueue(request);
+ if (request.bucketID) {
+ const rateLimitResetIn = await checkRatelimits(request.bucketID);
+ if (rateLimitResetIn) {
+ // This request is still rate limited readd to queue
+ addToQueue(request);
+ } else if (rateLimitedURLResetIn) {
+ // This URL is rate limited readd to queue
+ addToQueue(request);
+ } else {
+ // This request is not rate limited so it should be run
+ const result = await request.callback();
+ if (result && result.rateLimited) {
+ addToQueue(
+ { ...request, bucketID: result.bucketID || request.bucketID },
+ );
+ }
+ }
} else {
- // This request is not rate limited so it should be run
- const result = await request.callback();
- if (result && result.rateLimited) {
- addToQueue(
- { ...request, bucketID: result.bucketID || request.bucketID },
- );
+ if (rateLimitedURLResetIn) {
+ // This URL is rate limited readd to queue
+ addToQueue(request);
+ } else {
+ // This request has no bucket id so it should be processed
+ const result = await request.callback();
+ if (request && result && result.rateLimited) {
+ addToQueue(
+ { ...request, bucketID: result.bucketID || request.bucketID },
+ );
+ }
}
}
- } else {
- if (rateLimitedURLResetIn) {
- // This URL is rate limited readd to queue
- addToQueue(request);
- } else {
- // This request has no bucket id so it should be processed
- const result = await request.callback();
- if (request && result && result.rateLimited) {
- addToQueue(
- { ...request, bucketID: result.bucketID || request.bucketID },
- );
- }
- }
- }
- }),
- );
+ }),
+ );
+ }
+
+ if (Object.keys(pathQueues).length) {
+ avoidMaxStackError();
+ cleanupQueues();
+ } else queueInProcess = false;
}
- if (Object.keys(pathQueues).length) {
- await delay(1000);
- processQueue();
- cleanupQueues();
- } else queueInProcess = false;
+ return avoidMaxStackError();
}
processRateLimitedPaths();
@@ -196,7 +200,7 @@ async function runMethod(
},
);
- const errorStack = new Error("Location In Your Files:");
+ const errorStack = new Error("Location:");
Error.captureStackTrace(errorStack);
return new Promise((resolve, reject) => {
@@ -317,17 +321,36 @@ function handleStatusCode(response: Response, errorStack?: unknown) {
switch (status) {
case HttpResponseCode.BadRequest:
+ console.error(
+ "The request was improperly formatted, or the server couldn't understand it.",
+ );
+ throw errorStack;
case HttpResponseCode.Unauthorized:
+ console.error("The Authorization header was missing or invalid.");
+ throw errorStack;
case HttpResponseCode.Forbidden:
+ console.error(
+ "The Authorization token you passed did not have permission to the resource.",
+ );
+ throw errorStack;
case HttpResponseCode.NotFound:
+ console.error("The resource at the location specified doesn't exist.");
+ throw errorStack;
case HttpResponseCode.MethodNotAllowed:
- throw new Error(Errors.REQUEST_CLIENT_ERROR);
+ console.error(
+ "The HTTP method used is not valid for the location specified.",
+ );
+ throw errorStack;
case HttpResponseCode.GatewayUnavailable:
- throw new Error(Errors.REQUEST_SERVER_ERROR);
+ console.error(
+ "There was not a gateway available to process your request. Wait a bit and retry.",
+ );
+ throw errorStack;
+ // left are all unknown
+ default:
+ console.error(Errors.REQUEST_UNKNOWN_ERROR);
+ throw errorStack;
}
-
- // left are all unknown
- throw new Error(Errors.REQUEST_UNKNOWN_ERROR);
}
function processHeaders(url: string, headers: Headers) {
@@ -361,7 +384,7 @@ function processHeaders(url: string, headers: Headers) {
// If there is no remaining global limit, we save it in cache
if (global) {
- const reset = Date.now() + Number(retryAfter);
+ const reset = Date.now() + (Number(retryAfter) * 1000);
eventHandlers.debug?.(
{ type: "globallyRateLimited", data: { url, reset } },
);
diff --git a/src/module/shardingManager.ts b/src/module/shardingManager.ts
index e95ed6c0d..e955bb12e 100644
--- a/src/module/shardingManager.ts
+++ b/src/module/shardingManager.ts
@@ -90,6 +90,7 @@ export async function handleDiscordPayload(
shardID: number,
) {
eventHandlers.raw?.(data);
+ await eventHandlers.dispatchRequirements?.(data, shardID);
switch (data.op) {
case GatewayOpcode.HeartbeatACK:
diff --git a/src/structures/message.ts b/src/structures/message.ts
index 3c2fab5e5..7dd515a01 100644
--- a/src/structures/message.ts
+++ b/src/structures/message.ts
@@ -11,11 +11,14 @@ export async function createMessage(data: MessageCreateOptions) {
webhook_id: webhookID,
message_reference: messageReference,
edited_timestamp: editedTimestamp,
+ referenced_message: referencedMessageID,
...rest
} = data;
const message = {
...rest,
+ /** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */
+ referencedMessageID,
channelID,
guildID: guildID || "",
mentions: data.mentions.map((m) => m.id),
diff --git a/src/structures/mod.ts b/src/structures/mod.ts
index d69713721..abbdea3b7 100644
--- a/src/structures/mod.ts
+++ b/src/structures/mod.ts
@@ -3,6 +3,7 @@ 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 = {
@@ -11,6 +12,7 @@ export let structures = {
createMember,
createMessage,
createRole,
+ createTemplate,
};
export type Structures = typeof structures;
diff --git a/src/structures/template.ts b/src/structures/template.ts
new file mode 100644
index 000000000..59d84a99a
--- /dev/null
+++ b/src/structures/template.ts
@@ -0,0 +1,31 @@
+import { GuildTemplate } from "../types/guild.ts";
+
+export function createTemplate(
+ data: GuildTemplate,
+) {
+ const {
+ usage_count: usageCount,
+ creator_id: creatorID,
+ created_at: createdAt,
+ updated_at: updatedAt,
+ source_guild_id: sourceGuildID,
+ serialized_source_guild: serializedSourceGuild,
+ is_dirty: isDirty,
+ ...rest
+ } = data;
+
+ const template = {
+ ...rest,
+ usageCount,
+ creatorID,
+ createdAt,
+ updatedAt,
+ sourceGuildID,
+ serializedSourceGuild,
+ isDirty,
+ };
+
+ return template;
+}
+
+export interface Template extends ReturnType {}
diff --git a/src/types/channel.ts b/src/types/channel.ts
index d81d8c810..e3881bb8a 100644
--- a/src/types/channel.ts
+++ b/src/types/channel.ts
@@ -101,6 +101,8 @@ export interface MessageContent {
roles?: string[];
/** Array of user_ids to mention (Max size of 100) */
users?: string[];
+ /** Should the message author from the original message be mention. By default this is true. */
+ repliedUser?: boolean;
};
/** The message contents, up to 2000 characters */
content?: string;
@@ -114,6 +116,8 @@ export interface MessageContent {
embed?: Embed;
/** JSON encoded body of any additional request fields. */
payload_json?: string;
+ /** If you want to send a reply message, provide the original message id here */
+ replyMessageID?: string;
}
export interface GetMessages {
diff --git a/src/types/errors.ts b/src/types/errors.ts
index 98df112d2..5ed296520 100644
--- a/src/types/errors.ts
+++ b/src/types/errors.ts
@@ -33,4 +33,8 @@ export enum Errors {
INVALID_WEBHOOK_OPTIONS = "INVALID_WEBHOOK_OPTIONS",
CHANNEL_NOT_FOUND = "CHANNEL_NOT_FOUND",
CHANNEL_NOT_TEXT_BASED = "CHANNEL_NOT_TEXT_BASED",
+ USERNAME_MAX_LENGTH = "USERNAME_MAX_LENGTH",
+ USERNAME_MIN_LENGTH = "USERNAME_MIN_LENGTH",
+ USERNAME_INVALID_CHARACTER = "USERNAME_INVALID_CHARACTER",
+ USERNAME_INVALID_USERNAME = "USERNAME_INVALID_USERNAME",
}
diff --git a/src/types/guild.ts b/src/types/guild.ts
index ba52fedfd..5addee40d 100644
--- a/src/types/guild.ts
+++ b/src/types/guild.ts
@@ -1,3 +1,4 @@
+import { Guild } from "../structures/guild.ts";
import { ChannelCreatePayload, ChannelTypes } from "./channel.ts";
import { Emoji, StatusType } from "./discord.ts";
import { MemberCreatePayload } from "./member.ts";
@@ -612,3 +613,50 @@ export interface CreateServerOptions {
/** the id of the channel where guild notices such as welcome messages and boost events are posted */
system_channel_id?: string;
}
+
+// https://discord.com/developers/docs/resources/template#template-object
+export interface GuildTemplate {
+ /** the template code (unique ID) */
+ code: string;
+ /** template name */
+ name: string;
+ /** the description for the template */
+ description: string | null;
+ /** number of times this template has been used */
+ usage_count: number;
+ /** the ID of the user who created the template */
+ creator_id: string;
+ /** the user who created the template */
+ user: UserPayload;
+ /** when this template was created */
+ created_at: string;
+ /** when this template was last synced to the source guild */
+ updated_at: string;
+ /** the ID of the guild this template is based on */
+ source_guild_id: string;
+ /** the guild snapshot this template contains */
+ serialized_source_guild: Guild;
+ /** whether the template has unsynced changes */
+ is_dirty: boolean | null;
+}
+
+export interface CreateGuildFromTemplate {
+ /** name of the guild (2-100 characters) */
+ name: string;
+ /** base64 128x128 image for the guild icon */
+ icon?: string;
+}
+
+export interface CreateGuildTemplate {
+ /** name of the template (1-100 characters) */
+ name: string;
+ /** description for the template (0-120 characters) */
+ description?: string;
+}
+
+export interface EditGuildTemplate {
+ /** name of the template (1-100 characters) */
+ name?: string;
+ /** description for the template (0-120 characters) */
+ description?: string | null;
+}
diff --git a/src/types/message.ts b/src/types/message.ts
index 1e631d3cc..18c774be0 100644
--- a/src/types/message.ts
+++ b/src/types/message.ts
@@ -154,6 +154,11 @@ export enum MessageTypes {
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
CHANNEL_FOLLOW_ADD,
+ GUILD_DISCOVERY_DISQUALIFIED = 14,
+ GUILD_DISCOVERY_REQUALIFIED,
+ GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
+ GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
+ REPLY = 19,
}
export enum ActivityTypes {
@@ -275,6 +280,8 @@ export interface MessageCreateOptions {
message_reference?: Reference;
/** The message flags combined like permission bits describe extra features of the message */
flags?: 1 | 2 | 4 | 8 | 16;
+ /** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */
+ referenced_message?: MessageCreateOptions | null;
}
export interface BaseMessageDeletePayload {
diff --git a/src/types/options.ts b/src/types/options.ts
index f3c66fc01..75f6f739d 100644
--- a/src/types/options.ts
+++ b/src/types/options.ts
@@ -83,6 +83,7 @@ export interface EventHandlers {
channelUpdate?: (channel: Channel, cachedChannel: Channel) => unknown;
channelDelete?: (channel: Channel) => unknown;
debug?: (args: DebugArg) => unknown;
+ dispatchRequirements?: (data: DiscordPayload, shardID: number) => unknown;
guildBanAdd?: (guild: Guild, user: Member | UserPayload) => unknown;
guildBanRemove?: (guild: Guild, user: Member | UserPayload) => unknown;
guildCreate?: (guild: Guild) => unknown;
diff --git a/src/constants/discord.ts b/src/utils/constants.ts
similarity index 95%
rename from src/constants/discord.ts
rename to src/utils/constants.ts
index 896aedb76..ee1ad8a90 100644
--- a/src/constants/discord.ts
+++ b/src/utils/constants.ts
@@ -82,6 +82,9 @@ export const endpoints = {
`${baseEndpoints.CDN_URL}/splashes/${id}/${icon}`,
GUILD_VANITY_URL: (id: string) => `${GUILDS_BASE(id)}/vanity-url`,
GUILD_WEBHOOKS: (id: string) => `${GUILDS_BASE(id)}/webhooks`,
+ GUILD_TEMPLATE: (code: string) =>
+ `${baseEndpoints.BASE_URL}/guilds/templates/${code}`,
+ GUILD_TEMPLATES: (id: string) => `${GUILDS_BASE(id)}/templates`,
WEBHOOK: (id: string, token: string) =>
`${baseEndpoints.BASE_URL}/webhooks/${id}/${token}`,
@@ -89,6 +92,7 @@ export const endpoints = {
// User endpoints
USER: (id: string) => `${baseEndpoints.BASE_URL}/users/${id}`,
+ USER_BOT: `${baseEndpoints.BASE_URL}/users/@me`,
USER_AVATAR: (id: string, icon: string) =>
`${baseEndpoints.CDN_URL}/avatars/${id}/${icon}`,
USER_DEFAULT_AVATAR: (icon: number) =>
diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts
index 2537f3cf2..c466a48b8 100644
--- a/src/utils/permissions.ts
+++ b/src/utils/permissions.ts
@@ -58,7 +58,8 @@ export async function botHasPermission(
const member = guild.members.get(botID);
if (!member) return false;
- const permissionBits = member.roles
+ // The everyone role is not in member.roles
+ const permissionBits = [...member.roles, guild.id]
.map((id) => guild.roles.get(id)!)
// Remove any edge case undefined
.filter((r) => r)
@@ -182,7 +183,8 @@ export async function hasChannelPermissions(
if (permissions.every((perm) => allowedPermissions.has(perm))) return true;
// Some permission was not explicitly allowed so we default to checking role perms directly
- return botHasPermission(guild.id, permissions);
+ const hasPerms = await botHasPermission(guild.id, permissions);
+ return hasPerms;
}
/** This function converts a bitwise string to permission strings */