Merge remote-tracking branch 'upstream/master'

This commit is contained in:
ITOH
2021-01-28 11:00:01 +01:00
20 changed files with 499 additions and 58 deletions

View File

@@ -15,9 +15,12 @@
Examples of good PR title:
- fix(controllers): cache member from INTERACTION_CREATE payload
- fix(controllers/interactions): cache member from INTERACTION_CREATE payload
- docs: improve wording
- feat(handlers): add editGuild() function Examples of bad PR title:
- feat(handlers/guild): add editGuild() function Examples of bad PR title:
Examples of bad PR title:
- fix #7123
- update docs
- fix bugs

View File

@@ -1,14 +0,0 @@
**Please describe the changes this PR makes and why it should be merged:**
**Status**
- [ ] Code changes have been tested against the Discord API, or there are no
code changes
**Semantic versioning classification:**
- [ ] This PR changes the library's interface (methods or parameters added)
- [ ] This PR includes breaking changes (methods removed or renamed,
parameters moved or removed)
- [ ] This PR **only** includes non-code changes, like changes to documentation,
README, etc.

View File

@@ -7,6 +7,6 @@ jobs:
- uses: actions/checkout@v2
- uses: denolib/setup-deno@v2
- name: Run fmt check script
run: deno fmt --check
run: deno fmt --check --ignore=./src/types/util.ts
- name: Run lint script
run: deno lint src/** test/** --unstable

View File

@@ -13,8 +13,10 @@ jobs:
deno-version: ${{ matrix.deno }}
- name: Cache dependencies
run: deno cache mod.ts
- name: Run test script
- name: Run local tests
run: TEST_TYPE=local deno test --allow-env
- name: Run API tests
if: github.ref == 'refs/heads/master'
run: deno test --allow-net --allow-env
run: TEST_TYPE=api deno test --allow-net --allow-env
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}

View File

@@ -1,6 +1,8 @@
# Discordeno
> Discord API library for [Deno](https://deno.land)
<img align="right" src=docs/src/.vuepress/public/logo.png height="150px">
Discord API library for [Deno](https://deno.land)
[![Discord](https://img.shields.io/discord/785384884197392384?color=7289da&logo=discord&logoColor=dark)](https://discord.com/invite/5vBgXk3UcZ)
![Lint](https://github.com/discordeno/discordeno/workflows/Lint/badge.svg)
@@ -8,13 +10,10 @@
## Features
- **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.
- **Secure & stable**: Discordeno is actively maintained to ensure great
performance and convenience. Moreover, it internally checks all missing
permissions before forwarding a request to the Discord API so that the client
does not get globally-banned by Discord.
- **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)

View File

@@ -1,6 +1,6 @@
import { eventHandlers } from "../../bot.ts";
import {
Application,
ApplicationCommandEvent,
DiscordPayload,
InteractionCommandPayload,
} from "../../types/mod.ts";
@@ -23,5 +23,47 @@ export function handleInternalApplicationCommandCreate(
) {
if (data.t !== "APPLICATION_COMMAND_CREATE") return;
eventHandlers.applicationCommandCreate?.(data.d as Application);
const {
guild_id: guildID,
application_id: applicationID,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandCreate?.({
...rest,
guildID,
applicationID,
});
}
export function handleInternalApplicationCommandUpdate(data: DiscordPayload) {
if (data.t !== "APPLICATION_COMMAND_UPDATE") return;
const {
application_id: applicationID,
guild_id: guildID,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandUpdate?.({
...rest,
guildID,
applicationID,
});
}
export function handleInternalApplicationCommandDelete(data: DiscordPayload) {
if (data.t !== "APPLICATION_COMMAND_DELETE") return;
const {
application_id: applicationID,
guild_id: guildID,
...rest
} = data.d as ApplicationCommandEvent;
eventHandlers.applicationCommandDelete?.({
...rest,
guildID,
applicationID,
});
}

View File

@@ -80,6 +80,11 @@ export async function handleInternalGuildMemberUpdate(data: DiscordPayload) {
guildMember?.nick,
);
}
if (payload.pending === false && guildMember?.pending === true) {
eventHandlers.membershipScreeningPassed?.(guild, member);
}
const roleIDs = guildMember?.roles || [];
roleIDs.forEach((id) => {

View File

@@ -1,6 +1,8 @@
import { eventHandlers, setApplicationID, setBotID } from "../../bot.ts";
import {
DiscordPayload,
IntegrationCreateUpdateEvent,
IntegrationDeleteEvent,
PresenceUpdatePayload,
ReadyPayload,
TypingStartPayload,
@@ -30,7 +32,14 @@ export async function handleInternalReady(
eventHandlers.shardReady?.(shardID);
if (payload.shard && shardID === payload.shard[1] - 1) {
const loadedAllGuilds = async () => {
if (payload.guilds.some((g) => !cache.guilds.has(g.id))) {
const guildsMissing = async () => {
for (const g of payload.guilds) {
if (!(await cacheHandlers.has("guilds", g.id))) return true;
}
return false;
};
if (await guildsMissing()) {
setTimeout(loadedAllGuilds, 2000);
} else {
// The bot has already started, the last shard is resumed, however.
@@ -155,3 +164,73 @@ export function handleInternalWebhooksUpdate(data: DiscordPayload) {
options.guild_id,
);
}
export function handleInternalIntegrationCreate(
data: DiscordPayload,
) {
if (data.t !== "INTEGRATION_CREATE") return;
const {
guild_id: guildID,
enable_emoticons: enableEmoticons,
expire_behavior: expireBehavior,
expire_grace_period: expireGracePeriod,
subscriber_count: subscriberCount,
role_id: roleID,
synced_at: syncedAt,
...rest
} = data.d as IntegrationCreateUpdateEvent;
eventHandlers.integrationCreate?.({
...rest,
guildID,
enableEmoticons,
expireBehavior,
expireGracePeriod,
syncedAt,
subscriberCount,
roleID,
});
}
export function handleInternalIntegrationUpdate(data: DiscordPayload) {
if (data.t !== "INTEGRATION_UPDATE") return;
const {
enable_emoticons: enableEmoticons,
expire_behavior: expireBehavior,
expire_grace_period: expireGracePeriod,
role_id: roleID,
subscriber_count: subscriberCount,
synced_at: syncedAt,
guild_id: guildID,
...rest
} = data.d as IntegrationCreateUpdateEvent;
eventHandlers.integrationUpdate?.({
...rest,
guildID,
subscriberCount,
enableEmoticons,
expireGracePeriod,
roleID,
expireBehavior,
syncedAt,
});
}
export function handleInternalIntegrationDelete(data: DiscordPayload) {
if (data.t !== "INTEGRATION_DELETE") return;
const {
guild_id: guildID,
application_id: applicationID,
...rest
} = data.d as IntegrationDeleteEvent;
eventHandlers.integrationDelete?.({
...rest,
applicationID,
guildID,
});
}

View File

@@ -15,6 +15,8 @@ import {
} from "./guilds.ts";
import {
handleInternalApplicationCommandCreate,
handleInternalApplicationCommandDelete,
handleInternalApplicationCommandUpdate,
handleInternalInteractionCreate,
} from "./interactions.ts";
import {
@@ -30,6 +32,9 @@ import {
handleInternalMessageUpdate,
} from "./messages.ts";
import {
handleInternalIntegrationCreate,
handleInternalIntegrationDelete,
handleInternalIntegrationUpdate,
handleInternalPresenceUpdate,
handleInternalReady,
handleInternalTypingStart,
@@ -69,6 +74,8 @@ export let controllers = {
GUILD_ROLE_UPDATE: handleInternalGuildRoleUpdate,
INTERACTION_CREATE: handleInternalInteractionCreate,
APPLICATION_COMMAND_CREATE: handleInternalApplicationCommandCreate,
APPLICATION_COMMAND_DELETE: handleInternalApplicationCommandDelete,
APPLICATION_COMMAND_UPDATE: handleInternalApplicationCommandUpdate,
MESSAGE_CREATE: handleInternalMessageCreate,
MESSAGE_DELETE: handleInternalMessageDelete,
MESSAGE_DELETE_BULK: handleInternalMessageDeleteBulk,
@@ -82,6 +89,9 @@ export let controllers = {
USER_UPDATE: handleInternalUserUpdate,
VOICE_STATE_UPDATE: handleInternalVoiceStateUpdate,
WEBHOOKS_UPDATE: handleInternalWebhooksUpdate,
INTEGRATION_CREATE: handleInternalIntegrationCreate,
INTEGRATION_UPDATE: handleInternalIntegrationUpdate,
INTEGRATION_DELETE: handleInternalIntegrationDelete,
};
export type Controllers = typeof controllers;

View File

@@ -20,12 +20,15 @@ import {
Errors,
FetchMembersOptions,
GetAuditLogsOptions,
GetMemberOptions,
GuildEditOptions,
GuildTemplate,
ImageFormats,
ImageSize,
Intents,
MemberCreatePayload,
MembershipScreeningFieldTypes,
MembershipScreeningPayload,
Overwrite,
PositionSwap,
PruneOptions,
@@ -578,6 +581,67 @@ export function fetchMembers(guild: Guild, options?: FetchMembersOptions) {
}) as Promise<Collection<string, Member>>;
}
/**
* ⚠️ BEGINNER DEVS!! YOU SHOULD ALMOST NEVER NEED THIS AND YOU CAN GET FROM cache.members.get()
*
* ADVANCED:
* Highly recommended to **NOT** use this function to get members instead use fetchMembers().
* REST(this function): 50/s global(across all shards) rate limit with ALL requests this included
* GW(fetchMembers): 120/m(PER shard) rate limit. Meaning if you have 8 shards your limit is 960/m.
*/
export async function getMembers(
guildID: string,
options?: GetMemberOptions,
) {
if (!(identifyPayload.intents && Intents.GUILD_MEMBERS)) {
throw new Error(Errors.MISSING_INTENT_GUILD_MEMBERS);
}
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
const members = new Collection<string, Member>();
let membersLeft = options?.limit ?? guild.memberCount;
let loops = 1;
while (
(options?.limit ?? guild.memberCount) > members.size && membersLeft > 0
) {
if (options?.limit && options.limit > 1000) {
console.log(
`Paginating get members from REST. #${loops} / ${
Math.ceil((options?.limit ?? 1) / 1000)
}`,
);
}
const result = await RequestManager.get(
`${endpoints.GUILD_MEMBERS(guildID)}?limit=${
membersLeft > 1000 ? 1000 : membersLeft
}${options?.after ? `&after=${options.after}` : ""}`,
) as MemberCreatePayload[];
const memberStructures = await Promise.all(
result.map((member) => structures.createMember(member, guildID)),
) as Member[];
if (!memberStructures.length) break;
memberStructures.forEach((member) => members.set(member.id, member));
options = {
limit: options?.limit,
after: memberStructures[memberStructures.length - 1].id,
};
membersLeft -= 1000;
loops++;
}
return members;
}
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
export async function getAuditLogs(
guildID: string,
@@ -1021,3 +1085,69 @@ export async function editGuildTemplate(
return structures.createTemplate(template);
}
function createMembershipObj(
{ form_fields: formFields, ...props }: MembershipScreeningPayload,
) {
return {
...props,
formFields: formFields.map(({ field_type, ...rest }) => ({
...rest,
fieldType: field_type,
})),
};
}
export type MembershipScreening = ReturnType<typeof createMembershipObj>;
/** Get the membership screening form of a guild. */
export async function getGuildMembershipScreeningForm(guildID: string) {
const membershipScreeningPayload = await RequestManager.get(
endpoints.GUILD_MEMBER_VERIFICATION(guildID),
) as MembershipScreeningPayload;
return createMembershipObj(membershipScreeningPayload);
}
/** Edit the guild's Membership Screening form. Requires the `MANAGE_GUILD` permission. */
export async function editGuildMembershipScreeningForm(
guildID: string,
options?: EditGuildMembershipScreeningForm,
) {
const membershipScreeningFormPayload = await RequestManager.patch(
endpoints.GUILD_MEMBER_VERIFICATION(guildID),
{
...options,
form_fields: JSON.stringify(
options?.formFields?.map(({ fieldType, ...props }) => ({
...props,
field_type: fieldType,
})),
),
},
) as MembershipScreeningPayload;
return createMembershipObj(
membershipScreeningFormPayload,
);
}
export interface EditGuildMembershipScreeningForm {
/** whether Membership Screening is enabled */
enabled?: boolean;
/** array of field objects */
formFields?: MembershipScreeningField[];
/** the steps in the screening form */
description?: string;
}
export interface MembershipScreeningField {
/** the type of field */
fieldType: MembershipScreeningFieldTypes;
/** the title of the field */
label: string;
/** the list of rules */
values?: string[];
/** whether the user has to fill out this field */
required: boolean;
}

View File

@@ -57,6 +57,7 @@ import {
getIntegrations,
getInvites,
getMember,
getMembers,
getMembersByQuery,
getPruneCount,
getRoles,
@@ -183,6 +184,7 @@ export let handlers = {
getIntegrations,
getInvites,
getMember,
getMembers,
getTemplate,
getMembersByQuery,
getPruneCount,

View File

@@ -1,4 +1,9 @@
import { CreateGuildPayload, PartialUser, UserPayload } from "./guild.ts";
import {
CreateGuildPayload,
Integration,
PartialUser,
UserPayload,
} from "./guild.ts";
import { MemberCreatePayload } from "./member.ts";
import { Activity, Application } from "./message.ts";
import { ClientStatusPayload } from "./presence.ts";
@@ -13,6 +18,8 @@ export interface DiscordPayload {
/** The event name for this payload. ONLY for OPCode 0 */
t?:
| "APPLICATION_COMMAND_CREATE"
| "APPLICATION_COMMAND_UPDATE"
| "APPLICATION_COMMAND_DELETE"
| "CHANNEL_CREATE"
| "CHANNEL_DELETE"
| "CHANNEL_UPDATE"
@@ -43,7 +50,10 @@ export interface DiscordPayload {
| "TYPING_START"
| "USER_UPDATE"
| "VOICE_STATE_UPDATE"
| "WEBHOOKS_UPDATE";
| "WEBHOOKS_UPDATE"
| "INTEGRATION_CREATE"
| "INTEGRATION_UPDATE"
| "INTEGRATION_DELETE";
}
export interface DiscordBotGatewayData {
@@ -301,3 +311,17 @@ export type UnavailableGuildPayload = Pick<
CreateGuildPayload,
"id" | "unavailable"
>;
export type IntegrationCreateUpdateEvent = Integration & {
/** id of the guild */
guild_id: string;
};
export interface IntegrationDeleteEvent {
/** integration id */
id: string;
/** id of the guild */
guild_id: string;
/** id of the bot/OAuth2 application for this discord integration */
application_id?: string;
}

View File

@@ -2,7 +2,7 @@ import { Guild } from "../api/structures/mod.ts";
import { ChannelCreatePayload, ChannelTypes } from "./channel.ts";
import { Emoji, StatusType } from "./discord.ts";
import { MemberCreatePayload } from "./member.ts";
import { Activity } from "./message.ts";
import { Activity, Application } from "./message.ts";
import { Permission } from "./permission.ts";
import { ClientStatusPayload } from "./presence.ts";
import { RoleData } from "./role.ts";
@@ -49,6 +49,8 @@ export interface GuildMemberUpdatePayload {
nick: string;
/** When the user used their nitro boost on the guild. */
premium_since: string | null;
/** whether the user has not yet passed the guild's Membership Screening requirements */
pending?: boolean;
}
export interface GuildMemberAddPayload extends MemberCreatePayload {
@@ -172,7 +174,11 @@ export type GuildFeatures =
| "DISCOVERABLE"
| "FEATURABLE"
| "ANIMATED_ICON"
| "BANNER";
| "BANNER"
/** guild has enabled Membership Screening */
| "MEMBER_VERIFICATION_GATE_ENABLED"
/** guild can be previewed before joining via Membership Screening or the directory */
| "PREVIEW_ENABLED";
export interface VoiceRegion {
/** unique ID for the region */
@@ -246,7 +252,7 @@ export interface EditIntegrationOptions {
enable_emoticons: boolean;
}
export interface GuildIntegration {
export interface Integration {
/** The integrations unique id */
id: string;
/** the integrations name */
@@ -256,19 +262,32 @@ export interface GuildIntegration {
/** Is this integration enabled */
enabled: boolean;
/** is this integration syncing */
syncing: boolean;
syncing?: boolean;
/** id that this integration uses for "subscribers" */
role_id: string;
role_id?: string;
/** whether emoticons should be synced for this integration (twitch only currently) */
enable_emoticons?: boolean;
/** The behavior of expiring subscribers */
expire_behavior: number;
expire_behavior?: IntegrationExpireBehaviors;
/** The grace period before expiring subscribers */
expire_grace_period: number;
expire_grace_period?: number;
/** The user for this integration */
user: UserPayload;
user?: UserPayload;
/** The integration account information */
account: Account;
/** When this integration was last synced */
synced_at: string;
synced_at?: string;
/** how many subscribers this integration has */
subscriber_count?: number;
/** has this integration been revoked */
revoked?: boolean;
/** The bot/OAuth2 application for discord integrations */
application?: Application;
}
export enum IntegrationExpireBehaviors {
RemoveRole,
Kick,
}
export interface Account {
@@ -589,6 +608,13 @@ export interface FetchMembersOptions {
limit?: number;
}
export interface GetMemberOptions {
/** max number of members to return (1-1000), defaults to 1 */
limit?: number;
/** the highest user id in the previous page */
after?: string;
}
export interface CreateServerOptions {
/** name of the guild (2-100 characters) */
name: string;
@@ -660,3 +686,27 @@ export interface EditGuildTemplate {
/** description for the template (0-120 characters) */
description?: string | null;
}
export interface MembershipScreeningPayload {
/** when the fields were last updated */
version: string;
/** the steps in the screening form */
form_fields: MembershipScreeningFieldPayload[];
/** the server description shown in the screening form */
description: string | null;
}
export interface MembershipScreeningFieldPayload {
/** the type of field */
field_type: MembershipScreeningFieldTypes;
/** the title of the field */
label: string;
/** the list of rules */
values?: string[];
/** whether the user has to fill out this field */
required: boolean;
}
export type MembershipScreeningFieldTypes =
/** Server Rules */
"TERMS";

View File

@@ -41,3 +41,60 @@ export interface InteractionDataOption {
/** present if this option is a group or subcommand */
options?: InteractionDataOption[];
}
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommand */
export interface ApplicationCommand {
/** unique id of the command */
id: string;
/** unique id of the parent application */
application_id: string;
/** 3-32 character name matching `^[\w-]{3,32}$` */
name: string;
/** 1-100 character description */
description: string;
/** the parameters for the command */
options?: ApplicationCommandOption[];
}
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption */
export interface ApplicationCommandOption {
/** the type of the option */
type: ApplicationCommandOptionType;
/** 1-32 character name matching `^[\w-]{1,32}$` */
name: string;
/** 1-100 character description */
description: string;
/** the first `required` option for the user to complete--only one option can be `default` */
default?: boolean;
/** if the parameter is required or optional--default `false` */
required?: boolean;
/** choices for `string` and `int` types for the user to pick from */
choices?: ApplicationCommandOptionChoice[];
/** if the option is a subcommand or subcommand group type, this nested options will be the parameters */
options?: ApplicationCommandOption[];
}
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptiontype */
export enum ApplicationCommandOptionType {
SUB_COMMAND = 1,
SUB_COMMAND_GROUP,
STRING,
INTEGER,
BOOLEAN,
USER,
CHANNEL,
ROLE,
}
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */
export interface ApplicationCommandOptionChoice {
/** 1-100 character choice name */
name: string;
/** value of the choice */
value: string | number;
}
export type ApplicationCommandEvent = ApplicationCommand & {
/** id of the guild the command is in */
guild_id?: string;
};

View File

@@ -188,6 +188,10 @@ export interface Application {
icon: string | null;
/** The name of the application */
name: string;
/** the description of the app */
summary: string;
/** the bot associated with this application */
bot?: UserPayload;
}
export interface Reference {

View File

@@ -8,14 +8,18 @@ import {
import {
DiscordPayload,
Emoji,
IntegrationCreateUpdateEvent,
IntegrationDeleteEvent,
PresenceUpdatePayload,
TypingStartPayload,
VoiceStateUpdatePayload,
} from "./discord.ts";
import { UserPayload } from "./guild.ts";
import { InteractionCommandPayload } from "./interactions.ts";
import {
Application,
ApplicationCommandEvent,
InteractionCommandPayload,
} from "./interactions.ts";
import {
Attachment,
BaseMessageReactionPayload,
Embed,
@@ -24,6 +28,7 @@ import {
PartialMessage,
ReactionPayload,
} from "./message.ts";
import { Camelize } from "./util.ts";
export interface BotConfig {
token: string;
@@ -89,7 +94,18 @@ interface RateLimitData {
export interface EventHandlers {
rateLimit?: (data: RateLimitData) => unknown;
applicationCommandCreate?: (data: Application) => unknown;
/** Sent when a new Slash Command is created, relevant to the current user. */
applicationCommandCreate?: (
data: Camelize<ApplicationCommandEvent>,
) => unknown;
/** Sent when a Slash Command relevant to the current user is updated. */
applicationCommandUpdate?: (
data: Camelize<ApplicationCommandEvent>,
) => unknown;
/** Sent when a Slash Command relevant to the current user is deleted. */
applicationCommandDelete?: (
data: Camelize<ApplicationCommandEvent>,
) => unknown;
/** Sent when properties about the user change. */
botUpdate?: (user: UserPayload) => unknown;
/** Sent when a new guild channel is created, relevant to the current user. */
@@ -208,6 +224,14 @@ export interface EventHandlers {
) => unknown;
/** Sent when a guild channel's webhook is created, updated, or deleted. */
webhooksUpdate?: (channelID: string, guildID: string) => unknown;
/** Sent when a member has passed the guild's Membership Screening requirements */
membershipScreeningPassed?: (guild: Guild, member: Member) => unknown;
/** Sent when an integration is created on a server such as twitch, youtube etc.. */
integrationCreate?: (data: Camelize<IntegrationCreateUpdateEvent>) => unknown;
/** Sent when an integration is updated. */
integrationUpdate?: (data: Camelize<IntegrationCreateUpdateEvent>) => unknown;
/** Sent when an integration is deleted. */
integrationDelete?: (data: Camelize<IntegrationDeleteEvent>) => undefined;
}
/** https://discord.com/developers/docs/topics/gateway#list-of-intents */
@@ -241,6 +265,9 @@ export enum Intents {
GUILD_EMOJIS = 1 << 3,
/** Enables the following events:
* - GUILD_INTEGRATIONS_UPDATE
* - INTEGRATION_CREATE
* - INTEGRATION_UPDATE
* - INTEGRATION_DELETE
*/
GUILD_INTEGRATIONS = 1 << 4,
/** Enables the following events:
@@ -297,5 +324,3 @@ export enum Intents {
*/
DIRECT_MESSAGE_TYPING = 1 << 14,
}
export type ValueOf<T> = T[keyof T];

9
src/types/util.ts Normal file
View File

@@ -0,0 +1,9 @@
export type CamelizeString<T extends PropertyKey> = T extends string
? string extends T ? string
: T extends `${infer F}_${infer R}`
? `${F}${T extends `${infer F}_id` ? Uppercase<R>
: Capitalize<CamelizeString<R>>}`
: T
: T;
export type Camelize<T> = { [K in keyof T as CamelizeString<K>]: T[K] }

View File

@@ -116,6 +116,8 @@ export const endpoints = {
`${baseEndpoints.BASE_URL}/guilds/templates/${code}`,
GUILD_TEMPLATES: (guildID: string) => `${GUILDS_BASE(guildID)}/templates`,
GUILD_PREVIEW: (guildID: string) => `${GUILDS_BASE(guildID)}/preview`,
GUILD_MEMBER_VERIFICATION: (guildID: string) =>
`${GUILDS_BASE(guildID)}/member-verification`,
// Voice
VOICE_REGIONS: `${baseEndpoints.BASE_URL}/voice/regions`,

View File

@@ -31,7 +31,7 @@ export function memberHasPermission(
) {
if (memberID === guild.ownerID) return true;
const permissionBits = memberRoleIDs.map((id) =>
const permissionBits = [guild.id, ...memberRoleIDs].map((id) =>
guild.roles.get(id)?.permissions
)
// Removes any edge case undefined

View File

@@ -4,6 +4,7 @@ import {
deleteServer,
getChannel,
} from "../src/api/handlers/guild.ts";
import { eventHandlers } from "../src/bot.ts";
import {
addReaction,
assertEquals,
@@ -31,18 +32,11 @@ import {
unpin,
} from "./deps.ts";
const token = Deno.env.get("DISCORD_TOKEN");
if (!token) throw new Error("Token is not provided");
startBot({
token,
intents: ["GUILD_MESSAGES", "GUILDS"],
});
// Default options for tests
export const defaultTestOptions = {
export const defaultTestOptions: Partial<Deno.TestDefinition> = {
sanitizeOps: false,
sanitizeResources: false,
ignore: Deno.env.get("TEST_TYPE") !== "api",
};
// Temporary data
@@ -56,7 +50,23 @@ export const tempData = {
// Main
Deno.test({
name: "[main] connect to gateway",
fn: async () => {
async fn() {
const token = Deno.env.get("DISCORD_TOKEN");
if (!token) throw new Error("Token is not provided");
await startBot({
token,
intents: ["GUILD_MESSAGES", "GUILDS"],
});
eventHandlers.ready = () => {
if (cache.guilds.size >= 10) {
cache.guilds.map((guild) =>
guild.ownerID === botID && deleteServer(guild.id)
);
}
};
// Delay the execution by 5 seconds
await delay(5000);
@@ -185,6 +195,7 @@ Deno.test({
assertExists(channel);
assertEquals(channel.name, "discordeno-test-edited");
},
...defaultTestOptions,
});
Deno.test({
@@ -244,6 +255,7 @@ Deno.test({
assertExists(message);
assertEquals(message.embeds[0].title, "Discordeno Test");
},
...defaultTestOptions,
});
Deno.test({
@@ -318,6 +330,7 @@ Deno.test({
async fn() {
await deleteRole(tempData.guildID, tempData.roleID);
},
...defaultTestOptions,
});
Deno.test({
@@ -339,5 +352,4 @@ Deno.test({
fn() {
Deno.exit();
},
...defaultTestOptions,
});