This commit is contained in:
ITOH
2021-01-26 09:58:28 +01:00
28 changed files with 753 additions and 257 deletions

View File

@@ -1,69 +1,47 @@
# Fundamental Design Goals
# Contributing
This document serves to outline the overall design goals of the project. Please
see below a list of these fundamentals.
- Read the [style guide](#style-guide).
- Ask for help on the [official Discord server](https:)
- If you are going to work on an issue, mention so in the issue comments before
you start working on the issue.
- If you are going to work on a new feature, create an issue and discuss with
other contributors before you start working on the feature.
- Abide by and heed to
[Discord Developer Terms of Service](https://discord.com/developers/docs/legal)
## Do not allow anything Discord does not permit
## Submitting a Pull Request
Prevent any and all attempts of making user bots. If someone connects and the
client.user is not a bot user then throw an error immediately.
- Give the PR a descriptive title.
Do not support non-bot features like Group DMs or dm calls etc...
Examples of good PR title:
## Prettier Philosophy Regarding Options
- fix(controllers): cache member from INTERACTION_CREATE payload
- docs: improve wording
- feat(handlers): add editGuild() function Examples of bad PR title:
- fix #7123
- update docs
- fix bugs
Avoid options/customizable whenever possible. Always enforce default values.
Except in cases like intents where the user should be able to pick which intents
to listen for.
## Security
Permission checks should be done by the library! We can throw a custom error
that shows which permissions are missing in order to run this request and save
an API call. This will also prevent bots from getting banned due to Missing
Access errors.
Typescript 3.8 provides **TRUE** private props and methods that no one can
access. We will use this to our advantage to truly make a proper API. This isn't
a silly `_` to mark it as a private but the user should NEVER be able to access
it no matter what.
## Functional API
Events emitted by the client, for example the message creation event, should not
emit a `Message` class instance, but instead a _POJO_ (Plain Ol' JavaScript
Object). This will overall make a cleaner and more performant API, while
removing the headaches of extending built-in classes, and inheritance.
Use functions when possible instead of an event emitter to prevent emitter
related memory leak issues or a number of other headaches that arise.
TLDR: Avoid `classes` whenever possible. Avoid `loops` whenever possible(opt for
iterations like .forEach, map reduce, some find etc...)
## Documentation
Use `/** Description here */` comments above all properties and methods to
describe it so that VSC and other good IDE's with intellisense can pick it up
and provide the documentation right inside the IDE preventing a developer from
needing Discord API docs or even Deno documentation.
We should have a step by step guide nonetheless but this is a POST v1 launch. We
should have a template repo to creating a boilerplate bot.
## Backwards Compatibility BS
Backwards compatibility is the death of code. It causes clutter and uglyness to
pile up and makes developers lazier. There will be no such thing as backwards
compatibility reasons in Discordeno. We will always support the latest and
greatest of JS. The end! Users can fork the lib at any commit to keep older
versions until they are ready to update.
That said, we don't expect many things to be changing drastically after v1. As
you can imagine Typescript allows the latest and greatest of JS so we will be
ahead of the curve for years to come.
- Ensure there is a related issue and it is referenced in the pull request text.
- Ensure there are tests that cover the changes.
- Ensure all of the checks (lint and test) are passing.
## Style Guide
Prettier is our style guide. No discussions around styling ever. The options are
set and that is all. When you code let prettier handle the styling. PERIOD!
- Use underscores as a separator in filenames.
- Comply with
[these guidelines for inclusive code](https://chromium.googlesource.com/chromium/src/+/master/styleguide/inclusive_code.md).
- An exported function must not have more than 4 individual parameters, the rest
arguments should be encorporated inside an object as a single parameter.
- Export all interfaces, types, and enums that are used for or inside an
exported entity.
- Every exported entity must be accompanied by a Typedoc (JSDoc without explicit
types) comment block. Ideally, we prefer single line comment block.
- Top-level functions should not use arrow syntax.
- Minimize dependencies; do not make circular imports.
- Utilize functional API wherever possible and avoid usage of ES6 classes.
- Follow
[Convention Over Configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
wherever possible.
- Please follow the
[guidelines for inclusive code](https://chromium.googlesource.com/chromium/src/+/master/styleguide/inclusive_code.md).

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
- name: Build documentation
run: |
cd docs

View File

@@ -9,4 +9,4 @@ jobs:
- name: Run fmt check script
run: deno fmt --check
- name: Run lint script
run: deno lint src/** --unstable
run: deno lint src/** test/** --unstable

View File

@@ -3,9 +3,14 @@ on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
deno: ["v1.x", "nightly"]
steps:
- uses: actions/checkout@v2
- uses: denolib/setup-deno@v2
with:
deno-version: ${{ matrix.deno }}
- name: Cache dependencies
run: deno cache mod.ts
- name: Run test script

View File

@@ -8,21 +8,20 @@
## Features
- **Secure & stable**: Discordeno is comparatively more stable than the other
libraries. One of the greatest issues with almost every library is stability;
types are outdated, less (or minimal) parity with the API, core maintainers
have quit or no longer actively maintain the library, and whatnot. Discordeno,
on the other hand, is actively maintained to ensure great performance and
convenience. Discordeno internally checks all missing permissions before
forwarding a request to the API so that the client does not get
globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic, easy-to-use, versatile,
and efficient. Uses
- **Secure & stable**: Discordeno is secure and stable. One of the greatest
issues with almost every library is stability; types are outdated, less (or
minimal) parity with the API, core maintainers have quit or no longer actively
maintain the library, and whatnot. Discordeno, on the other hand, is actively
maintained to ensure great performance and convenience. Moreover, it
internally checks all missing permissions before forwarding a request to the
Discord API so that the client does not get globally-banned by Discord.
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use,
versatile while being efficient and lightweight. Follows
[Convention Over Configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
design paradigm ― prefers defaults that Discord recommends or the best
configuration for the majority of the users.
design paradigm ― prefers defaults options or values that are recommended by
Discord or the best configuration for the majority of the users.
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming):
This will produce an overall concise and more performant code while removing
Functional API ensures an overall concise yet performant code while removing
the difficulties of extending built-in classes and inheritance.
## Getting Started
@@ -54,12 +53,14 @@ startBot({
Note to developers: don't worry a lot of developers start out programming a
Discord bot as their first project (I did 😉) and it is not so easy to do so.
Discordeno is built considering all the issues that I and a lot of developers
that I personally know had when I first started out coding Discord bots with
existing libraries. If you are a beginner, you can check out these awesome
official and unofficial boilerplates:
Discordeno is designed and built considering all the issues that I and a lot of
developers had when I first started out coding Discord bots with existing
libraries. If you are a beginner, you can check out these awesome official and
unofficial boilerplates:
- [Discordeno Bot Template (official)](https://github.com/Skillz4Killz/Discordeno-bot-template)
- [Discordeno Bot Template (official)](https://github.com/discordeno/discordeno-bot-template)
- [Serverless Slash Commands Template
(official)](https://github.com/slash-commands-bot)
- [Add Your Own!](https://github.com/discordeno/discordeno/pulls)
## Useful Links

View File

@@ -6,7 +6,7 @@ module.exports = {
"/",
"faq",
"gettingstarted",
"djs",
"migrating",
],
},
{

View File

@@ -8,22 +8,22 @@
## Features
- **Secure & stable**: Discordeno is comparatively more stable than the other
libraries. One of the greatest issues with almost every library is stability;
types are outdated, less (or minimal) parity with the API, core maintainers
have quit or no longer actively maintain the library, and whatnot. Discordeno,
on the other hand, is actively maintained to ensure great performance and
convenience. Discordeno internally checks all missing permissions before
forwarding a request to the API so that the client does not get
globally-banned by Discord.
- **Efficient & lightweight**: Discordeno is simplistic, easy-to-use, versatile,
and efficient. Uses
- **Secure & stable**: Discordeno is secure and stable. One of the greatest
issues with almost every library is stability; types are outdated, less (or
minimal) parity with the API, core maintainers have quit or no longer actively
maintain the library, and whatnot. Discordeno, on the other hand, is actively
maintained to ensure great performance and convenience. Moreover, it
internally checks all missing permissions before forwarding a request to the
Discord API so that the client does not get globally-banned by Discord.
- **Simple, Efficient, & Lightweight**: Discordeno is simplistic, easy-to-use,
versatile while being efficient and lightweight. Follows
[Convention Over Configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
design paradigm ― prefers defaults that Discord recommends or the best
configuration for the majority of the users.
design paradigm ― prefers defaults options or values that are recommended by
Discord or the best configuration for the majority of the users.
- [**Functional API**](https://en.wikipedia.org/wiki/Functional_programming):
This will produce an overall concise and more performant code while removing
Functional API ensures an overall concise yet performant code while removing
the difficulties of extending built-in classes and inheritance.
[Learn more about class-free JavaScript](https://dannyfritz.wordpress.com/2014/10/11/class-free-object-oriented-programming/)
## Useful Links

View File

@@ -68,11 +68,8 @@ startBot({
Below you will find youtube playlists that display channels using Discordeno for
their tutorials.
Web-Mystery Tutorials:
- [Making a Discord bot with Deno and
Discordeno](https://web-mystery.com/articles/making-discord-bot-deno-and-discordeno)
- [Running a Discord bot written in Deno in
- [Running a Discord bot written using Deno in
Docker](https://web-mystery.com/articles/running-discord-bot-written-deno-docker)
- YouTube Tutorials:
- [Discordeno Bot Tutorials YouTube series](https://youtu.be/rIph9-BGsuQ)
- [Discordeno Bot Tutorials (YouTube)](https://youtu.be/rIph9-BGsuQ)

View File

@@ -1,12 +1,12 @@
# Migrating
# Migrating from Discord.js
## Migrating from Discord.js
This migration guide is not intended to discredit Discord.js authors/maintainers
or Discord.js itself. In fact, Discord.js is the most popular Node.js library,
admired and praised by a lot of JavaScript developers.
## Finding A Open Source Bot
## Finding an Open-Source Discord Bot
For the purposes of this guide, I wanted to find a moderation bot that is
totally open source to show an example of how to convert the bot to Discordeno.

View File

@@ -70,7 +70,7 @@ export async function handleInternalGuildMemberUpdate(data: DiscordPayload) {
payload.guild_id,
);
await cacheHandlers.set("messages", member.id, member);
await cacheHandlers.set("members", member.id, member);
if (guildMember?.nick !== payload.nick) {
eventHandlers.nicknameUpdate?.(

View File

@@ -75,6 +75,7 @@ export async function getMessage(
const result = await RequestManager.get(
endpoints.CHANNEL_MESSAGE(channelID, id),
) as MessageCreateOptions;
return structures.createMessage(result);
}
@@ -113,6 +114,7 @@ export async function getMessages(
endpoints.CHANNEL_MESSAGES(channelID),
options,
)) as MessageCreateOptions[];
return Promise.all(result.map((res) => structures.createMessage(res)));
}
@@ -121,6 +123,7 @@ 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)));
}
@@ -129,8 +132,10 @@ export async function getPins(channelID: string) {
* 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 function startTyping(channelID: string) {
return RequestManager.post(endpoints.CHANNEL_TYPING(channelID));
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. */
@@ -236,9 +241,9 @@ export async function sendMessage(
message_id: content.replyMessageID,
},
},
);
) as MessageCreateOptions;
return structures.createMessage(result as MessageCreateOptions);
return structures.createMessage(result);
}
/** Delete messages from the channel. 2-100. Requires the MANAGE_MESSAGES permission */
@@ -266,10 +271,15 @@ export async function deleteMessages(
);
}
return RequestManager.post(endpoints.CHANNEL_BULK_DELETE(channelID), {
messages: ids.splice(0, 100),
reason,
});
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 */
@@ -283,7 +293,10 @@ export async function getChannelInvites(channelID: string) {
) {
throw new Error(Errors.MISSING_MANAGE_CHANNELS);
}
return RequestManager.get(endpoints.CHANNEL_INVITES(channelID));
const result = await RequestManager.get(endpoints.CHANNEL_INVITES(channelID));
return result;
}
/** Creates a new invite for this channel. Requires CREATE_INSTANT_INVITE */
@@ -300,14 +313,22 @@ export async function createInvite(
) {
throw new Error(Errors.MISSING_CREATE_INSTANT_INVITE);
}
return RequestManager.post(endpoints.CHANNEL_INVITES(channelID), options);
const result = await RequestManager.post(
endpoints.CHANNEL_INVITES(channelID),
options,
);
return result;
}
/** Returns an invite for the given code. */
export function getInvite(inviteCode: string) {
return RequestManager.get(endpoints.INVITE(inviteCode)) as Promise<
InvitePayload
>;
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 */
@@ -331,9 +352,11 @@ export async function deleteInvite(
}
}
return RequestManager.delete(endpoints.INVITE(inviteCode)) as Promise<
InvitePayload
>;
const result = await RequestManager.delete(
endpoints.INVITE(inviteCode),
);
return result as InvitePayload;
}
/** Gets the webhooks for this channel. Requires MANAGE_WEBHOOKS */
@@ -348,9 +371,11 @@ export async function getChannelWebhooks(channelID: string) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
return RequestManager.get(
const result = await RequestManager.get(
endpoints.CHANNEL_WEBHOOKS(channelID),
) as Promise<WebhookPayload[]>;
);
return result as WebhookPayload[];
}
interface EditChannelRequest {
@@ -460,13 +485,15 @@ export async function editChannel(
),
};
return RequestManager.patch(
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. */

View File

@@ -3,8 +3,10 @@ import { DiscordBotGatewayData } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
/** Get the bots Gateway metadata that can help during the operation of large or sharded bots. */
export function getGatewayBot() {
return RequestManager.get(
export async function getGatewayBot() {
const result = await RequestManager.get(
endpoints.GATEWAY_BOT,
) as Promise<DiscordBotGatewayData>;
);
return result as DiscordBotGatewayData;
}

View File

@@ -40,7 +40,7 @@ import { botHasPermission, calculateBits } from "../../util/permissions.ts";
import { formatImageURL, urlToBase64 } from "../../util/utils.ts";
import { requestAllMembers } from "../../ws/shard_manager.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { Guild, Member, structures, Template } from "../structures/mod.ts";
import { Guild, Member, structures } from "../structures/mod.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 createServer(options: CreateServerOptions) {
@@ -48,13 +48,16 @@ export async function createServer(options: CreateServerOptions) {
endpoints.GUILDS,
options,
) as CreateGuildPayload;
return structures.createGuild(guild, 0);
}
/** Delete a guild permanently. User must be owner. Returns 204 No Content on success. Fires a Guild Delete Gateway event.
*/
export function deleteServer(guildID: string) {
return RequestManager.delete(endpoints.GUILDS_BASE(guildID));
export async function deleteServer(guildID: string) {
const result = await RequestManager.delete(endpoints.GUILDS_BASE(guildID));
return result;
}
/** Gets an array of all the channels ids that are the children of this category. */
@@ -161,7 +164,12 @@ export async function deleteChannel(
throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED);
}
return RequestManager.delete(endpoints.CHANNEL_BASE(channelID), { reason });
const result = await RequestManager.delete(
endpoints.CHANNEL_BASE(channelID),
{ reason },
);
return result;
}
/** Returns a list of guild channel objects.
@@ -172,6 +180,7 @@ 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 channel = await structures.createChannel(res, guildID);
if (addToCache) {
@@ -189,23 +198,28 @@ export async function getChannel(channelID: string, addToCache = true) {
const result = await RequestManager.get(
endpoints.CHANNEL_BASE(channelID),
) as ChannelCreatePayload;
const channel = await structures.createChannel(result, result.guild_id);
if (addToCache) await cacheHandlers.set("channels", channel.id, channel);
return channel;
}
/** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */
export function swapChannels(
export async function swapChannels(
guildID: string,
channelPositions: PositionSwap[],
) {
if (channelPositions.length < 2) {
throw "You must provide at least two channels to be swapped.";
}
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.GUILD_CHANNELS(guildID),
channelPositions,
);
return result;
}
/** Edit the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */
@@ -223,7 +237,7 @@ export async function editChannelOverwrite(
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.put(
const result = await RequestManager.put(
endpoints.CHANNEL_OVERWRITE(channelID, overwriteID),
{
allow: calculateBits(options.allow),
@@ -231,6 +245,8 @@ export async function editChannelOverwrite(
type: options.type,
},
);
return result;
}
/** Delete the channel permission overwrites for a user or role in this channel. Requires `MANAGE_ROLES` permission. */
@@ -247,9 +263,11 @@ export async function deleteChannelOverwrite(
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.CHANNEL_OVERWRITE(channelID, overwriteID),
);
return result;
}
/** Returns a guild member object for the specified user.
@@ -268,7 +286,7 @@ export async function getMember(
endpoints.GUILD_MEMBER(guildID, id),
) as MemberCreatePayload;
return await structures.createMember(data, guildID);
return structures.createMember(data, guildID);
}
/** Returns guild member objects for the specified user by their nickname/username.
@@ -304,11 +322,13 @@ export async function createEmoji(
image = await urlToBase64(image);
}
return RequestManager.post(endpoints.GUILD_EMOJIS(guildID), {
const result = await RequestManager.post(endpoints.GUILD_EMOJIS(guildID), {
...options,
name,
image,
});
return result;
}
/** Modify the given emoji. Requires the MANAGE_EMOJIS permission. */
@@ -322,10 +342,15 @@ export async function editEmoji(
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
return RequestManager.patch(endpoints.GUILD_EMOJI(guildID, id), {
name: options.name,
roles: options.roles,
});
const result = await RequestManager.patch(
endpoints.GUILD_EMOJI(guildID, id),
{
name: options.name,
roles: options.roles,
},
);
return result;
}
/** Delete the given emoji. Requires the MANAGE_EMOJIS permission. Returns 204 No Content on success. */
@@ -339,10 +364,12 @@ export async function deleteEmoji(
throw new Error(Errors.MISSING_MANAGE_EMOJIS);
}
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.GUILD_EMOJI(guildID, id),
{ reason },
);
return result;
}
/** Creates a url to the emoji from the Discord CDN. */
@@ -422,6 +449,7 @@ export async function createGuildRole(
const role = await structures.createRole(roleData);
const guild = await cacheHandlers.get("guilds", guildID);
guild?.roles.set(role.id, role);
return role;
}
@@ -436,12 +464,14 @@ export async function editRole(
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.patch(endpoints.GUILD_ROLE(guildID, id), {
const result = await RequestManager.patch(endpoints.GUILD_ROLE(guildID, id), {
...options,
permissions: options.permissions
? calculateBits(options.permissions)
: undefined,
});
return result;
}
/** Delete a guild role. Requires the MANAGE_ROLES permission. */
@@ -451,7 +481,9 @@ export async function deleteRole(guildID: string, id: string) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(endpoints.GUILD_ROLE(guildID, id));
const result = await RequestManager.delete(endpoints.GUILD_ROLE(guildID, id));
return result;
}
/** Returns a list of role objects for the guild.
@@ -464,7 +496,9 @@ export async function getRoles(guildID: string) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.get(endpoints.GUILD_ROLES(guildID));
const result = await RequestManager.get(endpoints.GUILD_ROLES(guildID));
return result;
}
/** Modify the positions of a set of role objects for the guild. Requires the MANAGE_ROLES permission. */
@@ -474,7 +508,12 @@ export async function swapRoles(guildID: string, rolePositons: PositionSwap) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.patch(endpoints.GUILD_ROLES(guildID), rolePositons);
const result = await RequestManager.patch(
endpoints.GUILD_ROLES(guildID),
rolePositons,
);
return result;
}
/** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */
@@ -505,10 +544,12 @@ export async function pruneMembers(guildID: string, options: PruneOptions) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
return RequestManager.post(
const result = await RequestManager.post(
endpoints.GUILD_PRUNE(guildID),
{ ...options, include_roles: options.roles.join(",") },
);
return result;
}
/**
@@ -547,7 +588,7 @@ export async function getAuditLogs(
throw new Error(Errors.MISSING_VIEW_AUDIT_LOG);
}
return RequestManager.get(endpoints.GUILD_AUDIT_LOGS(guildID), {
const result = await RequestManager.get(endpoints.GUILD_AUDIT_LOGS(guildID), {
...options,
action_type: options.action_type
? AuditLogs[options.action_type]
@@ -556,6 +597,8 @@ export async function getAuditLogs(
? options.limit
: 50,
});
return result;
}
/** Returns the guild embed object. Requires the MANAGE_GUILD permission. */
@@ -565,7 +608,9 @@ export async function getEmbed(guildID: string) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.get(endpoints.GUILD_WIDGET(guildID));
const result = await RequestManager.get(endpoints.GUILD_WIDGET(guildID));
return result;
}
/** Modify a guild embed object for the guild. Requires the MANAGE_GUILD permission. */
@@ -579,15 +624,19 @@ export async function editEmbed(
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.GUILD_WIDGET(guildID),
{ enabled, channel_id: channelID },
);
return result;
}
/** Returns the code and uses of the vanity url for this server if it is enabled. Requires the MANAGE_GUILD permission. */
export function getVanityURL(guildID: string) {
return RequestManager.get(endpoints.GUILD_VANITY_URL(guildID));
export async function getVanityURL(guildID: string) {
const result = await RequestManager.get(endpoints.GUILD_VANITY_URL(guildID));
return result;
}
/** Returns a list of integrations for the guild. Requires the MANAGE_GUILD permission. */
@@ -597,7 +646,11 @@ export async function getIntegrations(guildID: string) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.get(endpoints.GUILD_INTEGRATIONS(guildID));
const result = await RequestManager.get(
endpoints.GUILD_INTEGRATIONS(guildID),
);
return result;
}
/** Modify the behavior and settings of an integration object for the guild. Requires the MANAGE_GUILD permission. */
@@ -611,10 +664,12 @@ export async function editIntegration(
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.GUILD_INTEGRATION(guildID, id),
options,
);
return result;
}
/** Delete the attached integration object for the guild with this id. Requires MANAGE_GUILD permission. */
@@ -624,7 +679,11 @@ export async function deleteIntegration(guildID: string, id: string) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.delete(endpoints.GUILD_INTEGRATION(guildID, id));
const result = await RequestManager.delete(
endpoints.GUILD_INTEGRATION(guildID, id),
);
return result;
}
/** Sync an integration. Requires the MANAGE_GUILD permission. */
@@ -634,7 +693,11 @@ export async function syncIntegration(guildID: string, id: string) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.post(endpoints.GUILD_INTEGRATION_SYNC(guildID, id));
const result = await RequestManager.post(
endpoints.GUILD_INTEGRATION_SYNC(guildID, id),
);
return result;
}
/** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */
@@ -660,9 +723,11 @@ export async function getBan(guildID: string, memberID: string) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
return await RequestManager.get(
const result = await RequestManager.get(
endpoints.GUILD_BAN(guildID, memberID),
) as Promise<BannedUser>;
);
return result as BannedUser;
}
/** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */
@@ -672,10 +737,12 @@ export async function ban(guildID: string, id: string, options: BanOptions) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
return RequestManager.put(
const result = await RequestManager.put(
endpoints.GUILD_BAN(guildID, id),
{ ...options, delete_message_days: options.days },
);
return result;
}
/** Remove the ban for a user. Requires BAN_MEMBERS permission */
@@ -684,12 +751,17 @@ export async function unban(guildID: string, id: string) {
if (!hasPerm) {
throw new Error(Errors.MISSING_BAN_MEMBERS);
}
return RequestManager.delete(endpoints.GUILD_BAN(guildID, id));
const result = await RequestManager.delete(endpoints.GUILD_BAN(guildID, id));
return result;
}
/** Returns the guild preview object for the given id. If the bot is not in the guild, then the guild must be Discoverable. */
export function getGuildPreview(guildID: string) {
return RequestManager.get(endpoints.GUILD_PREVIEW(guildID));
export async function getGuildPreview(guildID: string) {
const result = await RequestManager.get(endpoints.GUILD_PREVIEW(guildID));
return result;
}
/** Modify a guilds settings. Requires the MANAGE_GUILD permission. */
@@ -711,7 +783,12 @@ export async function editGuild(guildID: string, options: GuildEditOptions) {
options.splash = await urlToBase64(options.splash);
}
return RequestManager.patch(endpoints.GUILDS_BASE(guildID), options);
const result = await RequestManager.patch(
endpoints.GUILDS_BASE(guildID),
options,
);
return result;
}
/** Get all the invites for this guild. Requires MANAGE_GUILD permission */
@@ -721,22 +798,30 @@ export async function getInvites(guildID: string) {
throw new Error(Errors.MISSING_MANAGE_GUILD);
}
return RequestManager.get(endpoints.GUILD_INVITES(guildID));
const result = await RequestManager.get(endpoints.GUILD_INVITES(guildID));
return result;
}
/** Leave a guild */
export function leaveGuild(guildID: string) {
return RequestManager.delete(endpoints.GUILD_LEAVE(guildID));
export async function leaveGuild(guildID: string) {
const result = await RequestManager.delete(endpoints.GUILD_LEAVE(guildID));
return result;
}
/** Returns an array of voice regions that can be used when creating servers. */
export function getAvailableVoiceRegions() {
return RequestManager.get(endpoints.VOICE_REGIONS);
export async function getAvailableVoiceRegions() {
const result = await RequestManager.get(endpoints.VOICE_REGIONS);
return result;
}
/** Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when the guild is VIP-enabled. */
export function getVoiceRegions(guildID: string) {
return RequestManager.get(endpoints.GUILD_REGIONS(guildID));
export async function getVoiceRegions(guildID: string) {
const result = await RequestManager.get(endpoints.GUILD_REGIONS(guildID));
return result;
}
/** Returns a list of guild webhooks objects. Requires the MANAGE_WEBHOOKs permission. */
@@ -749,12 +834,18 @@ export async function getWebhooks(guildID: string) {
throw new Error(Errors.MISSING_MANAGE_WEBHOOKS);
}
return RequestManager.get(endpoints.GUILD_WEBHOOKS(guildID));
const result = await RequestManager.get(endpoints.GUILD_WEBHOOKS(guildID));
return result;
}
/** This function will return the raw user payload in the rare cases you need to fetch a user directly from the API. */
export function getUser(userID: string) {
return RequestManager.get(endpoints.USER(userID)) as Promise<UserPayload>;
export async function getUser(userID: string) {
const result = await RequestManager.get(
endpoints.USER(userID),
);
return result as UserPayload;
}
/**
@@ -764,11 +855,13 @@ export function getUser(userID: string) {
* 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(
export async function getGuild(guildID: string, counts = true) {
const result = await RequestManager.get(
endpoints.GUILDS_BASE(guildID),
{ with_counts: counts },
) as Promise<UpdateGuildPayload>;
);
return result as UpdateGuildPayload;
}
/** Returns the guild template if it exists */
@@ -777,6 +870,7 @@ export async function getTemplate(templateCode: string) {
endpoints.GUILD_TEMPLATE(templateCode),
) as GuildTemplate;
const template = await structures.createTemplate(result);
return template;
}
@@ -809,10 +903,12 @@ export async function createGuildFromTemplate(
data.icon = await urlToBase64(data.icon);
}
return await RequestManager.post(
const result = await await RequestManager.post(
endpoints.GUILD_TEMPLATE(templateCode),
data,
) as Promise<CreateGuildPayload>;
);
return result as CreateGuildPayload;
}
/**
@@ -826,6 +922,7 @@ export async function getGuildTemplates(guildID: string) {
const templates = await RequestManager.get(
endpoints.GUILD_TEMPLATES(guildID),
) as GuildTemplate[];
return templates.map((template) => structures.createTemplate(template));
}
@@ -843,6 +940,7 @@ export async function deleteGuildTemplate(
const deletedTemplate = await RequestManager.delete(
`${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
) as GuildTemplate;
return structures.createTemplate(deletedTemplate);
}
@@ -874,6 +972,7 @@ export async function createGuildTemplate(
endpoints.GUILD_TEMPLATES(guildID),
data,
) as GuildTemplate;
return structures.createTemplate(template);
}
@@ -888,6 +987,7 @@ export async function syncGuildTemplate(guildID: string, templateCode: string) {
const template = await RequestManager.put(
`${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
) as GuildTemplate;
return structures.createTemplate(template);
}
@@ -918,5 +1018,6 @@ export async function editGuildTemplate(
`${endpoints.GUILD_TEMPLATES(guildID)}/${templateCode}`,
data,
) as GuildTemplate;
return structures.createTemplate(template);
}

View File

@@ -75,10 +75,12 @@ export async function addRole(
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.put(
const result = await RequestManager.put(
endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID),
{ reason },
);
return result;
}
/** Remove a role from the member */
@@ -109,10 +111,12 @@ export async function removeRole(
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(
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. */
@@ -157,10 +161,12 @@ export async function kick(guildID: string, memberID: string, reason?: string) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.GUILD_MEMBER(guildID, memberID),
{ reason },
);
return result;
}
/** Edit the member */
@@ -220,10 +226,12 @@ export async function editMember(
// TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.GUILD_MEMBER(guildID, memberID),
options,
);
return result;
}
/**
@@ -268,13 +276,15 @@ export async function editBotProfile(username?: string, botAvatarURL?: string) {
}
const avatar = botAvatarURL ? await urlToBase64(botAvatarURL) : undefined;
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.USER_BOT,
{
username: username?.trim(),
avatar,
},
);
return result;
}
/** Edit the nickname of the bot in this guild */

View File

@@ -24,10 +24,12 @@ export async function deleteMessageByID(
if (delayMilliseconds) await delay(delayMilliseconds);
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.CHANNEL_MESSAGE(channelID, messageID),
{ reason },
);
return result;
}
/** Delete a message */
@@ -51,10 +53,12 @@ export async function deleteMessage(
if (delayMilliseconds) await delay(delayMilliseconds);
return RequestManager.delete(
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. */
@@ -69,7 +73,11 @@ export async function pin(channelID: string, messageID: string) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
return RequestManager.put(endpoints.CHANNEL_PIN(channelID, messageID));
const result = await RequestManager.put(
endpoints.CHANNEL_PIN(channelID, messageID),
);
return result;
}
/** Unpin a message in a channel. Requires MANAGE_MESSAGES. */
@@ -84,9 +92,11 @@ export async function unpin(channelID: string, messageID: string) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
return RequestManager.delete(
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 */
@@ -119,15 +129,18 @@ export async function addReaction(
reaction = reaction.substring(3, reaction.length - 1);
}
return RequestManager.put(
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,
@@ -147,7 +160,7 @@ export async function addReactions(
}
/** Removes a reaction from the bot on this message. Reaction takes the form of **name:id** for custom guild emoji, or Unicode characters. */
export function removeReaction(
export async function removeReaction(
channelID: string,
messageID: string,
reaction: string,
@@ -158,13 +171,15 @@ export function removeReaction(
reaction = reaction.substring(3, reaction.length - 1);
}
return RequestManager.delete(
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. */
@@ -188,7 +203,7 @@ export async function removeUserReaction(
reaction = reaction.substring(3, reaction.length - 1);
}
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.CHANNEL_MESSAGE_REACTION_USER(
channelID,
messageID,
@@ -196,6 +211,8 @@ export async function removeUserReaction(
userID,
),
);
return result;
}
/** Removes all reactions for all emojis on this message. */
@@ -209,9 +226,12 @@ export async function removeAllReactions(channelID: string, messageID: string) {
) {
throw new Error(Errors.MISSING_MANAGE_MESSAGES);
}
return RequestManager.delete(
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. */
@@ -236,9 +256,11 @@ export async function removeReactionEmoji(
reaction = reaction.substring(3, reaction.length - 1);
}
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction),
);
return result;
}
/** Get a list of users that reacted with this emoji. */
@@ -295,6 +317,7 @@ export async function editMessage(
endpoints.CHANNEL_MESSAGE(message.channelID, message.id),
content,
);
return structures.createMessage(result as MessageCreateOptions);
}

View File

@@ -120,6 +120,7 @@ import {
getSlashCommands,
getWebhook,
upsertSlashCommand,
upsertSlashCommands,
} from "./webhook.ts";
export let handlers = {
@@ -241,6 +242,7 @@ export let handlers = {
getSlashCommand,
getSlashCommands,
upsertSlashCommand,
upsertSlashCommands,
editSlashCommand,
deleteSlashCommand,
executeSlashCommand,

View File

@@ -3,8 +3,10 @@ import { OAuthApplication } from "../../types/oauth.ts";
import { endpoints } from "../../util/constants.ts";
/** Returns the bot's OAuth2 application object without `flags`. */
export function getApplicationInformation() {
return RequestManager.get(endpoints.OAUTH2_APPLICATION) as Promise<
OAuthApplication
>;
export async function getApplicationInformation() {
const result = await RequestManager.get(
endpoints.OAUTH2_APPLICATION,
);
return result as OAuthApplication;
}

View File

@@ -11,7 +11,9 @@ import {
MessageCreateOptions,
SlashCommand,
UpsertSlashCommandOptions,
UpsertSlashCommandsOptions,
WebhookCreateOptions,
WebhookEditOptions,
WebhookPayload,
} from "../../types/mod.ts";
import { cache } from "../../util/cache.ts";
@@ -48,13 +50,98 @@ export async function createWebhook(
throw new Error(Errors.INVALID_WEBHOOK_NAME);
}
return RequestManager.post(
const result = await RequestManager.post(
endpoints.CHANNEL_WEBHOOKS(channelID),
{
...options,
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
},
) as Promise<WebhookPayload>;
);
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 */
@@ -116,12 +203,7 @@ export async function executeWebhook(
return structures.createMessage(result as MessageCreateOptions);
}
/** Returns the new webhook object for the given id. */
export function getWebhook(webhookID: string) {
return RequestManager.get(endpoints.WEBHOOK_ID(webhookID));
}
export function editWebhookMessage(
export async function editWebhookMessage(
webhookID: string,
webhookToken: string,
messageID: string,
@@ -167,20 +249,24 @@ export function editWebhookMessage(
}
}
return RequestManager.patch(
const result = await RequestManager.patch(
endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken, messageID),
{ ...options, allowed_mentions: options.allowed_mentions },
);
return result;
}
export function deleteWebhookMessage(
export async function deleteWebhookMessage(
webhookID: string,
webhookToken: string,
messageID: string,
) {
return RequestManager.delete(
const result = await RequestManager.delete(
endpoints.WEBHOOK_MESSAGE(webhookID, webhookToken, messageID),
);
return result;
}
/**
@@ -194,7 +280,7 @@ export function deleteWebhookMessage(
* 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 function createSlashCommand(options: CreateSlashCommandOptions) {
export async function createSlashCommand(options: CreateSlashCommandOptions) {
// Use ... for content length due to unicode characters and js .length handling
if ([...options.name].length < 2 || [...options.name].length > 32) {
throw new Error(Errors.INVALID_SLASH_NAME);
@@ -206,7 +292,7 @@ export function createSlashCommand(options: CreateSlashCommandOptions) {
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
}
return RequestManager.post(
const result = await RequestManager.post(
options.guildID
? endpoints.COMMANDS_GUILD(applicationID, options.guildID)
: endpoints.COMMANDS(applicationID),
@@ -214,6 +300,8 @@ export function createSlashCommand(options: CreateSlashCommandOptions) {
...options,
},
);
return result;
}
/** Fetchs the global command for the given ID. If a guildID is provided, the guild command will be fetched. */
@@ -228,19 +316,21 @@ export async function getSlashCommand(commandID: string, guildID?: string) {
}
/** Fetch all of the global commands for your application. */
export function getSlashCommands(guildID?: string) {
export async function getSlashCommands(guildID?: string) {
// TODO: Should this be a returned as a collection?
return RequestManager.get(
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 function upsertSlashCommand(
export async function upsertSlashCommand(
commandID: string,
options: UpsertSlashCommandOptions,
guildID?: string,
@@ -256,7 +346,7 @@ export function upsertSlashCommand(
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
}
const result = RequestManager.patch(
const result = await RequestManager.patch(
guildID
? endpoints.COMMANDS_GUILD_ID(
applicationID,
@@ -270,12 +360,46 @@ export function upsertSlashCommand(
return result;
}
/**
* Bulk edit existing slash commands. If a command does not exist, it will create it.
*
* **NOTE:** Any slash commands that are not specified in this function will be **deleted**. If you don't provide the commandID and rename your command, the command gets a new ID.
*/
export async function upsertSlashCommands(
options: UpsertSlashCommandsOptions[],
guildID?: string,
) {
const data = options.map((option) => {
// Use ... for content length due to unicode characters and js .length handling
if ([...option.name].length < 2 || [...option.name].length > 32) {
throw new Error(Errors.INVALID_SLASH_NAME);
}
if (
[...option.description].length < 1 || [...option.description].length > 100
) {
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
}
return option;
});
const result = await RequestManager.put(
guildID
? endpoints.COMMANDS_GUILD(applicationID, guildID)
: endpoints.COMMANDS(applicationID),
data,
);
return result;
}
// TODO: remove this function for v11
/**
* Edit an existing slash command.
* @deprecated This function will be removed in v11. Use `upsertSlashCommand()` instead
*/
export function editSlashCommand(
export async function editSlashCommand(
commandID: string,
options: EditSlashCommandOptions,
guildID?: string,
@@ -291,7 +415,7 @@ export function editSlashCommand(
throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
}
const result = RequestManager.patch(
const result = await RequestManager.patch(
guildID
? endpoints.COMMANDS_GUILD_ID(
applicationID,
@@ -321,7 +445,7 @@ export function deleteSlashCommand(id: string, guildID?: string) {
*
* NOTE: By default we will suppress mentions. To enable mentions, just pass any mentions object.
*/
export function executeSlashCommand(
export async function executeSlashCommand(
id: string,
token: string,
options: ExecuteSlashCommandOptions,
@@ -345,33 +469,83 @@ export function executeSlashCommand(
options.data.allowed_mentions = { parse: [] };
}
return RequestManager.post(endpoints.INTERACTION_ID_TOKEN(id, token), {
...options,
});
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 function deleteSlashResponse(
export async function deleteSlashResponse(
token: string,
messageID?: string,
) {
if (!messageID) {
return RequestManager.delete(
endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationID, token),
);
}
return RequestManager.delete(
endpoints.INTERACTION_ID_TOKEN_MESSAGEID(applicationID, token, messageID),
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 function editSlashResponse(
export async function editSlashResponse(
token: string,
options: EditSlashResponseOptions,
) {
return RequestManager.patch(
endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationID, token),
if (options.content && options.content.length > 2000) {
throw Error(Errors.MESSAGE_MAX_LENGTH);
}
if (options.embeds && options.embeds.length > 10) {
options.embeds.splice(10);
}
if (options.allowed_mentions) {
if (options.allowed_mentions.users?.length) {
if (options.allowed_mentions.parse.includes("users")) {
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
p,
) => p !== "users");
}
if (options.allowed_mentions.users.length > 100) {
options.allowed_mentions.users = options.allowed_mentions.users.slice(
0,
100,
);
}
}
if (options.allowed_mentions.roles?.length) {
if (options.allowed_mentions.parse.includes("roles")) {
options.allowed_mentions.parse = options.allowed_mentions.parse.filter((
p,
) => p !== "roles");
}
if (options.allowed_mentions.roles.length > 100) {
options.allowed_mentions.roles = options.allowed_mentions.roles.slice(
0,
100,
);
}
}
}
const result = await RequestManager.patch(
options.messageID
? endpoints.WEBHOOK_MESSAGE(applicationID, token, options.messageID)
: endpoints.INTERACTION_ORIGINAL_ID_TOKEN(applicationID, token),
options,
);
return result;
}

View File

@@ -210,7 +210,7 @@ export async function createGuild(data: CreateGuildPayload, shardID: number) {
initialMemberLoadQueue.set(guild.id, members);
return guild;
return guild as Guild;
}
export interface Guild {

View File

@@ -126,7 +126,7 @@ export async function createMember(data: MemberCreatePayload, guildID: string) {
await cacheHandlers.set("members", member.id, member);
return member;
return member as Member;
}
export interface Member {

View File

@@ -40,7 +40,7 @@ export function createTemplate(
sourceGuildID: createNewProp(sourceGuildID),
serializedSourceGuild: createNewProp(serializedSourceGuild),
isDirty: createNewProp(isDirty),
});
}) as Template;
}
export interface Template {

View File

@@ -423,5 +423,16 @@ function processHeaders(url: string, headers: Headers) {
}
}
if (ratelimited) {
eventHandlers.rateLimit?.({
remaining,
bucketID,
global,
resetTimestamp,
retryAfter,
url,
});
}
return ratelimited ? bucketID : undefined;
}

View File

@@ -72,7 +72,23 @@ export interface DebugArg {
data: unknown;
}
interface RateLimitData {
/** The number of remaining requests that can be made */
remaining: string | null;
/** Epoch time (seconds since 00:00:00 UTC on January 1, 1970) at which the rate limit resets */
resetTimestamp: string | null;
/** Total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond ratelimit precision */
retryAfter: string | null;
/** Returned only on a HTTP 429 response if the rate limit headers returned are of the global rate limit (not per-route) */
global: string | null;
/** A unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path) */
bucketID: string | null;
/** The URL the HTTP request is made to */
url: string;
}
export interface EventHandlers {
rateLimit?: (data: RateLimitData) => unknown;
applicationCommandCreate?: (data: Application) => unknown;
/** Sent when properties about the user change. */
botUpdate?: (user: UserPayload) => unknown;
@@ -126,7 +142,9 @@ export interface EventHandlers {
) => unknown;
heartbeat?: () => unknown;
/** Sent when a user in a guild uses a Slash Command. */
interactionCreate?: (data: InteractionCommandPayload) => unknown;
interactionCreate?: (
data: Omit<InteractionCommandPayload, "member"> & { member: Member },
) => unknown;
/** Sent when a message is created. */
messageCreate?: (message: Message) => unknown;
/** Sent when a message is deleted. */

View File

@@ -36,6 +36,15 @@ export interface WebhookCreateOptions {
avatar?: string;
}
export interface WebhookEditOptions {
/** Name of the webhook (1-80 characters) */
name?: string;
/** Image url for avatar image for the default webhook avatar */
avatar?: string | null;
/** The new channel id this webhook should be moved to */
channelID?: string;
}
export interface ExecuteWebhookOptions {
/** waits for server confirmation of message send before response, and returns the created message body (defaults to false; when false a message that is not saved does not return an error) */
wait?: boolean;
@@ -228,3 +237,14 @@ export interface UpsertSlashCommandOptions {
/** The parameters for the command */
options?: SlashCommandOption[];
}
export interface UpsertSlashCommandsOptions {
/** The id of the command */
id: string;
/** 3-32 character command name */
name: string;
/** 1-100 character description */
description: string;
/** The parameters for the command */
options?: SlashCommandOption[];
}

View File

@@ -55,19 +55,14 @@ export async function botHasPermission(
const member = await cacheHandlers.get("members", botID);
if (!member) return false;
const guild = member.guild(guildID);
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) return false;
// The owner of a guild has all permissions, therefore, if the bot is the owner of the guild, permissions do not need to be inspected.
// Check if the bot is the owner of the guild, if it is, returns true
if (guild.ownerID === botID) return true;
const guildMember = member.guilds.get(
guildID,
);
if (!guildMember) return false;
// The everyone role is not in member.roles
const permissionBits = [...guildMember.roles, guild.id]
const permissionBits = [...member.guilds.get(guildID)?.roles || [], guild.id]
.map((id) => guild.roles.get(id)!)
// Remove any edge case undefined
.filter((r) => r)

View File

@@ -60,3 +60,57 @@ export const formatImageURL = (
return `${url}.${format ||
(url.includes("/a_") ? "gif" : "jpg")}?size=${size}`;
};
function camelToSnakeCase(text: string) {
return text.replace(/ID|[A-Z]/g, ($1) => {
if ($1 === "ID") return "_id";
return `_${$1.toLowerCase()}`;
});
}
function snakeToCamelCase(text: string) {
return text.replace(/_id|([-_][a-z])/ig, ($1) => {
if ($1 === "_id") return "ID";
return $1.toUpperCase().replace("_", "");
});
}
function isObject(obj: unknown) {
return obj === Object(obj) && !Array.isArray(obj) &&
typeof obj !== "function";
}
// deno-lint-ignore no-explicit-any
export function camelKeysToSnakeCase(obj: Record<string, any>) {
if (isObject(obj)) {
// deno-lint-ignore no-explicit-any
const convertedObject: Record<string, any> = {};
Object.keys(obj)
.forEach((key) => {
convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase(
obj[key],
);
});
return convertedObject;
} else if (Array.isArray(obj)) {
obj = obj.map((element) => camelKeysToSnakeCase(element));
}
return obj;
}
// deno-lint-ignore no-explicit-any
export function snakeKeysToCamelCase(obj: Record<string, any>) {
if (isObject(obj)) {
// deno-lint-ignore no-explicit-any
const convertedObject: Record<string, any> = {};
Object.keys(obj)
.forEach((key) => {
convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase(
obj[key],
);
});
return convertedObject;
} else if (Array.isArray(obj)) {
obj = obj.map((element) => snakeKeysToCamelCase(element));
}
return obj;
}

View File

@@ -248,8 +248,8 @@ Deno.test({
Deno.test({
name: "[message] pin a message in a channel",
fn() {
pin(tempData.channelID, tempData.messageID);
async fn() {
await pin(tempData.channelID, tempData.messageID);
},
...defaultTestOptions,
});
@@ -269,18 +269,18 @@ Deno.test({
Deno.test({
name: "[message] unpin a message",
fn() {
unpin(tempData.channelID, tempData.messageID);
async fn() {
await unpin(tempData.channelID, tempData.messageID);
},
...defaultTestOptions,
});
Deno.test({
name: "[message] add a reaction to a message",
fn() {
async fn() {
// TODO: add tests for a guild emoji ― <:name:id>
addReaction(tempData.channelID, tempData.messageID, "👍");
await addReaction(tempData.channelID, tempData.messageID, "👍");
},
...defaultTestOptions,
});
@@ -289,8 +289,8 @@ Deno.test({
Deno.test({
name: "[message] remove a reaction to a message",
fn() {
removeReaction(tempData.channelID, tempData.messageID, "👍");
async fn() {
await removeReaction(tempData.channelID, tempData.messageID, "👍");
},
...defaultTestOptions,
});
@@ -299,31 +299,31 @@ Deno.test({
Deno.test({
name: "[message] delete a message by channel ID",
fn() {
deleteMessageByID(tempData.channelID, tempData.messageID);
async fn() {
await deleteMessageByID(tempData.channelID, tempData.messageID);
},
...defaultTestOptions,
});
Deno.test({
name: "[channel] delete a channel in a guild",
fn() {
deleteChannel(tempData.guildID, tempData.channelID);
async fn() {
await deleteChannel(tempData.guildID, tempData.channelID);
},
...defaultTestOptions,
});
Deno.test({
name: "[role] delete a role in a guild",
fn() {
deleteRole(tempData.guildID, tempData.roleID);
async fn() {
await deleteRole(tempData.guildID, tempData.roleID);
},
});
Deno.test({
name: "[guild] delete a guild",
fn() {
deleteServer(tempData.guildID);
async fn() {
await deleteServer(tempData.guildID);
// TODO(ayntee): remove this weird shit lol
// TODO(ayntee): check if the GUILD_DELETE event is fired

76
test/util/utils.test.ts Normal file
View File

@@ -0,0 +1,76 @@
import {
camelKeysToSnakeCase,
snakeKeysToCamelCase,
} from "../../src/util/utils.ts";
import { assertEquals } from "../deps.ts";
const testSnakeObject = {
// deno-lint-ignore camelcase
hello_world: "hello_world",
// deno-lint-ignore camelcase
the_universe: {
blue_planet: {
water: "is_blue",
dirt: "isDirty",
},
moon: {
earth_moon: {
is_round: true,
},
other_moon: {
is_round: 0,
},
},
arrays: ["one_two", { moo_cow: { boo: true } }],
test_the_id: "123123123123",
},
};
const testCamelObject = {
helloWorld: "hello_world",
theUniverse: {
bluePlanet: {
water: "is_blue",
dirt: "isDirty",
},
moon: {
earthMoon: {
isRound: true,
},
otherMoon: {
isRound: 0,
},
},
arrays: ["one_two", { mooCow: { boo: true } }],
testTheID: "123123123123",
},
};
const someOther = {
helloWorld: 1,
};
const someElseOther = {
// deno-lint-ignore camelcase
hello_world: 1,
};
Deno.test({
name: "[utils] snakeKeysToCamelCase: assert convertion",
fn() {
const result = snakeKeysToCamelCase(testSnakeObject);
assertEquals(result, testCamelObject);
const resultTwo = snakeKeysToCamelCase(someOther);
assertEquals(resultTwo, someOther);
},
});
Deno.test({
name: "[utils] camelKeysToSnakeCase: assert convertion",
fn() {
const result = camelKeysToSnakeCase(testCamelObject);
assertEquals(result, testSnakeObject);
const resultTwo = camelKeysToSnakeCase(someElseOther);
assertEquals(resultTwo, someElseOther);
},
});