mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-03 17:30:07 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
7
.github/CONTRIBUTING.md
vendored
7
.github/CONTRIBUTING.md
vendored
@@ -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
|
||||
|
||||
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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.
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -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 }}
|
||||
|
||||
15
README.md
15
README.md
@@ -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)
|
||||
|
||||
[](https://discord.com/invite/5vBgXk3UcZ)
|
||||

|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
9
src/types/util.ts
Normal 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] }
|
||||
@@ -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`,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user