fix: start deris

This commit is contained in:
Skillz
2023-01-07 15:03:58 -06:00
parent d8e70df57d
commit 851214d798
61 changed files with 15720 additions and 968 deletions

View File

@@ -0,0 +1,7 @@
{
"all": true,
"src": "src",
"reporter": ["text", "lcov"],
"include": ["src/**/*.ts"],
"exclude": ["tests"]
}

View File

@@ -0,0 +1,10 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"recursive": true,
"timeout": 2000,
"watch-extensions": "ts",
"watch-files": ["src", "tests"],
"enable-source-maps": true,
"parallel": false
}

31
packages/client/.swcrc Normal file
View File

@@ -0,0 +1,31 @@
{
"minify": true,
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2022",
"keepClassNames": true,
"loose": true,
"minify": {
"compress": {
"unused": true
},
"mangle": true
}
},
"module": {
"type": "es6",
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
},
"sourceMaps": "inline"
}

View File

@@ -0,0 +1,47 @@
{
"name": "@discordeno/client",
"version": "18.0.0-alpha.1",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/discordeno/discordeno.git"
},
"scripts": {
"build": "swc --delete-dir-on-start src --out-dir dist",
"build:type": "tsc --declaration --emitDeclarationOnly --declarationDir dist",
"release-build": "yarn build && yarn build:type",
"fmt": "eslint --fix \"src/**/*.ts*\"",
"lint": "eslint \"src/**/*.ts*\"",
"test:unit-coverage": "c8 mocha --no-warnings 'tests/**/*.spec.ts'",
"test:unit": "c8 --r lcov mocha --no-warnings 'tests/**/*.spec.ts' && node ../../scripts/coveragePathFixing.js client",
"test:deno-unit": "swc tests --delete-dir-on-start --out-dir denoTestsDist && node ../../scripts/fixDenoTestExtension.js && deno test -A --import-map ../../denoImportMap.json denoTestsDist",
"test:unit:watch": "mocha --no-warnings --watch --parallel 'tests/**/*.spec.ts'",
"test:type": "tsc --noEmit",
"test:test-type": "tsc --project tsconfig.test.json"
},
"dependencies": {
"@discordeno/gateway": "18.0.0-alpha.1",
"@discordeno/rest": "18.0.0-alpha.1",
"@discordeno/utils": "18.0.0-alpha.1"
},
"devDependencies": {
"@discordeno/types": "18.0.0-alpha.1",
"@swc/cli": "^0.1.57",
"@swc/core": "^1.3.21",
"@types/chai": "^4",
"@types/mocha": "^10",
"@types/node": "^18.11.9",
"@types/sinon": "^10.0.13",
"c8": "^7.12.0",
"chai": "^4.3.7",
"eslint": "^8.0.1",
"eslint-config-discordeno": "*",
"mocha": "^10.1.0",
"sinon": "^15.0.0",
"tsconfig": "*",
"typescript": "^4.9.3"
}
}

View File

@@ -0,0 +1,77 @@
import type { BigString } from './Client.js'
export class Base {
/** Internal storage of the id done in bigint to preserve memory */
_id: bigint
constructor(id: BigString) {
this._id = BigInt(id)
}
/** The snowflake id */
get id(): string {
return this._id.toString()
}
set id(value: BigString) {
this._id = BigInt(value)
}
get createdAt(): number {
return Number(this._id / 4194304n + 1420070400000n)
}
/**
* Calculates the timestamp in milliseconds associated with a Discord ID/snowflake.
* @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris.
*/
static getCreatedAt(id: string): number {
return Base.getDiscordEpoch(id) + 1420070400000
}
/**
* Gets the number of milliseconds since epoch represented by an ID/snowflake
* @deprecated Recommend using Object.createdAt or Client.snowflakeToTimestamp if you want to get a timestamp from a id. This is not desired but supported only to keep a similar api to eris.
*/
static getDiscordEpoch(id: string): number {
// @ts-expect-error some eris magic at play here
return Math.floor(id / 4194304)
}
toString(): string {
return `[${this.constructor.name} ${this.id}]`
}
toJSON(props: string[] = []): Record<string, any> {
const json = {
id: this.id,
createdAt: this.createdAt,
}
for (const prop of props) {
// @ts-expect-error some eris magic at play here
const value = this[prop]
const type = typeof value
if (value === undefined) {
continue
} else if ((type !== 'object' && type !== 'function' && type !== 'bigint') || value === null) {
// @ts-expect-error some eris magic at play here
json[prop] = value
} else if (value.toJSON !== undefined) {
// @ts-expect-error some eris magic at play here
json[prop] = value.toJSON()
} else if (value.values !== undefined) {
// @ts-expect-error some eris magic at play here
json[prop] = [...value.values()]
} else if (type === 'bigint') {
// @ts-expect-error some eris magic at play here
json[prop] = value.toString()
} else if (type === 'object') {
// @ts-expect-error some eris magic at play here
json[prop] = value
}
}
return json
}
}
export default Base

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
export class Collection<K, V> extends Map<K, V> {
limit: number | undefined;
set(key: K, value: V): this {
// When this collection is limitd make sure we can add first
if ((this.limit ?? this.limit === 0) && this.size >= this.limit) {
return this;
}
return super.set(key, value);
}
forceSet(key: K, value: V): this {
return super.set(key, value);
}
array(): V[] {
return [...this.values()];
}
/** Retrieve the value of the first element in this collection */
first(): V | undefined {
return this.values().next().value;
}
last(): V | undefined {
return [...this.values()][this.size - 1];
}
random(): V | undefined {
const array = [...this.values()];
return array[Math.floor(Math.random() * array.length)];
}
find(callback: (value: V, key: K) => boolean): V | undefined {
for (const key of this.keys()) {
const value = this.get(key)!;
if (callback(value, key)) return value;
}
}
filter(callback: (value: V, key: K) => boolean, returnArray?: true): V[];
filter(callback: (value: V, key: K) => boolean, returnArray: false): Collection<K, V>;
filter(callback: (value: V, key: K) => boolean, returnArray = true): Collection<K, V> | V[] {
const relevant = new Collection<K, V>();
this.forEach((value, key) => {
if (callback(value, key)) relevant.set(key, value);
});
return returnArray ? relevant.array() : relevant;
}
map<T>(callback: (value: V, key: K) => T): T[] {
const results = [];
for (const key of this.keys()) {
const value = this.get(key)!;
results.push(callback(value, key));
}
return results;
}
some(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!;
if (callback(value, key)) return true;
}
return false;
}
every(callback: (value: V, key: K) => boolean): boolean {
for (const key of this.keys()) {
const value = this.get(key)!;
if (!callback(value, key)) return false;
}
return true;
}
reduce<T>(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T {
let accumulator: T = initialValue!;
for (const key of this.keys()) {
const value = this.get(key)!;
accumulator = callback(accumulator, value, key);
}
return accumulator;
}
/**
* Adds a object to the collection.
* @deprecated Recommend using Collection.set(). Keeping for the sake of Eris API.
* @deprecated extra parameter. No longer used, keeping for sake of Eris API.
*/
add(obj: V & { id: K }, extra?: unknown, replace?: boolean): V {
if (this.limit === 0) return obj;
const existing = this.get(obj.id);
if (existing && !replace) {
return existing;
}
this.set(obj.id, obj);
return obj;
}
remove(obj: { id: K }): V | undefined {
const item = this.get(obj.id);
if (!item) return;
this.delete(obj.id);
return item;
}
update(obj: V & { id: K }, extra?: unknown, replace?: boolean): V {
const item = this.get(obj.id);
if (!item) {
this.set(obj.id, obj);
return obj;
}
// @ts-expect-error some eris magic at play here
item.update?.(obj, extra);
return item;
}
toRecord(): Record<string, V> {
const record: Record<string, V> = {};
for (const [key, value] of this.entries()) {
// @ts-expect-error should work fine
const finalKey = typeof key === 'string' ? key : key.toString();
record[finalKey] = value;
}
return record;
}
}
export default Collection;

View File

@@ -0,0 +1,136 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { BigString } from './Client.js'
export const ORIGINAL_INTERACTION_RESPONSE = (appID: BigString, interactToken: string) => `/webhooks/${appID}/${interactToken}`
export const COMMAND = (applicationID: BigString, commandID: BigString) => `/applications/${applicationID}/commands/${commandID}`
export const COMMANDS = (applicationID: BigString) => `/applications/${applicationID}/commands`
export const COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString, commandID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/${commandID}/permissions`
export const CHANNEL = (chanID: BigString) => `/channels/${chanID}`
export const CHANNEL_BULK_DELETE = (chanID: BigString) => `/channels/${chanID}/messages/bulk-delete`
export const CHANNEL_CALL_RING = (chanID: BigString) => `/channels/${chanID}/call/ring`
export const CHANNEL_CROSSPOST = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/crosspost`
export const CHANNEL_FOLLOW = (chanID: BigString) => `/channels/${chanID}/followers`
export const CHANNEL_INVITES = (chanID: BigString) => `/channels/${chanID}/invites`
export const CHANNEL_MESSAGE_REACTION = (chanID: BigString, msgID: BigString, reaction: string) =>
`/channels/${chanID}/messages/${msgID}/reactions/${reaction}`
export const CHANNEL_MESSAGE_REACTION_USER = (chanID: BigString, msgID: BigString, reaction: string, userID: BigString) =>
`/channels/${chanID}/messages/${msgID}/reactions/${reaction}/${userID}`
export const CHANNEL_MESSAGE_REACTIONS = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}/reactions`
export const CHANNEL_MESSAGE = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/messages/${msgID}`
export const CHANNEL_MESSAGES = (chanID: BigString) => `/channels/${chanID}/messages`
export const CHANNEL_MESSAGES_SEARCH = (chanID: BigString) => `/channels/${chanID}/messages/search`
export const CHANNEL_PERMISSION = (chanID: BigString, overID: BigString) => `/channels/${chanID}/permissions/${overID}`
export const CHANNEL_PERMISSIONS = (chanID: BigString) => `/channels/${chanID}/permissions`
export const CHANNEL_PIN = (chanID: BigString, msgID: BigString) => `/channels/${chanID}/pins/${msgID}`
export const CHANNEL_PINS = (chanID: BigString) => `/channels/${chanID}/pins`
export const CHANNEL_RECIPIENT = (groupID: BigString, userID: BigString) => `/channels/${groupID}/recipients/${userID}`
export const CHANNEL_TYPING = (chanID: BigString) => `/channels/${chanID}/typing`
export const CHANNEL_WEBHOOKS = (chanID: BigString) => `/channels/${chanID}/webhooks`
export const CHANNELS = '/channels'
export const CUSTOM_EMOJI_GUILD = (emojiID: BigString) => `/emojis/${emojiID}/guild`
export const DISCOVERY_CATEGORIES = '/discovery/categories'
export const DISCOVERY_VALIDATION = '/discovery/valid-term'
export const GATEWAY = '/gateway'
export const GATEWAY_BOT = '/gateway/bot'
export const GUILD = (guildID: BigString) => `/guilds/${guildID}`
export const GUILD_AUDIT_LOGS = (guildID: BigString) => `/guilds/${guildID}/audit-logs`
export const GUILD_BAN = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/bans/${memberID}`
export const GUILD_BANS = (guildID: BigString) => `/guilds/${guildID}/bans`
export const GUILD_CHANNELS = (guildID: BigString) => `/guilds/${guildID}/channels`
export const GUILD_COMMAND = (applicationID: BigString, guildID: BigString, commandID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/${commandID}`
export const GUILD_COMMAND_PERMISSIONS = (applicationID: BigString, guildID: BigString) =>
`/applications/${applicationID}/guilds/${guildID}/commands/permissions`
export const GUILD_COMMANDS = (applicationID: BigString, guildID: BigString) => `/applications/${applicationID}/guilds/${guildID}/commands`
export const GUILD_DISCOVERY = (guildID: BigString) => `/guilds/${guildID}/discovery-metadata`
export const GUILD_DISCOVERY_CATEGORY = (guildID: BigString, categoryID: BigString) => `/guilds/${guildID}/discovery-categories/${categoryID}`
export const GUILD_EMOJI = (guildID: BigString, emojiID: BigString) => `/guilds/${guildID}/emojis/${emojiID}`
export const GUILD_EMOJIS = (guildID: BigString) => `/guilds/${guildID}/emojis`
export const GUILD_INTEGRATION = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}`
export const GUILD_INTEGRATION_SYNC = (guildID: BigString, inteID: BigString) => `/guilds/${guildID}/integrations/${inteID}/sync`
export const GUILD_INTEGRATIONS = (guildID: BigString) => `/guilds/${guildID}/integrations`
export const GUILD_INVITES = (guildID: BigString) => `/guilds/${guildID}/invites`
export const GUILD_VANITY_URL = (guildID: BigString) => `/guilds/${guildID}/vanity-url`
export const GUILD_MEMBER = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}`
export const GUILD_MEMBER_NICK = (guildID: BigString, memberID: BigString) => `/guilds/${guildID}/members/${memberID}/nick`
export const GUILD_MEMBER_ROLE = (guildID: BigString, memberID: BigString, roleID: BigString) =>
`/guilds/${guildID}/members/${memberID}/roles/${roleID}`
export const GUILD_MEMBERS = (guildID: BigString) => `/guilds/${guildID}/members`
export const GUILD_MEMBERS_SEARCH = (guildID: BigString) => `/guilds/${guildID}/members/search`
export const GUILD_MESSAGES_SEARCH = (guildID: BigString) => `/guilds/${guildID}/messages/search`
export const GUILD_PREVIEW = (guildID: BigString) => `/guilds/${guildID}/preview`
export const GUILD_PRUNE = (guildID: BigString) => `/guilds/${guildID}/prune`
export const GUILD_ROLE = (guildID: BigString, roleID: BigString) => `/guilds/${guildID}/roles/${roleID}`
export const GUILD_ROLES = (guildID: BigString) => `/guilds/${guildID}/roles`
export const GUILD_STICKER = (guildID: BigString, stickerID: BigString) => `/guilds/${guildID}/stickers/${stickerID}`
export const GUILD_STICKERS = (guildID: BigString) => `/guilds/${guildID}/stickers`
export const GUILD_TEMPLATE = (code: string) => `/guilds/templates/${code}`
export const GUILD_TEMPLATES = (guildID: BigString) => `/guilds/${guildID}/templates`
export const GUILD_TEMPLATE_GUILD = (guildID: BigString, code: string) => `/guilds/${guildID}/templates/${code}`
export const GUILD_VOICE_REGIONS = (guildID: BigString) => `/guilds/${guildID}/regions`
export const GUILD_WEBHOOKS = (guildID: BigString) => `/guilds/${guildID}/webhooks`
export const GUILD_WELCOME_SCREEN = (guildID: BigString) => `/guilds/${guildID}/welcome-screen`
export const GUILD_WIDGET = (guildID: BigString) => `/guilds/${guildID}/widget.json`
export const GUILD_WIDGET_SETTINGS = (guildID: BigString) => `/guilds/${guildID}/widget`
export const GUILD_VOICE_STATE = (guildID: BigString, user: BigString) => `/guilds/${guildID}/voice-states/${user}`
export const GUILDS = '/guilds'
export const INTERACTION_RESPOND = (interactID: BigString, interactToken: string) => `/interactions/${interactID}/${interactToken}/callback`
export const INVITE = (inviteID: string) => `/invites/${inviteID}`
export const OAUTH2_APPLICATION = (appID: BigString) => `/oauth2/applications/${appID}`
export const STAGE_INSTANCE = (channelID: BigString) => `/stage-instances/${channelID}`
export const STAGE_INSTANCES = '/stage-instances'
export const STICKER = (stickerID: BigString) => `/stickers/${stickerID}`
export const STICKER_PACKS = '/sticker-packs'
export const THREAD_MEMBER = (channelID: BigString, userID: BigString) => `/channels/${channelID}/thread-members/${userID}`
export const THREAD_MEMBERS = (channelID: BigString) => `/channels/${channelID}/thread-members`
export const THREAD_WITH_MESSAGE = (channelID: BigString, msgID: BigString) => `/channels/${channelID}/messages/${msgID}/threads`
export const THREAD_WITHOUT_MESSAGE = (channelID: BigString) => `/channels/${channelID}/threads`
export const THREADS_ACTIVE = (channelID: BigString) => `/channels/${channelID}/threads/active`
export const THREADS_ARCHIVED = (channelID: BigString, type: string) => `/channels/${channelID}/threads/archived/${type}`
export const THREADS_ARCHIVED_JOINED = (channelID: BigString) => `/channels/${channelID}/users/@me/threads/archived/private`
export const THREADS_GUILD_ACTIVE = (guildID: BigString) => `/guilds/${guildID}/threads/active`
export const USER = (userID: BigString) => `/users/${userID}`
export const USER_BILLING = (userID: BigString) => `/users/${userID}/billing`
export const USER_BILLING_PAYMENTS = (userID: BigString) => `/users/${userID}/billing/payments`
export const USER_BILLING_PREMIUM_SUBSCRIPTION = (userID: BigString) => `/users/${userID}/billing/premium-subscription`
export const USER_CHANNELS = (userID: BigString) => `/users/${userID}/channels`
export const USER_CONNECTIONS = (userID: BigString) => `/users/${userID}/connections`
export const USER_CONNECTION_PLATFORM = (userID: BigString, platform: string, id: string) => `/users/${userID}/connections/${platform}/${id}`
export const USER_GUILD = (userID: BigString, guildID: BigString) => `/users/${userID}/guilds/${guildID}`
export const USER_GUILDS = (userID: BigString) => `/users/${userID}/guilds`
export const USER_MFA_CODES = (userID: BigString) => `/users/${userID}/mfa/codes`
export const USER_MFA_TOTP_DISABLE = (userID: BigString) => `/users/${userID}/mfa/totp/disable`
export const USER_MFA_TOTP_ENABLE = (userID: BigString) => `/users/${userID}/mfa/totp/enable`
export const USER_NOTE = (userID: BigString, targetID: BigString) => `/users/${userID}/note/${targetID}`
export const USER_PROFILE = (userID: BigString) => `/users/${userID}/profile`
export const USER_RELATIONSHIP = (userID: BigString, relID: BigString) => `/users/${userID}/relationships/${relID}`
export const USER_SETTINGS = (userID: BigString) => `/users/${userID}/settings`
export const USERS = '/users'
export const VOICE_REGIONS = '/voice/regions'
export const WEBHOOK = (hookID: BigString) => `/webhooks/${hookID}`
export const WEBHOOK_MESSAGE = (hookID: BigString, token: string, msgID: BigString) => `/webhooks/${hookID}/${token}/messages/${msgID}`
export const WEBHOOK_SLACK = (hookID: BigString) => `/webhooks/${hookID}/slack`
export const WEBHOOK_TOKEN = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}`
export const WEBHOOK_TOKEN_SLACK = (hookID: BigString, token: string) => `/webhooks/${hookID}/${token}/slack`
// CDN Endpoints
export const ACHIEVEMENT_ICON = (applicationID: BigString, achievementID: BigString, icon: string) =>
`/app-assets/${applicationID}/achievements/${achievementID}/icons/${icon}`
export const APPLICATION_ASSET = (applicationID: BigString, asset: string) => `/app-assets/${applicationID}/${asset}`
export const APPLICATION_ICON = (applicationID: BigString, icon: string) => `/app-icons/${applicationID}/${icon}`
export const BANNER = (guildOrUserID: BigString, hash: string) => `/banners/${guildOrUserID}/${hash}`
export const CHANNEL_ICON = (chanID: BigString, chanIcon: string) => `/channel-icons/${chanID}/${chanIcon}`
export const CUSTOM_EMOJI = (emojiID: BigString) => `/emojis/${emojiID}`
export const DEFAULT_USER_AVATAR = (userDiscriminator: string) => `/embed/avatars/${userDiscriminator}`
export const GUILD_AVATAR = (guildID: BigString, userID: BigString, guildAvatar: string) =>
`/guilds/${guildID}/users/${userID}/avatars/${guildAvatar}`
export const GUILD_DISCOVERY_SPLASH = (guildID: BigString, guildDiscoverySplash: string) => `/discovery-splashes/${guildID}/${guildDiscoverySplash}`
export const GUILD_ICON = (guildID: BigString, guildIcon: string) => `/icons/${guildID}/${guildIcon}`
export const GUILD_SPLASH = (guildID: BigString, guildSplash: string) => `/splashes/${guildID}/${guildSplash}`
export const ROLE_ICON = (roleID: BigString, roleIcon: string) => `/role-icons/${roleID}/${roleIcon}`
export const TEAM_ICON = (teamID: BigString, teamIcon: string) => `/team-icons/${teamID}/${teamIcon}`
export const USER_AVATAR = (userID: BigString, userAvatar: string) => `/avatars/${userID}/${userAvatar}`
// Client Endpoints
export const MESSAGE_LINK = (guildID: BigString, channelID: BigString, messageID: BigString) => `/channels/${guildID}/${channelID}/${messageID}`

View File

@@ -0,0 +1,117 @@
import type { RequestMethods, RestManager } from '@discordeno/rest'
import { createRestManager } from '@discordeno/rest'
import Base from './Base.js'
import type Client from './Client.js'
import type { FileContent, RequestHandlerOptions } from './typings.js'
// TODO: make dynamic based on package.json file
const version = '18.0.0-alpha.1'
export class RequestHandler {
/** The client manager. */
client: Client
/** The options this manager was configured with. */
options: RequestHandlerOptions
/** The user agent used to make requests. */
userAgent: string
/** The rate limits currently in cache. */
ratelimits: Record<string, unknown>
/** The latency information for this manager. */
latencyRef: {
latency: number
raw: number[]
timeOffset: number
timeOffsets: number[]
lastTimeOffsetCheck: number
}
/** Whether or not the manager is globally blocked. */
globalBlock: boolean
/** The ready queue */
readyQueue: unknown[]
/** The internal rest manager from dd. */
discordeno: RestManager
constructor(client: Client, options: RequestHandlerOptions) {
this.options = options = Object.assign(
{
// agent: client.options.agent || null,
agent: null,
baseURL: client.BASE_URL,
decodeReasons: true,
disableLatencyCompensation: false,
domain: 'discord.com',
// latencyThreshold: client.options.latencyThreshold || 30000,
latencyThreshold: 30000,
// ratelimiterOffset: client.options.ratelimiterOffset || 0,
ratelimiterOffset: 0,
// requestTimeout: client.options.requestTimeout || 15000,
requestTimeout: 15000,
},
options,
)
this.client = client
this.discordeno = createRestManager({
token: this.client.token,
baseUrl: options.baseURL ?? this.client.options.proxyURL,
})
this.userAgent = `DiscordBot (https://github.com/discordeno/discordeno, ${version})`
this.ratelimits = {}
this.latencyRef = {
latency: this.options.ratelimiterOffset ?? 0,
raw: new Array(10).fill(this.options.ratelimiterOffset),
timeOffset: 0,
timeOffsets: new Array(10).fill(0),
lastTimeOffsetCheck: 0,
}
this.globalBlock = false
this.readyQueue = []
}
/**
* @deprecated Use `.client` instead
*/
get _client(): Client {
return this.client
}
/**
* @deprecated Useless, handled by discordeno itself. Kept for Eris api compatibility.
*/
globalUnblock(): void {}
warnUser(): void {
// LOG IT ENOUGH TIMES TO MAKE USER SEE IT CLEARLY
for (let i = 0; i < 10; i++) {
console.warn(
'[WARNING] Using internal RestManager since no proxy rest manager was provided. THIS IS NOT RECOMMENDED. Please use a proxy rest manager. If you need help setting it up, join discord.gg/ddeno',
)
}
}
/**
* Make an API request
* @deprecated Use a proxy rest instead.
*/
async request(method: RequestMethods, url: string, auth?: boolean, body?: any, file?: FileContent): Promise<unknown> {
if (file) body.file = file
return await this.discordeno.makeRequest(method, url, body)
}
routefy(url: string, method: RequestMethods): string {
return this.discordeno.simplifyUrl(url, method)
}
toString(): string {
return '[RequestHandler]'
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(this, ['globalBlock', 'latencyRef', 'options', 'ratelimits', 'readyQueue', 'userAgent', ...props])
}
}
export default RequestHandler

View File

@@ -0,0 +1,146 @@
/* eslint-disable no-useless-call */
import type { DiscordInvite, DiscordInviteCreate, DiscordMemberWithUser, TargetTypes } from '@discordeno/types'
import Base from '../Base.js'
import type Client from '../Client.js'
import type Channel from './channels/Channel.js'
import Guild from './guilds/Guild.js'
import Member from './guilds/Member.js'
import User from './users/User.js'
export class Invite {
/** The client object. */
client: Client
/** The invite code (unique Id) */
code: string
/** The channel this invite is for */
channel?: Channel
/** The guild this invite is for. */
guild?: Guild
/** The user who created this invite. */
inviter?: User
/** The amount of times this invite has been used. */
uses: number | null = null
/** The amount of times this invite can be used. */
maxUses: number | null = null
/** How long the invite is valid for (in seconds) */
maxAge: number | null = null
/** Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */
temporary: boolean = false
/** The time at which the invite was created */
createdAt?: number
presenceCount?: number | null
memberCount?: number | null
stageInstance?: {
members: Member[]
participantCount: number
speakerCount: number
topic: string
} | null
targetApplicationID?: string | null
targetType?: TargetTypes | null
targetUser?: User | null
constructor(data: DiscordInvite | DiscordInviteCreate, client: Client) {
// super();
this.client = client
this.code = data.code
this.channel = data.channel
if (data.inviter) {
this.inviter = new User(data.inviter, client)
client.users.set(this.inviter.id, this.inviter)
}
if (this.isInviteCreate(data)) {
this.uses = data.uses !== undefined ? data.uses : null
this.maxUses = data.max_uses !== undefined ? data.max_uses : null
this.maxAge = data.max_age !== undefined ? data.max_age : null
this.temporary = data.temporary !== undefined ? data.temporary : false
this.createdAt = Date.parse(data.created_at)
} else {
if (data.guild) {
if (client.guilds.has(data.guild.id!)) {
if (data.channel) {
// @ts-expect-error should work i think dumb partials
const channel = new GuildChannel(data.channel, client)
client.guilds.get(data.guild.id!)?.channels.set(channel.id, channel)
}
} else {
this.guild = new Guild(data.guild, client)
}
}
this.presenceCount = data.approximate_presence_count !== undefined ? data.approximate_presence_count : null
this.memberCount = data.approximate_member_count !== undefined ? data.approximate_member_count : null
if (data.stage_instance !== undefined) {
this.stageInstance = {
members: data.stage_instance.members.map((m) => {
const member = new Member(m as DiscordMemberWithUser, this.guild, client)
this.guild.members.set(member.id, member)
return member
}),
participantCount: data.stage_instance.participant_count,
speakerCount: data.stage_instance.speaker_count,
topic: data.stage_instance.topic,
}
} else {
this.stageInstance = null
}
}
this.targetApplicationID = data.target_application !== undefined ? data.target_application.id : null
this.targetType = data.target_type !== undefined ? data.target_type : null
this.targetUser = data.target_user !== undefined ? new User(data.target_user, client) : null
if (this.targetUser) client.users.set(this.targetUser.id, this.targetUser)
}
/**
* @deprecated Use .client
*/
get _client(): Client {
return this.client
}
/**
* @deprecated Use .createdAt
*/
get _createdAt(): number | undefined {
return this.createdAt
}
/** Delete the invite */
async delete(reason?: string): Promise<void> {
return await this.client.deleteInvite.call(this.client, this.code, reason)
}
toString(): string {
return `[Invite ${this.code}]`
}
toJSON(props = []): Record<string, any> {
return Base.prototype.toJSON([
'channel',
'code',
'createdAt',
'guild',
'maxAge',
'maxUses',
'memberCount',
'presenceCount',
'revoked',
'temporary',
'uses',
...props,
])
}
isInviteCreate(data: DiscordInvite | DiscordInviteCreate): data is DiscordInviteCreate {
return Reflect.has(data, 'created_at')
}
}
export default Invite

View File

@@ -0,0 +1,438 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { DiscordEmbed, DiscordMessageActivity, DiscordStickerItem } from '@discordeno/types'
import {
MessageTypes,
type DiscordApplication,
type DiscordAttachment,
type DiscordMemberWithUser,
type DiscordMessage,
type DiscordMessageComponents,
type InteractionTypes,
} from '@discordeno/types'
import Base from '../Base.js'
import type Client from '../Client.js'
import { MESSAGE_LINK } from '../Endpoints.js'
import { MessageFlags, type GetMessageReactionOptions, type MessageContentEdit, type MessageWebhookContent } from '../typings.js'
import type NewsChannel from './channels/News.js'
import type PrivateChannel from './channels/Private.js'
import type TextChannel from './channels/Text.js'
import type NewsThreadChannel from './channels/threads/NewsThread.js'
import type PublicThreadChannel from './channels/threads/PublicThread.js'
import type Guild from './guilds/Guild.js'
import Member from './guilds/Member.js'
import User from './users/User.js'
export class Message extends Base {
/** The client manager. */
client: Client
/** Timestamp of message creation */
timestamp: number
/** The type of the message */
type: MessageTypes
/** The channel the message is in. Can be partial with only the id if the channel is not cached. */
channel: PrivateChannel | TextChannel | NewsChannel | { id: string }
/** The message content. */
content: string
/** An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `me` (Boolean) and `count` (Number) for that specific reaction emoji. */
reactions: Record<string, { me: boolean; count: number }>
/** The ID of the guild this message is in (undefined if in DMs) */
guildID?: string
/** ID of the webhook that sent the message */
webhookID?: string
/** An object containing the reference to the original message if it is a crossposted message or reply */
messageReference?: {
/** The id of the original message this message was crossposted from */
messageID?: string
/** The id of the channel this message was crossposted from */
channelID?: string
/** The id of the guild this message was crossposted from */
guildID?: string
} | null
/** The flags that are enabled on this message. */
flags: number
/** The message author */
author: User
/** The message author with server-specific data */
member?: Member
/** The message that was replied to. If undefined, message data was not received. If null, the message was deleted. */
referencedMessage?: Message | null
/** An object containing info about the interaction the message is responding to, if applicable */
interaction?: {
/** The id of the interaction */
id: string
/** The type of interaction */
type: InteractionTypes
/** The name of the command */
name: string
/** The user who invoked the interaction */
user: User
/** The member who invoked the interaction */
member?: Member
}
/** Array of mentioned users */
mentions: User[] = []
/** Array of mentioned roles' ids */
roleMentions: string[] = []
/** Array of attachments */
attachments: DiscordAttachment[] = []
/** Array of embeds */
embeds: DiscordEmbed[] = []
/** The stickers sent with the message */
stickerItems: DiscordStickerItem[] = []
/** An array of component objects */
components: DiscordMessageComponents = []
/** The activity specified in the message */
activity?: DiscordMessageActivity
/** The application of the activity in the message */
application?: Partial<DiscordApplication>
/** The ID of the interaction's application */
applicationID?: string
/** Timestamp of latest message edit */
editedTimestamp?: number
/** Whether the message mentions everyone/here or not */
mentionEveryone: boolean = false
/** Whether the message is pinned or not */
pinned: boolean = false
/** Whether to play the message using TTS or not */
tts: boolean = false
constructor(data: DiscordMessage, client: Client) {
super(data.id)
this.client = client
this.timestamp = Date.parse(data.timestamp)
this.type = data.type || MessageTypes.Default
this.timestamp = Date.parse(data.timestamp)
this.channel = this.client.getChannel(data.channel_id) || {
id: data.channel_id,
}
this.content = ''
this.reactions = {}
this.guildID = data.guild_id
this.webhookID = data.webhook_id
if (data.message_reference) {
this.messageReference = {
messageID: data.message_reference.message_id,
channelID: data.message_reference.channel_id,
guildID: data.message_reference.guild_id,
}
} else {
this.messageReference = null
}
this.flags = data.flags || 0
this.author = new User(data.author, client)
if (!data.webhook_id) {
this.client.users.set(this.author.id, this.author)
}
if (data.referenced_message) {
const channel = this.client.getChannel(data.referenced_message.channel_id) as TextChannel
this.referencedMessage = new Message(data.referenced_message, this.client)
if (channel) {
channel.messages.set(this.referencedMessage.id, this.referencedMessage)
}
} else {
this.referencedMessage = data.referenced_message
}
if (data.interaction) {
this.interaction = {
id: data.interaction.id,
type: data.interaction.type,
name: data.interaction.name,
user: new User(data.interaction.user, client),
}
if (data.interaction.member) {
data.interaction.member.user = data.interaction.user
if (this.guild) {
this.interaction.member = new Member(
// @ts-expect-error some eris magic at play here
data.interaction.member,
this.guild,
client,
)
this.guild.members.set(this.interaction.member.id, this.interaction.member)
} else {
// @ts-expect-error some eris magic at play here
interactionMember = data.interaction.member
}
} else if (this.guild?.members.has(data.interaction.user.id)) {
this.interaction.member = this.guild.members.get(data.interaction.user.id)
}
}
if (this.guild) {
if (data.member) {
data.member.user = data.author
this.member = new Member(data.member as DiscordMemberWithUser, this.guild, client)
this.guild.members.set(this.member.id, this.member)
} else if (this.guild.members.has(this.author.id)) {
this.member = this.guild.members.get(this.author.id)
}
}
this.update(data)
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client
}
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
update(data: DiscordMessage) {
if (data.pinned !== undefined) this.pinned = !!data.pinned
if (data.tts !== undefined) this.tts = data.tts
if (data.attachments !== undefined) this.attachments = data.attachments
if (data.embeds !== undefined) this.embeds = data.embeds
if (data.flags !== undefined) this.flags = data.flags
if (data.activity !== undefined) this.activity = data.activity
if (data.components !== undefined) this.components = data.components
if (data.application !== undefined) this.application = data.application
if (data.edited_timestamp) this.editedTimestamp = Date.parse(data.edited_timestamp)
if (data.application_id !== undefined) this.applicationID = data.application_id
if (data.sticker_items !== undefined) this.stickerItems = data.sticker_items
if (data.content !== undefined) {
this.content = data.content || ''
this.mentionEveryone = !!data.mention_everyone
this.mentions = []
for (const mention of data.mentions ?? []) {
const user = new User(mention, this.client)
this.client.users.set(user.id, user)
if (mention.member && this.guild) {
mention.member.user = mention
this.guild.members.set(mention.id, new Member(mention.member as DiscordMemberWithUser, this.guild, this.client))
}
}
if (data.mention_roles) this.roleMentions = data.mention_roles
}
if (data.reactions) {
for (const reaction of data.reactions ?? []) {
this.reactions[reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name!] = {
count: reaction.count,
me: reaction.me,
}
}
}
}
get channelMentions(): string[] {
return (this.content.match(/<#[0-9]+>/g) ?? []).map((mention) => mention.substring(2, mention.length - 1))
}
get cleanContent() {
let cleanContent = this.content?.replace(/<a?(:\w+:)[0-9]+>/g, '$1') || ''
let authorName = this.author.username
if (this.guild) {
const member = this.guild.members.get(this.author.id)
if (member?.nick) {
authorName = member.nick
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${this.author.id}>`, 'g'), '@\u200b' + authorName)
if (this.mentions) {
this.mentions.forEach((mention) => {
if (this.guild) {
const member = this.guild.members.get(mention.id)
if (member?.nick) {
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + member.nick)
}
}
cleanContent = cleanContent.replace(new RegExp(`<@!?${mention.id}>`, 'g'), '@\u200b' + mention.username)
})
}
if (this.guild && this.roleMentions) {
for (const roleID of this.roleMentions) {
const role = this.guild.roles.get(roleID)
const roleName = role ? role.name : 'deleted-role'
cleanContent = cleanContent.replace(new RegExp(`<@&${roleID}>`, 'g'), '@\u200b' + roleName)
}
}
this.channelMentions.forEach((id) => {
const channel = this.client.getChannel(id) as TextChannel
if (channel?.name && channel.mention) {
cleanContent = cleanContent.replace(channel.mention, '#' + channel.name)
}
})
return cleanContent.replace(/@everyone/g, '@\u200beveryone').replace(/@here/g, '@\u200bhere')
}
get jumpLink() {
return `${this.client.CLIENT_URL}${MESSAGE_LINK(this.guildID ?? '@me', this.channel.id, this.id)}`
}
/** Add a reaction to a message */
async addReaction(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.addMessageReaction.call(this.client, this.channel.id, this.id, reaction)
}
/** Create a thread with this message */
async createThreadWithMessage(options: {
name: string
autoArchiveDuration: 60 | 1440 | 4320 | 10080
}): Promise<NewsThreadChannel | PublicThreadChannel> {
return await this.client.createThreadWithMessage.call(this.client, this.channel.id, this.id, options)
}
/** Crosspost (publish) a message to subscribed channels (NewsChannel only) */
async crosspost(): Promise<Message> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be crossposted')
}
return await this.client.crosspostMessage.call(this.client, this.channel.id, this.id)
}
/** Delete the message */
async delete(reason?: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be deleted')
}
return await this.client.deleteMessage.call(this.client, this.channel.id, this.id, reason)
}
/** Delete the message as a webhook */
async deleteWebhook(token: string): Promise<void> {
if (!this.webhookID) throw new Error('Message is not a webhook')
if (this.flags & MessageFlags.EPHEMERAL) throw new Error('Ephemeral messages cannot be deleted')
return await this.client.deleteWebhookMessage.call(this.client, this.webhookID, token, this.id)
}
/** Edit the message */
async edit(content: MessageContentEdit): Promise<Message> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be edited via this method')
}
return await this.client.editMessage.call(this.client, this.channel.id, this.id, content)
}
/** Edit the message as a webhook */
async editWebhook(token: string, options: MessageWebhookContent): Promise<Message> {
if (!this.webhookID) {
throw new Error('Message is not a webhook')
}
return await this.client.editWebhookMessage.call(this.client, this.webhookID, token, this.id, options)
}
/** Get a list of users who reacted with a specific reaction */
async getReaction(reaction: string, options?: GetMessageReactionOptions): Promise<User[]> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.getMessageReaction.call(this.client, this.channel.id, this.id, reaction, options)
}
/** Pin the message */
async pin(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be pinned')
}
return await this.client.pinMessage.call(this.client, this.channel.id, this.id)
}
/** Remove a reaction from a message */
async removeReaction(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReaction.call(this.client, this.channel.id, this.id, reaction)
}
/** Remove all reactions from a message for a single emoji */
async removeReactionEmoji(reaction: string): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReactionEmoji.call(this.client, this.channel.id, this.id, reaction)
}
/** Remove all reactions from a message */
async removeReactions(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot have reactions')
}
return await this.client.removeMessageReactions.call(this.client, this.channel.id, this.id)
}
/** Unpin the message */
async unpin(): Promise<void> {
if (this.flags & MessageFlags.EPHEMERAL) {
throw new Error('Ephemeral messages cannot be pinned')
}
return await this.client.unpinMessage.call(this.client, this.channel.id, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'activity',
'application',
'attachments',
'author',
'content',
'editedTimestamp',
'embeds',
'flags',
'guildID',
'hit',
'member',
'mentionEveryone',
'mentions',
'messageReference',
'pinned',
'reactions',
'referencedMesssage',
'roleMentions',
'stickers',
'stickerItems',
'timestamp',
'tts',
'type',
'webhookID',
...props,
])
}
}
export default Message

View File

@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { BitwisePermissionFlags } from '@discordeno/types'
import { Base } from '../Base.js'
import type { BigString } from '../Client.js'
export class Permission {
allow: bigint
deny: bigint
_json?: Record<string, boolean>
constructor(allow: BigString | number = 0, deny: BigString | number = 0) {
this.allow = BigInt(allow)
this.deny = BigInt(deny)
}
get isAdmin(): boolean {
return !!(this.allow & BigInt(BitwisePermissionFlags.ADMINISTRATOR))
}
get json() {
if (!this._json) {
this._json = {}
for (const key of Object.keys(BitwisePermissionFlags)) {
if (typeof key === 'number') continue
const perm = key as keyof typeof BitwisePermissionFlags
if (this.allow & BigInt(BitwisePermissionFlags[perm])) {
this._json[perm] = true
} else if (this.deny & BigInt(BitwisePermissionFlags[perm])) {
this._json[perm] = false
}
}
}
return this._json
}
/** Check if this permission allows a specific permission */
has(permission: bigint | keyof typeof BitwisePermissionFlags): boolean {
if (this.isAdmin) return true
if (typeof permission === 'bigint') {
return (this.allow & permission) === permission
}
return !!(this.allow & BigInt(BitwisePermissionFlags[permission]))
}
toString() {
return `[${this.constructor.name} +${this.allow} -${this.deny}]`
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(['allow', 'deny', ...props])
}
}
export default Permission

View File

@@ -0,0 +1,22 @@
import type { DiscordOverwrite, OverwriteTypes } from '@discordeno/types'
import { Base } from '../Base.js'
import type { BigString } from '../Client.js'
import Permission from './Permission.js'
export class PermissionOverwrite extends Permission {
id: BigString
type: OverwriteTypes
constructor(data: DiscordOverwrite) {
super(data.allow, data.deny)
this.id = data.id
this.type = data.type
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(['id', 'type', ...props])
}
}
export default PermissionOverwrite

View File

@@ -0,0 +1,12 @@
import type { BigString } from '@discordeno/types'
import type Collection from '../../Collection.js'
import type { AnyGuildChannel } from '../../typings.js'
import GuildChannel from './Guild.js'
export class CategoryChannel extends GuildChannel {
get channels(): Collection<BigString, Exclude<AnyGuildChannel, CategoryChannel>> {
return this.guild?.channels.filter((c) => c.parentID === this.id) as unknown as Collection<BigString, Exclude<AnyGuildChannel, CategoryChannel>>
}
}
export default CategoryChannel

View File

@@ -0,0 +1,81 @@
import { ChannelTypes, type DiscordChannel } from "@discordeno/types";
import Base from "../../Base.js";
import type Client from "../../Client.js";
import type { AnyChannel } from "../../typings.js";
import CategoryChannel from "./Category.js";
import GuildChannel from "./Guild.js";
import NewsChannel from "./News.js";
import PrivateChannel from "./Private.js";
import StageChannel from "./Stage.js";
import TextChannel from "./Text.js";
import TextVoiceChannel from "./TextVoice.js";
import NewsThreadChannel from "./threads/NewsThread.js";
import PrivateThreadChannel from "./threads/PrivateThread.js";
import PublicThreadChannel from "./threads/PublicThread.js";
export class Channel extends Base {
type: ChannelTypes;
client: Client;
constructor(data: DiscordChannel | Pick<DiscordChannel, "id" | "permissions" | "name" | "type">, client: Client) {
super(data.id);
this.type = data.type;
this.client = client;
}
get mention(): string {
return `<#${this.id}>`;
}
static from(data: DiscordChannel, client: Client): AnyChannel {
switch (data.type) {
case ChannelTypes.GuildText: {
return new TextChannel(data, client);
}
case ChannelTypes.DM: {
return new PrivateChannel(data, client);
}
case ChannelTypes.GuildVoice: {
return new TextVoiceChannel(data, client);
}
case ChannelTypes.GuildCategory: {
return new CategoryChannel(data, client);
}
case ChannelTypes.GuildAnnouncement: {
return new NewsChannel(data, client);
}
case ChannelTypes.AnnouncementThread: {
return new NewsThreadChannel(data, client);
}
case ChannelTypes.PublicThread: {
return new PublicThreadChannel(data, client);
}
case ChannelTypes.PrivateThread: {
return new PrivateThreadChannel(data, client);
}
case ChannelTypes.GuildStageVoice: {
return new StageChannel(data, client);
}
}
if (data.guild_id) {
if (data.last_message_id !== undefined) {
client.emit("warn", new Error(`Unknown guild text channel type: ${data.type}\n${JSON.stringify(data)}`));
return new TextChannel(data, client);
}
client.emit("warn", new Error(`Unknown guild channel type: ${data.type}\n${JSON.stringify(data)}`));
return new GuildChannel(data, client);
}
client.emit("warn", new Error(`Unknown channel type: ${data.type}\n${JSON.stringify(data)}`));
return new Channel(data, client);
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
"type",
...props,
]);
}
}
export default Channel;

View File

@@ -0,0 +1,113 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { BigString, DiscordChannel, OverwriteTypes } from '@discordeno/types'
import { BitwisePermissionFlags } from '@discordeno/types'
import { Collection } from '@discordeno/utils'
import type Client from '../../Client.js'
import type { EditChannelOptions, EditChannelPositionOptions } from '../../typings.js'
import type Guild from '../guilds/Guild.js'
import Permission from '../Permission.js'
import PermissionOverwrite from '../PermissionOverwrite.js'
import Channel from './Channel.js'
import type Member from './threads/Member.js'
import ThreadChannel from './threads/Thread.js'
export class GuildChannel extends Channel {
position: number
name: string
parentID?: BigString | null
guild: Guild
nsfw: boolean
permissionOverwrites = new Collection<BigString, PermissionOverwrite>()
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.position = data.position ?? 0
this.guild = client.guilds.get(data.guild_id!)!
this.name = data.name ?? ''
this.parentID = data.parent_id
this.nsfw = !!data.nsfw
}
update(data: DiscordChannel): void {
if (data.type !== undefined) {
this.type = data.type
}
if (data.name !== undefined) {
this.name = data.name
}
if (data.position !== undefined) {
this.position = data.position
}
if (data.parent_id !== undefined) {
this.parentID = data.parent_id
}
this.nsfw = !!data.nsfw
if (data.permission_overwrites) {
data.permission_overwrites.forEach((overwrite) => {
const perms = new PermissionOverwrite(overwrite)
this.permissionOverwrites.set(perms.id, perms)
})
}
}
/** Delete the channel */
async delete(reason?: string): Promise<void> {
return await this.client.deleteChannel.call(this.client, this.id, reason)
}
/** Delete a channel permission overwrite */
async deletePermission(overwriteID: BigString, reason?: string): Promise<void> {
return await this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason)
}
/** Edit the channel's properties */
async edit(options: EditChannelOptions, reason?: string) {
return await this.client.editChannel.call(this.client, this.id, options, reason)
}
/** Create a channel permission overwrite */
async editPermission(overwriteID: BigString, allow: bigint | number, deny: bigint | number, type: OverwriteTypes, reason?: string): Promise<void> {
return await this.client.editChannelPermission.call(this.client, this.id, overwriteID, allow, deny, type, reason)
}
/** Edit the channel's position. Note that channel position numbers are lowest on top and highest at the bottom. */
async editPosition(position: number, options?: EditChannelPositionOptions): Promise<void> {
return await this.client.editChannelPosition.call(this.client, this.id, position, options)
}
/** Get the channel-specific permissions of a member */
permissionsOf(memberID: BigString | Member): Permission {
const member = ['string', 'bigint'].includes(typeof memberID) ? this.guild.members.get(memberID as BigString)! : (memberID as Member)
let permission = this.guild.permissionsOf(member).allow
if (permission & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
}
const channel = this instanceof ThreadChannel ? this.guild.channels.get(this.parentID) : this
let overwrite = channel?.permissionOverwrites.get(this.guild.id)
if (overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow
}
let deny = 0n
let allow = 0n
for (const roleID of member.roles) {
if ((overwrite = channel?.permissionOverwrites.get(roleID))) {
deny |= overwrite.deny
allow |= overwrite.allow
}
}
permission = (permission & ~deny) | allow
overwrite = channel?.permissionOverwrites.get(member.id)
if (overwrite) {
permission = (permission & ~overwrite.deny) | overwrite.allow
}
return new Permission(permission)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['name', 'nsfw', 'parentID', 'permissionOverwrites', 'position', ...props])
}
}
export default GuildChannel

View File

@@ -0,0 +1,28 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordChannel } from '@discordeno/types'
import type Client from '../../Client.js'
import type { ChannelFollow } from '../../typings.js'
import type Message from '../Message.js'
import TextChannel from './Text.js'
export class NewsChannel extends TextChannel {
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client, messageLimit)
this.rateLimitPerUser = 0
this.update(data)
}
/** Crosspost (publish) a message to subscribed channels */
async crosspostMessage(messageID: BigString): Promise<Message> {
return await this.client.crosspostMessage.call(this.client, this.id, messageID)
}
/** Follow this channel in another channel. This creates a webhook in the target channel */
async follow(webhookChannelID: BigString): Promise<ChannelFollow> {
return await this.client.followChannel.call(this.client, this.id, webhookChannelID)
}
}
export default NewsChannel

View File

@@ -0,0 +1,104 @@
/* eslint-disable no-useless-call */
import { ChannelTypes, type BigString, type DiscordChannel, type GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit } from '../../typings.js'
import type Message from '../Message.js'
import User from '../users/User.js'
import Channel from './Channel.js'
export class PrivateChannel extends Channel {
/** The ID of the last message in this channel */
lastMessageID?: string | null
// TODO: THIS A THING IN DMS????
/** The rate limit per user. */
rateLimitPerUser?: number
/** Collection of Messages in this channel */
messages: Collection<string, Message>
/** The recipient in this private channel */
recipient?: User
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.lastMessageID = data.last_message_id
this.rateLimitPerUser = data.rate_limit_per_user
if (this.type === ChannelTypes.DM || this.type === undefined) {
if (data.recipients?.[0]) this.recipient = new User(data.recipients[0], client)
}
this.messages = new Collection()
this.messages.limit = client.options.messageLimit
}
/** Add a reaction to a message */
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Create a message in a text channel */
async createMessage(content: MessageContent, file: FileContent | FileContent[]): Promise<Message> {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
// TODO: REASONS ARE A THING FOR AUDIT LOGS IN DMS???
/** Delete a message */
async deleteMessage(messageID: BigString, reason?: string): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/** Edit a message */
async editMessage(messageID: BigString, content: MessageContentEdit): Promise<Message> {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
/** Get a previous message in a text channel */
async getMessage(messageID: BigString): Promise<Message> {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
/** Get a list of users who reacted with a specific reaction */
async getMessageReaction(messageID: BigString, reaction: string, options: GetMessageReactionOptions): Promise<User[]> {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/** Get a previous message in a text channel */
async getMessages(options: GetMessagesOptions): Promise<Message[]> {
return await this.client.getMessages.call(this.client, this.id, options)
}
/** Get all the pins in a text channel */
async getPins(): Promise<Message[]> {
return await this.client.getPins.call(this.client, this.id)
}
/** Leave the channel */
async leave(): Promise<void> {
return await this.client.deleteChannel.call(this.client, this.id)
}
/** Pin a message */
async pinMessage(messageID: BigString): Promise<void> {
return await this.client.pinMessage.call(this.client, this.id, messageID)
}
/** Remove a reaction from a message */
async removeMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Send typing status in a text channel */
async sendTyping(): Promise<void> {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
/** Unpin a message */
async unpinMessage(messageID: BigString): Promise<void> {
return await this.client.unpinMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['call', 'lastCall', 'lastMessageID', 'messages', 'recipient', ...props])
}
}
export default PrivateChannel

View File

@@ -0,0 +1,46 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { DiscordChannel } from "@discordeno/types"
import type { StageInstanceOptions } from "../../typings.js"
import type StageInstance from "../guilds/StageInstance.js"
import VoiceChannel from "./Voice.js"
export class StageChannel extends VoiceChannel {
/** The topic of the channel */
topic?: string | null
update(data: DiscordChannel): void {
super.update(data)
if (data.topic !== undefined) {
this.topic = data.topic
}
}
/** Create a stage instance */
async createInstance(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.createStageInstance.call(this.client, this.id, options)
}
/** Delete the stage instance for this channel */
async deleteInstance(): Promise<void> {
return await this.client.deleteStageInstance.call(this.client, this.id)
}
/** Update the stage instance for this channel */
async editInstance(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.editStageInstance.call(this.client, this.id, options)
}
/** Get the stage instance for this channel */
async getInstance(): Promise<StageInstance> {
return await this.client.getStageInstance.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['topic', ...props])
}
}
export default StageChannel

View File

@@ -0,0 +1,375 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type {
CreateChannelInviteOptions,
CreateThreadOptions,
CreateThreadWithoutMessageOptions,
FileContent,
GetArchivedThreadsOptions,
GetMessageReactionOptions,
ListedChannelThreads,
MessageContent,
PurgeChannelOptions,
} from '../../typings.js'
import type Invite from '../Invite.js'
import type Message from '../Message.js'
import GuildChannel from './Guild.js'
import type PrivateThreadChannel from './threads/PrivateThread.js'
import type PublicThreadChannel from './threads/PublicThread.js'
export class TextChannel extends GuildChannel {
/** Collection of Messages in this channel */
messages: Collection<string, Message>
/** The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled */
rateLimitPerUser: number | null
/** The ID of the last message in this channel */
lastMessageID?: string | null
/** The timestamp of the last pinned message */
lastPinTimestamp?: number | null
/** Default duration for newly created threads, in minutes, to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */
defaultAutoArchiveDuration?: number
/** The channel topic (0-4096 characters for GUILD_FORUM channels, 0-1024 characters for all others) */
topic?: string | null
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.messages = new Collection()
if (messageLimit == null) this.messages.limit = client.options.messageLimit
else this.messages.limit = messageLimit
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user
this.lastMessageID = data.last_message_id ?? null
this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.rate_limit_per_user !== undefined) this.rateLimitPerUser = data.rate_limit_per_user
if (data.topic !== undefined) this.topic = data.topic
if (data.default_auto_archive_duration !== undefined) this.defaultAutoArchiveDuration = data.default_auto_archive_duration
}
/** Add a reaction to a message */
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/** Create an invite for the channel */
async createInvite(options?: CreateChannelInviteOptions, reason?: string): Promise<Invite> {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Create a message in the channel
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {String} [content.messageReferenceID] [DEPRECATED] The ID of the message should be replied to. Use `messageReference` instead
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async createMessage(content: MessageContent, file: FileContent | FileContent[]) {
return this.client.createMessage.call(this.client, this.id, content, file)
}
/**
* Create a thread with an existing message
* @arg {String} messageID The ID of the message to create the thread from
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {String} options.name The thread channel name
* @returns {Promise<NewsThreadChannel | PublicThreadChannel>}
*/
async createThreadWithMessage(messageID: string, options: CreateThreadOptions) {
return this.client.createThreadWithMessage.call(this.client, this.id, messageID, options)
}
/**
* Create a thread without an existing message
* @arg {Object} options The thread options
* @arg {Number} options.autoArchiveDuration Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080
* @arg {boolean} [options.invitable] Whether non-moderators can add other non-moderators to the thread (private threads only)
* @arg {String} options.name The thread channel name
* @arg {Number} [options.type] The channel type of the thread to create. It is recommended to explicitly set this property as this will be a required property in API v10
* @returns {Promise<PrivateThreadChannel>}
*/
async createThreadWithoutMessage(options: CreateThreadWithoutMessageOptions) {
return this.client.createThreadWithoutMessage.call(this.client, this.id, options)
}
/**
* Create a channel webhook
* @arg {Object} options Webhook options
* @arg {String} [options.avatar] The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings
* @arg {String} options.name The default name
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Object>} Resolves with a webhook object
*/
async createWebhook(options: { name: string; avatar?: string | null }, reason: string) {
return this.client.createChannelWebhook.call(this.client, this.id, options, reason)
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessage(messageID: string, reason: string) {
return this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessages(messageIDs: string[], reason: string) {
return this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [content.file] A file object (or an Array of them)
* @arg {Buffer} content.file[].file A buffer containing file data
* @arg {String} content.file[].name What to name the file
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: string) {
return this.client.editMessage.call(this.client, this.id, messageID, content)
}
/** Get all archived threads in this channel */
async getArchivedThreads(type: 'private', options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PrivateThreadChannel>>
async getArchivedThreads(type: 'public', options?: GetArchivedThreadsOptions): Promise<ListedChannelThreads<PublicThreadChannel>>
async getArchivedThreads(
type: 'public' | 'private',
options?: GetArchivedThreadsOptions,
): Promise<ListedChannelThreads<PrivateThreadChannel | PublicThreadChannel>> {
return await this.client.getArchivedThreads.call(this.client, this.id, type as 'public', options)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return this.client.getChannelInvites.call(this.client, this.id)
}
/**
* Get joined private archived threads in this channel
* @arg {Object} [options] Additional options when requesting archived threads
* @arg {Date} [options.before] List of threads to return before the timestamp
* @arg {Number} [options.limit] Maximum number of threads to return
* @returns {Promise<Object>} An object containing an array of `threads`, an array of `members` and whether the response `hasMore` threads that could be returned in a subsequent call
*/
async getJoinedPrivateArchivedThreads(options: GetArchivedThreadsOptions) {
return this.client.getJoinedPrivateArchivedThreads.call(this.client, this.id, options)
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
async getMessage(messageID: string) {
return this.client.getMessage.call(this.client, this.id, messageID)
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) {
return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @returns {Promise<Array<Message>>}
*/
async getMessages(options: GetMessagesOptions) {
return this.client.getMessages.call(this.client, this.id, options)
}
/**
* Get all the pins in the channel
* @returns {Promise<Array<Message>>}
*/
async getPins() {
return this.client.getPins.call(this.client, this.id)
}
/**
* Get all the webhooks in the channel
* @returns {Promise<Array<Object>>} Resolves with an array of webhook objects
*/
async getWebhooks() {
return this.client.getChannelWebhooks.call(this.client, this.id)
}
/**
* Pin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async pinMessage(messageID: string) {
return this.client.pinMessage.call(this.client, this.id, messageID)
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
async purge(limit: PurgeChannelOptions) {
return this.client.purgeChannel.call(this.client, this.id, limit)
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
async removeMessageReaction(messageID: string, reaction: string, userID: string) {
return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async removeMessageReactionEmoji(messageID: string, reaction: string) {
return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async removeMessageReactions(messageID: string) {
return this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
async sendTyping() {
return this.client.sendChannelTyping.call(this.client, this.id)
}
/**
* Unpin a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async unpinMessage(messageID: string) {
return this.client.unpinMessage.call(this.client, this.id, messageID)
}
/**
* Un-send a message. You're welcome Programmix
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async unsendMessage(messageID: string) {
return this.client.deleteMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['lastMessageID', 'lastPinTimestamp', 'messages', 'rateLimitPerUser', 'topic', ...props])
}
}
export default TextChannel

View File

@@ -0,0 +1,271 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type {
CreateInviteOptions,
FileContent,
GetMessageReactionOptions,
MessageContent,
MessageContentEdit,
PurgeChannelOptions,
} from '../../typings.js'
import type Message from '../Message.js'
import VoiceChannel from './Voice.js'
/**
* Represents a Text-in-Voice channel. See VoiceChannel for more properties and methods.
* @extends VoiceChannel
* @prop {String} lastMessageID The ID of the last message in this channel
* @prop {Collection<Message>} messages Collection of Messages in this channel
* @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled
*/
export class TextVoiceChannel extends VoiceChannel {
lastMessageID: string | null
messages: Collection<string, Message>
rateLimitPerUser: number | null
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.messages = new Collection()
if (messageLimit == null) this.messages.limit = client.options.messageLimit
else this.messages.limit = messageLimit
this.lastMessageID = data.last_message_id ?? null
this.rateLimitPerUser = data.rate_limit_per_user == null ? null : data.rate_limit_per_user
}
update(data: DiscordChannel) {
super.update(data)
// "not yet, possibly TBD"
if (data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user
}
}
/**
* Add a reaction to a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async addMessageReaction(messageID: string, reaction: string) {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
async createInvite(options: CreateInviteOptions, reason: string) {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Create a message in the channel
* Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object} [content.messageReference] The message reference, used when replying to messages
* @arg {String} [content.messageReference.channelID] The channel ID of the referenced message
* @arg {Boolean} [content.messageReference.failIfNotExists=true] Whether to throw an error if the message reference doesn't exist. If false, and the referenced message doesn't exist, the message is created without a referenced message
* @arg {String} [content.messageReference.guildID] The guild ID of the referenced message
* @arg {String} content.messageReference.messageID The message ID of the referenced message. This cannot reference a system message
* @arg {Array<String>} [content.stickerIDs] An array of IDs corresponding to the stickers to send
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object} [file] A file object
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async createMessage(content: MessageContent, file?: FileContent | FileContent[]) {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
/**
* Delete a message
* @arg {String} messageID The ID of the message
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessage(messageID: string, reason: string) {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
/**
* Bulk delete messages (bot accounts only)
* @arg {Array<String>} messageIDs Array of message IDs to delete
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise}
*/
async deleteMessages(messageIDs: string[], reason: string) {
return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
/**
* Edit a message
* @arg {String} messageID The ID of the message
* @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} content.content A content string
* @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default)
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#message-object-message-flags) for flags reference
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: MessageContentEdit) {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return await this.client.getChannelInvites.call(this.client, this.id)
}
/**
* Get a previous message in the channel
* @arg {String} messageID The ID of the message
* @returns {Promise<Message>}
*/
async getMessage(messageID: string) {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
/**
* Get a list of users who reacted with a specific reaction
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior)
* @arg {Number} [options.limit=100] The maximum number of users to get
* @arg {String} [options.after] Get users after this user ID
* @returns {Promise<Array<User>>}
*/
async getMessageReaction(messageID: string, reaction: string, options: GetMessageReactionOptions) {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
/**
* Get previous messages in the channel
* @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.around] Get messages around this message ID (does not work with limit > 100)
* @arg {String} [options.before] Get messages before this message ID
* @arg {Number} [options.limit=50] The max number of messages to get
* @returns {Promise<Array<Message>>}
*/
async getMessages(options: GetMessagesOptions) {
return await this.client.getMessages.call(this.client, this.id, options)
}
/**
* Purge previous messages in the channel with an optional filter (bot accounts only)
* @arg {Object} options Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit`
* @arg {String} [options.after] Get messages after this message ID
* @arg {String} [options.before] Get messages before this message ID
* @arg {Function} [options.filter] Optional filter function that returns a boolean when passed a Message object
* @arg {Number} options.limit The max number of messages to search through, -1 for no limit
* @arg {String} [options.reason] The reason to be displayed in audit logs
* @returns {Promise<Number>} Resolves with the number of messages deleted
*/
async purge(options: PurgeChannelOptions) {
return await this.client.purgeChannel.call(this.client, this.id, options)
}
/**
* Remove a reaction from a message
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @arg {String} [userID="@me"] The ID of the user to remove the reaction for
* @returns {Promise}
*/
async removeMessageReaction(messageID: string, reaction: string, userID: string) {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
/**
* Remove all reactions from a message for a single emoji
* @arg {String} messageID The ID of the message
* @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji)
* @returns {Promise}
*/
async removeMessageReactionEmoji(messageID: string, reaction: string) {
return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
/**
* Remove all reactions from a message
* @arg {String} messageID The ID of the message
* @returns {Promise}
*/
async removeMessageReactions(messageID: string) {
return await this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
/**
* Send typing status in the channel
* @returns {Promise}
*/
async sendTyping() {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['lastMessageID', 'messages', 'rateLimitPerUser', ...props])
}
}
export default TextVoiceChannel

View File

@@ -0,0 +1,91 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordChannel } from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type { CreateInviteOptions, TextVoiceChannelTypes, VideoQualityMode } from '../../typings.js'
import type Member from '../guilds/Member.js'
import GuildChannel from './Guild.js'
export class VoiceChannel extends GuildChannel {
bitrate: number = 0
rtcRegion: string | null = null
type: TextVoiceChannelTypes = 0
userLimit: number = 0
videoQualityMode: VideoQualityMode = 0
voiceMembers: Collection<string, Member>
constructor(data: DiscordChannel, client: Client) {
super(data, client)
this.voiceMembers = new Collection()
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.bitrate !== undefined) {
this.bitrate = data.bitrate
}
if (data.rtc_region !== undefined) {
this.rtcRegion = data.rtc_region
}
if (data.user_limit !== undefined) {
this.userLimit = data.user_limit
}
if (data.video_quality_mode !== undefined) {
this.videoQualityMode = data.video_quality_mode
}
}
/**
* Create an invite for the channel
* @arg {Object} [options] Invite generation options
* @arg {Number} [options.maxAge] How long the invite should last in seconds
* @arg {Number} [options.maxUses] How many uses the invite should last for
* @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not
* @arg {Boolean} [options.unique] Whether the invite is unique or not
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Invite>}
*/
async createInvite(options: CreateInviteOptions, reason: string) {
return await this.client.createChannelInvite.call(this.client, this.id, options, reason)
}
/**
* Get all invites in the channel
* @returns {Promise<Array<Invite>>}
*/
async getInvites() {
return await this.client.getChannelInvites.call(this.client, this.id)
}
// TODO: gateway
// /**
// * Joins the channel.
// * @arg {Object} [options] VoiceConnection constructor options
// * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing
// * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not
// * @arg {Boolean} [options.selfMute] Whether the bot joins the channel muted or not
// * @arg {Boolean} [options.selfDeaf] Whether the bot joins the channel deafened or not
// * @returns {Promise<VoiceConnection>} Resolves with a VoiceConnection
// */
// join(options: JoinVoiceChannelOptions) {
// return this.client.joinVoiceChannel.call(this.client, this.id, options);
// }
// TODO: gateway
// /**
// * Leaves the channel.
// */
// leave() {
// return this.client.leaveVoiceChannel.call(this.client, this.id);
// }
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['bitrate', 'rtcRegion', 'userLimit', 'videoQualityMode', 'voiceMembers', ...props])
}
}
export default VoiceChannel

View File

@@ -0,0 +1,42 @@
/* eslint-disable no-useless-call */
import type { BigString, DiscordThreadMember } from "@discordeno/types"
import Base from "../../../Base.js"
import type Client from "../../../Client.js"
import type Member from "../../guilds/Member.js"
export class ThreadMember extends Base {
client: Client
/** The user-thread settings of this member */
flags: number
/** The ID of the thread this member is a part of */
threadID: BigString
/** Timestamp of when the member joined the thread */
joinTimestamp: number
/** The guild member that this thread member belongs to. This will never be present when fetching over REST */
guildMember?: Member
constructor(data: DiscordThreadMember, client: Client) {
super(data.user_id)
this.client = client
this.flags = data.flags
this.threadID = data.id
this.joinTimestamp = Date.parse(data.join_timestamp)
}
get _client(): Client {
return this.client
}
/** Remove the member from the thread */
async leave(): Promise<void> {
return await this._client.leaveThread.call(this._client, this.threadID, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['threadID', 'joinTimestamp', ...props])
}
}
export default ThreadMember

View File

@@ -0,0 +1,5 @@
import ThreadChannel from './Thread.js'
export class NewsThreadChannel extends ThreadChannel {}
export default NewsThreadChannel

View File

@@ -0,0 +1,26 @@
import type { DiscordChannel } from "@discordeno/types";
import type Client from "../../../Client.js";
import ThreadChannel from "./Thread.js";
export class PrivateThreadChannel extends ThreadChannel {
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client, messageLimit);
this.update(data);
}
update(data: DiscordChannel): void {
if(data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
invitable: data.thread_metadata.invitable,
locked: data.thread_metadata.locked
};
}
}
}
export default PrivateThreadChannel;

View File

@@ -0,0 +1,5 @@
import ThreadChannel from './Thread.js'
export class PublicThreadChannel extends ThreadChannel {}
export default PublicThreadChannel

View File

@@ -0,0 +1,180 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { BigString, DiscordChannel, GetMessagesOptions } from '@discordeno/types'
import type Client from '../../../Client.js'
import Collection from '../../../Collection.js'
import type { FileContent, GetMessageReactionOptions, MessageContent, MessageContentEdit, PurgeChannelOptions } from '../../../typings.js'
import type Message from '../../Message.js'
import type User from '../../users/User.js'
import GuildChannel from '../Guild.js'
import ThreadMember from './Member.js'
export class ThreadChannel extends GuildChannel {
/** The cached messages that were sent in this channel. */
messages: Collection<BigString, Message>
/** The cached thread members that are in this channel. */
members: Collection<BigString, ThreadMember>
/** The id of the last message in this channel. */
lastMessageID: BigString | null
/** The id of the user who created this thread. */
ownerID: BigString
/** The approximate amount of members that have joined this thread. */
memberCount?: number
/** The approximate amount of messages in this channel. */
messageCount?: number
/** The rate limit that users can send messages in this channel. 0 means no rate limit has been enabled. */
rateLimitPerUser?: number
/** The data relevant to this thread. */
threadMetadata?: {
/** Timestamp when the thread's archive status was last changed, used for calculating recent activity */
archiveTimestamp: number
/** Whether the thread is archived. */
archived: boolean
/** Duration in minutes to automatically archive the thread after recent activity, either 60, 1440, 4320 or 10080 */
autoArchiveDuration: number
/** Whether the thread is locked. */
locked: boolean
/** Whether or not the thread is inviteable. */
invitable?: boolean
}
/** The bot's thread member object if it has joined the thread. */
member?: ThreadMember
constructor(data: DiscordChannel, client: Client, messageLimit?: number) {
super(data, client)
this.members = new Collection()
this.messages = new Collection()
this.messages.limit = messageLimit ?? client.options.messageLimit
this.lastMessageID = data.last_message_id ?? null
this.ownerID = data.owner_id!
this.update(data)
}
update(data: DiscordChannel): void {
super.update(data)
if (data.member_count !== undefined) {
this.memberCount = data.member_count
}
if (data.message_count !== undefined) {
this.messageCount = data.message_count
}
if (data.rate_limit_per_user !== undefined) {
this.rateLimitPerUser = data.rate_limit_per_user
}
if (data.thread_metadata !== undefined) {
this.threadMetadata = {
archiveTimestamp: Date.parse(data.thread_metadata.archive_timestamp),
archived: data.thread_metadata.archived,
autoArchiveDuration: data.thread_metadata.auto_archive_duration,
locked: data.thread_metadata.locked,
}
}
if (data.member !== undefined) {
this.member = new ThreadMember(data.member, this.client)
}
}
async addMessageReaction(messageID: BigString, reaction: string): Promise<void> {
return await this.client.addMessageReaction.call(this.client, this.id, messageID, reaction)
}
async createMessage(content: MessageContent, file?: FileContent | FileContent[]) {
return await this.client.createMessage.call(this.client, this.id, content, file)
}
async deleteMessage(messageID: BigString, reason?: string): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID, reason)
}
async deleteMessages(messageIDs: BigString[], reason?: string): Promise<void> {
return await this.client.deleteMessages.call(this.client, this.id, messageIDs, reason)
}
async editMessage(messageID: BigString, content: MessageContentEdit) {
return await this.client.editMessage.call(this.client, this.id, messageID, content)
}
async getMembers(): Promise<ThreadMember[]> {
return await this.client.getThreadMembers.call(this.client, this.id)
}
async getMessage(messageID: BigString): Promise<Message> {
return await this.client.getMessage.call(this.client, this.id, messageID)
}
async getMessageReaction(messageID: BigString, reaction: string, options?: GetMessageReactionOptions): Promise<User[]> {
return await this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, options)
}
async getMessages(options: GetMessagesOptions) {
return await this.client.getMessages.call(this.client, this.id, options)
}
async getPins(): Promise<Message[]> {
return await this.client.getPins.call(this.client, this.id)
}
async join(userID: BigString = '@me'): Promise<void> {
return await this.client.joinThread.call(this.client, this.id, userID)
}
async leave(userID: BigString): Promise<void> {
return await this.client.leaveThread.call(this.client, this.id, userID)
}
async pinMessage(messageID: BigString): Promise<void> {
return await this.client.pinMessage.call(this.client, this.id, messageID)
}
async purge(options: PurgeChannelOptions): Promise<number> {
return await this.client.purgeChannel.call(this.client, this.id, options)
}
async removeMessageReaction(messageID: BigString, reaction: string, userID: BigString = '@me') {
return await this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID)
}
async removeMessageReactionEmoji(messageID: BigString, reaction: string): Promise<void> {
return await this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction)
}
async removeMessageReactions(messageID: BigString): Promise<void> {
return await this.client.removeMessageReactions.call(this.client, this.id, messageID)
}
async sendTyping(): Promise<void> {
return await this.client.sendChannelTyping.call(this.client, this.id)
}
async unpinMessage(messageID: BigString): Promise<void> {
return await this.client.unpinMessage.call(this.client, this.id, messageID)
}
/**
* @deprecated Use deleteMessage instead
*/
async unsendMessage(messageID: BigString): Promise<void> {
return await this.client.deleteMessage.call(this.client, this.id, messageID)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'lastMessageID',
'memberCount',
'messageCount',
'messages',
'ownerID',
'rateLimitPerUser',
'threadMetadata',
'member',
...props,
])
}
}
export default ThreadChannel

View File

@@ -0,0 +1,181 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordAuditLogChange, DiscordAuditLogEntry } from '@discordeno/types'
import { AuditLogEvents } from '@discordeno/types'
import Base from '../../Base.js'
import type GuildChannel from '../channels/Guild.js'
import type TextChannel from '../channels/Text.js'
import Invite from '../Invite.js'
import type Message from '../Message.js'
import type User from '../users/User.js'
import type Guild from './Guild.js'
import type Member from './Member.js'
import type Role from './Role.js'
export class GuildAuditLogEntry extends Base {
/** The guild to which this entry belongs. */
guild: Guild
/** The action type of the entry. */
actionType: AuditLogEvents
/** The reason for the action. */
reason: string | null
/** The user that performed the action. */
user?: User
/** The properties of the targeted object before the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}`` */
before: Record<DiscordAuditLogChange['key'], DiscordAuditLogChange['old_value']>
/** The properties of the targeted object after the action was taken. For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}`` */
after: Record<DiscordAuditLogChange['key'], DiscordAuditLogChange['new_value']>
/** The ID of the action target */
targetID?: string
/** The number of entities targeted. For example, for action type 26 (MEMBER_MOVE), this is the number of members that were moved/disconnected from the voice channel */
count?: number
/** The channel targeted in the entry, action types 26 (MEMBER_MOVE), 72/74/75 (MESSAGE_DELETE/PIN/UNPIN) and 83/84/85 (STAGE_INSTANCE_CREATE/UPDATE/DELETE) only */
channel?: GuildChannel
/** The message that was (un)pinned, action types 74/75 (MESSAGE_PIN/UNPIN) only. If the message is not cached, this will be an object with an `id` key. No other property is guaranteed. */
message?: Message | { id: string }
/** The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only */
deleteMemberDays?: number
/** The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only */
membersRemoved?: number
/** The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String} */
member?: Member | { id: string }
/** The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String} */
role?: Role | { id: string; name: string }
constructor(data: DiscordAuditLogEntry, guild: Guild) {
super(data.id)
this.guild = guild
this.actionType = data.action_type
this.reason = data.reason ?? null
this.user = data.user_id ? guild.client.users.get(data.user_id) : undefined
this.before = {} as any
this.after = {} as any
if (data.changes) {
data.changes.forEach((change) => {
if (change.old_value !== undefined) {
this.before[change.key] = change.old_value
}
if (change.new_value !== undefined) {
this.after[change.key] = change.new_value
}
})
}
if (data.target_id) {
this.targetID = data.target_id
}
if (data.options) {
if (data.options.count) {
this.count = +data.options.count
}
if (data.options.channel_id) {
if (this.actionType >= 83) {
this.channel = guild.threads.get(data.options.channel_id)
} else {
this.channel = guild.channels.get(data.options.channel_id)
}
if (data.options.message_id) {
this.message = (this.channel && (this.channel as TextChannel).messages.get(data.options.message_id)) ?? {
id: data.options.message_id,
}
}
}
if (data.options.delete_member_days) {
this.deleteMemberDays = +data.options.delete_member_days
this.membersRemoved = +data.options.members_removed
}
if (data.options.type) {
if (data.options.type === '1') {
this.member = guild.members.get(data.options.id) ?? {
id: data.options.id,
}
} else if (data.options.type === '0') {
this.role = guild.roles.get(data.options.id) ?? {
id: data.options.id,
name: data.options.role_name,
}
}
}
}
}
get target() {
// pay more, get less
if (this.actionType < 10) {
// Guild
return this.guild
} else if (this.actionType < 20) {
// Channel
return this.guild?.channels.get(this.targetID!)
} else if (this.actionType < 30) {
// Member
if (this.actionType === AuditLogEvents.MemberMove || this.actionType === AuditLogEvents.MemberDisconnect) {
// MEMBER_MOVE / MEMBER_DISCONNECT
return null
}
return this.guild?.members.get(this.targetID!)
} else if (this.actionType < 40) {
// Role
return this.guild?.roles.get(this.targetID!)
} else if (this.actionType < 50) {
// Invite
const changes = this.actionType === 42 ? this.before : this.after // Apparently the meaning of life is a deleted invite
return new Invite(
{
code: changes.code as string,
// @ts-expect-error idk why this is happening
channel: changes.channel,
guild: this.guild.toJSON(),
uses: changes.uses as number,
max_uses: changes.max_uses as number,
max_age: changes.max_age as number,
temporary: changes.temporary as boolean,
},
this.guild?.client,
)
} else if (this.actionType < 60) {
// Webhook
return null // Go get the webhook yourself
} else if (this.actionType < 70) {
// Emoji
return this.guild?.emojis?.find((emoji) => emoji.id === this.targetID)
} else if (this.actionType < 80) {
// Message
return this.guild?.client.users.get(this.targetID!)
} else if (this.actionType < 83) {
// Integrations
return null
} else if (this.actionType < 90) {
// Stage Instances
return this.guild?.threads.get(this.targetID!)
} else if (this.actionType < 100) {
// Sticker
return this.guild?.stickers?.find((sticker) => sticker.id === this.targetID)
} else {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
throw new Error('Unrecognized action type: ' + this.actionType)
}
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'actionType',
'after',
'before',
'channel',
'count',
'deleteMemberDays',
'member',
'membersRemoved',
'reason',
'role',
'targetID',
'user',
...props,
])
}
}
export default GuildAuditLogEntry

View File

@@ -0,0 +1,946 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import {
BitwisePermissionFlags,
ChannelTypes,
type ApplicationCommandTypes,
type BigString,
type DefaultMessageNotificationLevels,
type DiscordEmoji,
type DiscordGuild,
type DiscordMemberWithUser,
type DiscordSticker,
type ExplicitContentFilterLevels,
type GuildFeatures,
type GuildNsfwLevel,
type MfaLevels,
type PremiumTiers,
type SystemChannelFlags,
type VerificationLevels,
} from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import Collection from '../../Collection.js'
import { BANNER, GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js'
import type {
AnyGuildChannel,
AnyThreadChannel,
ApplicationCommand,
ApplicationCommandPermissions,
ApplicationCommandStructure,
ChannelPosition,
CreateChannelOptions,
CreateStickerOptions,
DiscoveryMetadata,
DiscoveryOptions,
DiscoverySubcategoryResponse,
EditStickerOptions,
Emoji,
EmojiOptions,
GetGuildAuditLogOptions,
GetGuildBansOptions,
GetPruneOptions,
GetRESTGuildMembersOptions,
GuildApplicationCommandPermissions,
GuildAuditLog,
GuildBan,
GuildOptions,
GuildTemplateOptions,
GuildVanity,
IntegrationOptions,
ListedGuildThreads,
MemberOptions,
PruneMemberOptions,
RoleOptions,
Sticker,
VoiceRegion,
VoiceStateOptions,
Webhook,
WelcomeScreen,
WelcomeScreenOptions,
Widget,
WidgetData,
} from '../../typings.js'
import type CategoryChannel from '../channels/Category.js'
import Channel from '../channels/Channel.js'
import type GuildChannel from '../channels/Guild.js'
import type StageChannel from '../channels/Stage.js'
import type TextChannel from '../channels/Text.js'
import type TextVoiceChannel from '../channels/TextVoice.js'
import type ThreadChannel from '../channels/threads/Thread.js'
import type VoiceChannel from '../channels/Voice.js'
import type Invite from '../Invite.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import type GuildIntegration from './Integration.js'
import Member from './Member.js'
import Role from './Role.js'
import StageInstance from './StageInstance.js'
import type GuildTemplate from './Template.js'
import type { VoiceState } from './VoiceState.js'
export class Guild extends Base {
/** The client object */
client: Client
/** The id of the guild owner. */
ownerID: BigString
/** The id of the application. */
applicationID?: BigString | null
/** The id of the widget channel. */
widgetChannelID?: BigString | null
/** The afk channel id if one is set. */
afkChannelID?: BigString | null
/** The system channel id if one is set. */
systemChannelID?: BigString | null
/** The public updates channel id if one is set. */
publicUpdatesChannelID?: BigString | null
/** The rules channel id if one is set. */
rulesChannelID?: BigString | null
/** The name of the guild. */
name?: string
/** The description of the guild. */
description?: string | null
/** The vanity url if one is set. */
vanityURL?: string | null
/** The preferred locale of the server. */
preferredLocale?: string
/** The system channel flags. */
systemChannelFlags?: SystemChannelFlags
/** The verification level of the guild. */
verificationLevel?: VerificationLevels
/** The default notification level. */
defaultNotifications?: DefaultMessageNotificationLevels
/** The explicit content filter setting for this guild. */
explicitContentFilter?: ExplicitContentFilterLevels
/** Array of guild features */
features: GuildFeatures[] = []
/** The premium tier of the guild. */
premiumTier?: PremiumTiers
/** The MFA level of the guild. */
mfaLevel?: MfaLevels
/** The NSFW level of the guild. */
nsfwLevel?: GuildNsfwLevel
/** The compressed form of the guild splash image. */
_splash?: bigint
/** The compressed form of the guild's discovery splash image. */
_discoverySplash?: bigint
/** The compressed form of the guild's banner image. */
_banner?: bigint
/** The compressed form of the guild's icon image. */
_icon?: bigint
/** The cached emojis in the guild. */
emojis?: DiscordEmoji[]
/** The cached stickers in the guild. */
stickers?: DiscordSticker[]
/** The afk timeout in seconds. */
afkTimeout?: number
/** When this guild was joined at. */
joinedAt: number
/** The amount of members in the guild. */
memberCount?: number
/** The approximate member count in the guild. */
approximateMemberCount?: number
/** The approximate presence count in the guild. */
approximatePresenceCount?: number
/** The amount of subscribers to the server. */
premiumSubscriptionCount?: number
/** The maximum amount of presences that can be in a guild. */
maxPresences?: number | null
/** The maximum amount of members that can be in the guild. */
maxMembers?: number
/** The maximum amount of members that can be in a video channel. */
maxVideoChannelUsers?: number | null
/** Whether or not this guild is unavailable. */
unavailable: boolean
/** Whether or not the widget is enabled in this guild. */
widgetEnabled: boolean
/** Whether or not this guild is considered large. */
large?: boolean
/** Whether or not the premium progress bar is enabled. */
premiumProgressBarEnabled?: boolean
/** Whether or not this server is nsfw. */
nsfw?: boolean
/** The welcome screen settings. */
welcomeScreen?: {
description: string | null
welcomeChannels?: Array<{
channelID: string
description: string
emojiID: string | null
emojiName: string | null
}>
}
/** The cached members in this guild. */
members = new Collection<BigString, Member>()
/** The cached roles in this guild. */
roles = new Collection<BigString, Role>()
/** The cached channels in this guild. */
channels = new Collection<BigString, GuildChannel>()
/** The cached threads in this guild. */
threads = new Collection<BigString, ThreadChannel>()
/** The cached voice states in this guild. */
voiceStates = new Collection<BigString, VoiceState>()
/** The cached stage instances in this guild. */
stageInstances = new Collection<BigString, StageInstance>()
constructor(data: DiscordGuild, client: Client) {
super(data.id)
this.client = client
this.ownerID = data.owner_id
this.unavailable = !!data.unavailable
this.joinedAt = Date.parse(data.joined_at!)
this.memberCount = data.member_count
this.applicationID = data.application_id
this.widgetEnabled = !!data.widget_enabled
if (data.widget_channel_id !== undefined) {
this.widgetChannelID = data.widget_channel_id
}
if (data.approximate_member_count !== undefined) {
this.approximateMemberCount = data.approximate_member_count
}
if (data.approximate_presence_count !== undefined) {
this.approximatePresenceCount = data.approximate_presence_count
}
if (data.roles) {
for (const r of data.roles) {
const role = new Role(r, this)
this.roles.set(role.id, role)
}
}
if (data.channels) {
for (const channelData of data.channels) {
channelData.guild_id = this.id.toString()
const channel = Channel.from(channelData, client) as GuildChannel
this.channels.set(channel.id, channel)
client._channelGuildMap.set(channel.id, this.id)
}
}
if (data.threads) {
for (const threadData of data.threads) {
threadData.guild_id = this.id.toString()
const thread = Channel.from(threadData, client) as ThreadChannel
this.threads.set(thread.id, thread)
client._threadGuildMap.set(thread.id, this.id)
}
}
if (data.members) {
for (const m of data.members) {
const member = new Member(m as DiscordMemberWithUser, this, client)
this.members.set(member.id, member)
}
}
if (data.stage_instances) {
for (const stageInstance of data.stage_instances) {
stageInstance.guild_id = this.id
const instance = new StageInstance(stageInstance, client)
this.stageInstances.set(instance.id, instance)
}
}
if (data.presences) {
for (const presence of data.presences) {
if (presence.user?.id) {
const cached = this.client.users.get(presence.user.id)
if (cached) cached.update(presence.user)
else {
const user = new User(presence.user, this.client)
this.client.users.set(user.id, user)
}
}
}
}
if (data.voice_states) {
for (const voiceState of data.voice_states) {
if (!this.members.get(voiceState.user_id)) continue
if (voiceState.member) {
const member = new Member(voiceState.member, this, client)
this.members.set(member.id, member)
const user = new User(voiceState.member.user, client)
this.client.users.set(user.id, user)
// TODO: check channel type maybe voice channel?
;(this.channels.get(voiceState.channel_id!) as VoiceChannel)?.voiceMembers.set(member.id, member)
}
// TODO: voice support
// if (
// client.options.seedVoiceConnections &&
// voiceState.user_id === client.id &&
// !client.voiceConnections.get(this.id)
// ) {
// process.nextTick(() =>
// this.client.joinVoiceChannel(voiceState.channel_id)
// );
// }
}
}
this.update(data)
}
/**
* @deprecated - please use .client
*/
get _client() {
return this.client
}
update(data: DiscordGuild) {
if (data.name !== undefined) {
this.name = data.name
}
if (data.verification_level !== undefined) {
this.verificationLevel = data.verification_level
}
if (data.splash !== undefined) {
this._splash = data.splash ? this.client.iconHashToBigInt(data.splash) : undefined
}
if (data.discovery_splash !== undefined) {
this._discoverySplash = data.discovery_splash ? this.client.iconHashToBigInt(data.discovery_splash) : undefined
}
if (data.banner !== undefined) {
this._banner = data.banner ? this.client.iconHashToBigInt(data.banner) : undefined
}
if (data.owner_id !== undefined) {
this.ownerID = data.owner_id
}
if (data.icon !== undefined) {
this._icon = data.icon ? this.client.iconHashToBigInt(data.icon) : undefined
}
// TODO: compress features.
if (data.features !== undefined) {
this.features = data.features
}
if (data.emojis !== undefined) {
this.emojis = data.emojis
}
if (data.stickers !== undefined) {
this.stickers = data.stickers
}
if (data.afk_channel_id !== undefined) {
this.afkChannelID = data.afk_channel_id
}
if (data.afk_timeout !== undefined) {
this.afkTimeout = data.afk_timeout
}
if (data.default_message_notifications !== undefined) {
this.defaultNotifications = data.default_message_notifications
}
if (data.mfa_level !== undefined) {
this.mfaLevel = data.mfa_level
}
if (data.large !== undefined) {
this.large = data.large
}
if (data.max_presences !== undefined) {
this.maxPresences = data.max_presences
}
if (data.explicit_content_filter !== undefined) {
this.explicitContentFilter = data.explicit_content_filter
}
if (data.system_channel_id !== undefined) {
this.systemChannelID = data.system_channel_id
}
if (data.system_channel_flags !== undefined) {
this.systemChannelFlags = data.system_channel_flags
}
if (data.premium_progress_bar_enabled !== undefined) {
this.premiumProgressBarEnabled = data.premium_progress_bar_enabled
}
if (data.premium_tier !== undefined) {
this.premiumTier = data.premium_tier
}
if (data.premium_subscription_count !== undefined) {
this.premiumSubscriptionCount = data.premium_subscription_count
}
if (data.vanity_url_code !== undefined) {
this.vanityURL = data.vanity_url_code
}
if (data.preferred_locale !== undefined) {
this.preferredLocale = data.preferred_locale
}
if (data.description !== undefined) {
this.description = data.description
}
if (data.max_members !== undefined) {
this.maxMembers = data.max_members
}
if (data.public_updates_channel_id !== undefined) {
this.publicUpdatesChannelID = data.public_updates_channel_id
}
if (data.rules_channel_id !== undefined) {
this.rulesChannelID = data.rules_channel_id
}
if (data.max_video_channel_users !== undefined) {
this.maxVideoChannelUsers = data.max_video_channel_users
}
if (data.welcome_screen !== undefined) {
this.welcomeScreen = {
description: data.welcome_screen.description,
welcomeChannels: data.welcome_screen.welcome_channels?.map((c) => {
return {
channelID: c.channel_id,
description: c.description,
emojiID: c.emoji_id,
emojiName: c.emoji_name,
}
}),
}
}
// if (data.nsfw !== undefined) {
// this.nsfw = data.nsfw;
// }
if (data.nsfw_level !== undefined) {
this.nsfwLevel = data.nsfw_level
}
}
get banner(): string | undefined {
return this._banner ? this.client.iconBigintToHash(this._banner) : undefined
}
get bannerURL(): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner)) : null
}
get icon(): string | undefined {
return this._icon ? this.client.iconBigintToHash(this._icon) : undefined
}
get iconURL(): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null
}
get splash(): string | undefined {
return this._splash ? this.client.iconBigintToHash(this._splash) : undefined
}
get splashURL(): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null
}
get discoverySplash(): string | undefined {
return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined
}
get discoverySplashURL(): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null
}
/** Add a discovery subcategory */
async addDiscoverySubcategory(categoryID: BigString, reason?: string): Promise<DiscoverySubcategoryResponse> {
return await this.client.addGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason)
}
/** Add a role to a guild member */
async addMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise<void> {
return await this.client.addGuildMemberRole.call(this.client, this.id, memberID, roleID, reason)
}
/** Ban a user from the guild */
async banMember(userID: BigString, deleteMessageDays = 0, reason?: string): Promise<void> {
return await this.client.banGuildMember.call(this.client, this.id, userID, deleteMessageDays, reason)
}
/** Bulk create/edit guild application commands */
async bulkEditCommands(commands: Array<ApplicationCommand<ApplicationCommandTypes>>): Promise<Array<ApplicationCommand<ApplicationCommandTypes>>> {
return await this.client.bulkEditGuildCommands.call(this.client, this.id, commands)
}
/** Create a channel in the guild */
async createChannel(
name: string,
type = ChannelTypes.GuildText,
options: CreateChannelOptions,
): Promise<CategoryChannel | TextChannel | TextVoiceChannel | StageChannel> {
return await this.client.createChannel.call(this.client, this.id, name, type as number, options)
}
/** Create a guild application command */
async createCommand(command: ApplicationCommandStructure): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.createGuildCommand.call(this.client, this.id, command)
}
/** Create a emoji in the guild */
async createEmoji(options: EmojiOptions, reason?: string): Promise<Emoji> {
return await this.client.createGuildEmoji.call(this.client, this.id, options, reason)
}
/** Create a guild role */
async createRole(options: Role | RoleOptions, reason?: string): Promise<Role> {
return await this.client.createRole.call(this.client, this.id, options, reason)
}
/** Create a guild sticker */
async createSticker(options: CreateStickerOptions, reason?: string): Promise<Sticker> {
return await this.client.createGuildSticker.call(this.client, this.id, options, reason)
}
/** Create a template for this guild */
async createTemplate(name: string, description?: string): Promise<GuildTemplate> {
return await this.client.createGuildTemplate.call(this.client, this.id, name, description)
}
/** Delete the guild (bot user must be owner) */
async delete(): Promise<void> {
if (this.ownerID !== this.client.id) throw new Error('To delete a guild, the bot must be the owner of the guild.')
return await this.client.deleteGuild.call(this.client, this.id)
}
/** Delete a guild application command */
async deleteCommand(commandID: BigString): Promise<void> {
return await this.client.deleteGuildCommand.call(this.client, this.id, commandID)
}
/** Delete a discovery subcategory */
async deleteDiscoverySubcategory(categoryID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildDiscoverySubcategory.call(this.client, this.id, categoryID, reason)
}
/** Delete a emoji in the guild */
async deleteEmoji(emojiID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildEmoji.call(this.client, this.id, emojiID, reason)
}
/** Delete a guild integration */
async deleteIntegration(integrationID: BigString): Promise<void> {
return await this.client.deleteGuildIntegration.call(this.client, this.id, integrationID)
}
/** Delete a role */
async deleteRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.deleteRole.call(this.client, this.id, roleID, reason)
}
/** Delete a guild sticker */
async deleteSticker(stickerID: BigString, reason?: string): Promise<void> {
return await this.client.deleteGuildSticker.call(this.client, this.id, stickerID, reason)
}
/** Delete a guild template */
async deleteTemplate(code: string): Promise<void> {
return await this.client.deleteGuildTemplate.call(this.client, this.id, code)
}
/** Get the guild's banner with the given format and size */
dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null
}
/** Get the guild's discovery splash with the given format and size */
dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null
}
/** Get the guild's icon with the given format and size */
dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null
}
/** Get the guild's splash with the given format and size */
dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null
}
/** Edit the guild */
async edit(options: GuildOptions, reason?: string): Promise<Guild> {
return await this.client.editGuild.call(this.client, this.id, options, reason)
}
/** Edit multiple channels' positions. Note that channel position numbers are grouped by type (category, text, voice), then sorted in ascending order (lowest number is on top). */
async editChannelPositions(channelPositions: ChannelPosition[]): Promise<void> {
return await this.client.editChannelPositions.call(this.client, this.id, channelPositions)
}
/** Edit a guild application command */
async editCommand(commandID: BigString, commands: ApplicationCommandStructure): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.editGuildCommand.call(this.client, this.id, commandID, commands)
}
/**
* Edits command permissions for a specific command in a guild.
* Note: You can only add up to 10 permission overwrites for a command.
*/
async editCommandPermissions(commandID: BigString, permissions: ApplicationCommandPermissions[]): Promise<GuildApplicationCommandPermissions> {
return await this.client.editCommandPermissions.call(this.client, this.id, commandID, permissions)
}
/** Edit the guild's discovery data */
async editDiscovery(options: DiscoveryOptions): Promise<DiscoveryMetadata> {
return await this.client.editGuildDiscovery.call(this.client, this.id, options)
}
/**
* Edit a emoji in the guild
* @arg {String} emojiID The ID of the emoji you want to modify
* @arg {Object} options Emoji options
* @arg {String} [options.name] The name of emoji
* @arg {Array} [options.roles] An array containing authorized role IDs
* @arg {String} [reason] The reason to be displayed in audit logs
* @returns {Promise<Object>} A guild emoji object
*/
async editEmoji(
emojiID: BigString,
options: {
name?: string | undefined
roles?: string[] | undefined
},
reason?: string,
): Promise<Emoji> {
return await this.client.editGuildEmoji.call(this.client, this.id, emojiID, options, reason)
}
/** Edit a guild integration */
async editIntegration(integrationID: BigString, options: IntegrationOptions): Promise<void> {
return await this.client.editGuildIntegration.call(this.client, this.id, integrationID, options)
}
/** Edit a guild member */
async editMember(memberID: BigString, options: MemberOptions, reason?: string): Promise<Member> {
return await this.client.editGuildMember.call(this.client, this.id, memberID, options, reason)
}
/** Edit the guild role */
async editRole(roleID: BigString, options: RoleOptions, reason?: string): Promise<Role> {
return await this.client.editRole.call(this.client, this.id, roleID, options, reason)
}
/** Edit a guild sticker */
async editSticker(stickerID: BigString, options: EditStickerOptions, reason?: string): Promise<Sticker> {
return await this.client.editGuildSticker.call(this.client, this.id, stickerID, options, reason)
}
/** Edit a guild template */
async editTemplate(code: string, options: GuildTemplateOptions): Promise<GuildTemplate> {
return await this.client.editGuildTemplate.call(this.client, this.id, code, options)
}
/** Modify the guild's vanity code */
async editVanity(code: string | null): Promise<unknown> {
return await this.client.editGuildVanity.call(this.client, this.id, code)
}
/** Update a user's voice state - See [caveats](https://discord.com/developers/docs/resources/guild#modify-user-voice-state-caveats) */
async editVoiceState(options: VoiceStateOptions, userID: BigString = '@me'): Promise<void> {
return await this.client.editGuildVoiceState.call(this.client, this.id, options, userID)
}
/** Edit the guild welcome screen */
async editWelcomeScreen(options: WelcomeScreenOptions): Promise<WelcomeScreen> {
return await this.client.editGuildWelcomeScreen.call(this.client, this.id, options)
}
/** Modify a guild's widget */
async editWidget(options: Widget): Promise<Widget> {
return await this.client.editGuildWidget.call(this.client, this.id, options)
}
/** Request all guild members from Discord */
async fetchAllMembers(timeout?: number): Promise<number> {
return await this.fetchMembers({
guildId: this.id,
limit: 0,
}).then((m: any[]) => m.length)
}
/** Request specific guild members through the gateway connection */
async fetchMembers(options: RequestGuildMembers): Promise<Member[]> {
// TODO: Use gateway fetch
return await this.client.getRESTGuildMembers(this.id, options)
}
/** Get all active threads in this guild */
async getActiveThreads(): Promise<ListedGuildThreads<AnyThreadChannel>> {
return await this.client.getActiveGuildThreads.call(this.client, this.id)
}
/** Get the audit log for the guild */
async getAuditLog(options: GetGuildAuditLogOptions): Promise<GuildAuditLog> {
return await this.client.getGuildAuditLog.call(this.client, this.id, options)
}
/** Get a ban from the ban list of a guild */
async getBan(userID: BigString): Promise<GuildBan> {
return await this.client.getGuildBan.call(this.client, this.id, userID)
}
/** Get the ban list of the guild */
async getBans(options: GetGuildBansOptions): Promise<GuildBan[]> {
return await this.client.getGuildBans.call(this.client, this.id, options)
}
/** Get a guild application command */
async getCommand(commandID: BigString): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.getGuildCommand.call(this.client, this.id, commandID)
}
/** Get the a guild's application command permissions */
async getCommandPermissions(commandID: BigString): Promise<GuildApplicationCommandPermissions> {
return await this.client.getCommandPermissions.call(this.client, this.id, commandID)
}
/** Get the guild's application commands */
async getCommands(): Promise<ApplicationCommand<ApplicationCommandTypes>> {
return await this.client.getGuildCommands.call(this.client, this.id)
}
/** Get the guild's discovery object */
async getDiscovery(): Promise<DiscoveryMetadata> {
return await this.client.getGuildDiscovery.call(this.client, this.id)
}
/** Get the all of a guild's application command permissions */
async getGuildCommandPermissions(): Promise<GuildApplicationCommandPermissions[]> {
return await this.client.getGuildCommandPermissions.call(this.client, this.id)
}
/** Get a list of integrations for the guild */
async getIntegrations(): Promise<GuildIntegration[]> {
return await this.client.getGuildIntegrations.call(this.client, this.id)
}
/** Get all invites in the guild */
async getInvites(): Promise<Invite[]> {
return await this.client.getGuildInvites.call(this.client, this.id)
}
/** Get the prune count for the guild */
async getPruneCount(options: GetPruneOptions): Promise<number> {
return await this.client.getPruneCount.call(this.client, this.id, options)
}
/** Get a guild's channels via the REST API. REST mode is required to use this endpoint. */
async getRESTChannels(): Promise<AnyGuildChannel[]> {
return await this.client.getRESTGuildChannels.call(this.client, this.id)
}
/** Get a guild emoji via the REST API. REST mode is required to use this endpoint. */
async getRESTEmoji(emojiID: BigString): Promise<Emoji> {
return await this.client.getRESTGuildEmoji.call(this.client, this.id, emojiID)
}
/** Get a guild's emojis via the REST API. REST mode is required to use this endpoint. */
async getRESTEmojis(): Promise<Emoji[]> {
return await this.client.getRESTGuildEmojis.call(this.client, this.id)
}
/** Get a guild's members via the REST API. REST mode is required to use this endpoint. */
async getRESTMember(memberID: BigString): Promise<Member> {
return await this.client.getRESTGuildMember.call(this.client, this.id, memberID)
}
/** Get a guild's members via the REST API. REST mode is required to use this endpoint. */
async getRESTMembers(options?: GetRESTGuildMembersOptions): Promise<Member[]> {
return await this.client.getRESTGuildMembers.call(this.client, this.id, options)
}
/** Get a guild's roles via the REST API. REST mode is required to use this endpoint. */
async getRESTRoles(): Promise<Role[]> {
return await this.client.getRESTGuildRoles.call(this.client, this.id)
}
/** Get a guild sticker via the REST API. REST mode is required to use this endpoint. */
async getRESTSticker(stickerID: BigString): Promise<Sticker> {
return await this.client.getRESTGuildSticker.call(this.client, this.id, stickerID)
}
/** Get a guild's stickers via the REST API. REST mode is required to use this endpoint. */
async getRESTStickers(): Promise<Sticker[]> {
return await this.client.getRESTGuildStickers.call(this.client, this.id)
}
/** Get the guild's templates */
async getTemplates(): Promise<GuildTemplate[]> {
return await this.client.getGuildTemplates.call(this.client, this.id)
}
/** Returns the vanity url of the guild */
async getVanity(): Promise<GuildVanity> {
return await this.client.getGuildVanity.call(this.client, this.id)
}
/** Get possible voice regions for a guild */
async getVoiceRegions(): Promise<VoiceRegion[]> {
return await this.client.getVoiceRegions.call(this.client, this.id)
}
/** Get all the webhooks in the guild */
async getWebhooks(): Promise<Webhook[]> {
return await this.client.getGuildWebhooks.call(this.client, this.id)
}
/** Get the welcome screen of the Community guild, shown to new members */
async getWelcomeScreen(): Promise<WelcomeScreen> {
return await this.client.getGuildWelcomeScreen.call(this.client, this.id)
}
/** Get a guild's widget object */
async getWidget(): Promise<WidgetData> {
return await this.client.getGuildWidget.call(this.client, this.id)
}
/** Get a guild's widget settings object */
async getWidgetSettings(): Promise<Widget> {
return await this.client.getGuildWidgetSettings.call(this.client, this.id)
}
/** Kick a member from the guild */
async kickMember(userID: BigString, reason?: string): Promise<void> {
return await this.client.kickGuildMember.call(this.client, this.id, userID, reason)
}
/** Leave the guild */
async leave(): Promise<void> {
return await this.client.leaveGuild.call(this.client, this.id)
}
// TODO: gateway voice
// /** Leaves the voice channel in this guild */
// async leaveVoiceChannel(): Promise<void> {
// return await this.client.closeVoiceConnection.call(this.client, this.id);
// }
/** Get the guild permissions of a member */
permissionsOf(memberID: BigString | Member): Permission {
const member = ['string', 'bigint'].includes(typeof memberID) ? this.members.get(memberID as BigString)! : (memberID as Member)
if (member.id === this.ownerID) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
} else {
let permissions = this.roles.get(this.id)!.permissions.allow
if (permissions & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
return new Permission(BitwisePermissionFlags.ADMINISTRATOR)
}
for (const id of member.roles) {
const role = this.roles.get(id)
if (!role) {
continue
}
const { allow: perm } = role.permissions
if (perm & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) {
permissions = BigInt(BitwisePermissionFlags.ADMINISTRATOR)
break
} else {
permissions |= perm
}
}
return new Permission(permissions)
}
}
/** Begin pruning the guild */
async pruneMembers(options?: PruneMemberOptions): Promise<number> {
return await this.client.pruneMembers.call(this.client, this.id, options)
}
/** Remove a role from a guild member */
async removeMemberRole(memberID: BigString, roleID: BigString, reason?: string): Promise<void> {
return await this.client.removeGuildMemberRole.call(this.client, this.id, memberID, roleID, reason)
}
/** Search for guild members by partial nickname/username */
async searchMembers(query: string, limit = 1): Promise<Member[]> {
return await this.client.searchGuildMembers.call(this.client, this.id, query, limit)
}
/** Force a guild integration to sync */
async syncIntegration(integrationID: BigString): Promise<void> {
return await this.client.syncGuildIntegration.call(this.client, this.id, integrationID)
}
/** Force a guild template to sync */
async syncTemplate(code: string): Promise<GuildTemplate> {
return await this.client.syncGuildTemplate.call(this.client, this.id, code)
}
/** Unban a user from the guild */
async unbanMember(userID: BigString, reason?: string): Promise<void> {
return await this.client.unbanGuildMember.call(this.client, this.id, userID, reason)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'afkChannelID',
'afkTimeout',
'applicationID',
'approximateMemberCount',
'approximatePresenceCount',
'autoRemoved',
'banner',
'categories',
'channels',
'defaultNotifications',
'description',
'discoverySplash',
'emojiCount',
'emojis',
'explicitContentFilter',
'features',
'icon',
'joinedAt',
'keywords',
'large',
'maxMembers',
'maxPresences',
'maxVideoChannelUsers',
'memberCount',
'members',
'mfaLevel',
'name',
'ownerID',
'pendingVoiceStates',
'preferredLocale',
'premiumProgressBarEnabled',
'premiumSubscriptionCount',
'premiumTier',
'primaryCategory',
'primaryCategoryID',
'publicUpdatesChannelID',
'roles',
'rulesChannelID',
'splash',
'stickers',
'systemChannelFlags',
'systemChannelID',
'unavailable',
'vanityURL',
'verificationLevel',
'voiceStates',
'welcomeScreen',
'widgetChannelID',
'widgetEnabled',
...props,
])
}
}
export default Guild

View File

@@ -0,0 +1,131 @@
/* eslint-disable no-useless-call */
import type { DiscordIntegration, DiscordIntegrationApplication } from '@discordeno/types'
import Base from '../../Base.js'
import type { IntegrationOptions } from '../../typings.js'
import User from '../users/User.js'
import type Guild from './Guild.js'
export class GuildIntegration extends Base {
/** The guild where this integration exists. */
guild: Guild
/** The name of the integration. */
name: string
/** The type of integration. */
type: string
/** The user connected to the integration. */
user?: User
/** Whether the integration is syncing or not. */
syncing?: boolean
/** THe Unix timestamp of last integration sync. */
syncedAt?: number
/** The number of subscribers. */
subscriberCount?: number
/** The role id of the role connected to the integration. */
roleID?: string
/** WHether or not the application was revoked. */
revoked?: boolean
/** Whether integration emoticons are enabled or not. */
enableEmoticons?: boolean
/** Behavior of expired subscriptions */
expireBehavior?: number
/** Grace period for expired subscriptions. */
expireGracePeriod?: number
/** Whether the integration is enabled or not. */
enabled?: boolean
/** The bot/oauth2 application for integration. */
application?: DiscordIntegrationApplication
/** Info on the integration account */
account: {
/** The id of the integration account. */
id: string
/** The name of the integration account. */
name: string
}
constructor(data: DiscordIntegration, guild: Guild) {
super(data.id)
this.guild = guild
this.name = data.name
this.type = data.type
if (data.role_id !== undefined) {
this.roleID = data.role_id
}
if (data.user) {
this.user = new User(data.user, guild.client)
guild.client.users.set(this.user.id, this.user)
}
this.account = data.account // not worth making a class for
this.update(data)
}
update(data: DiscordIntegration): void {
this.enabled = data.enabled
if (data.syncing !== undefined) {
this.syncing = data.syncing
}
if (data.expire_behavior !== undefined) {
this.expireBehavior = data.expire_behavior
}
if (data.expire_behavior !== undefined) {
this.expireGracePeriod = data.expire_grace_period
}
if (data.enable_emoticons) {
this.enableEmoticons = data.enable_emoticons
}
if (data.subscriber_count !== undefined) {
this.subscriberCount = data.subscriber_count
}
if (data.synced_at !== undefined) {
this.syncedAt = Date.parse(data.synced_at)
}
if (data.revoked !== undefined) {
this.revoked = data.revoked
}
if (data.application !== undefined) {
this.application = data.application
}
}
/** Delete the guild integration */
async delete(): Promise<void> {
return await this.guild.client.deleteGuildIntegration.call(this.guild.client, this.guild.id, this.id)
}
/** Edit the guild integration */
async edit(options: IntegrationOptions): Promise<void> {
return await this.guild.client.editGuildIntegration.call(this.guild.client, this.guild.id, this.id, options)
}
/** Force the guild integration to sync */
async sync(): Promise<void> {
return await this.guild.client.syncGuildIntegration.call(this.guild.client, this.guild.id, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'account',
'application',
'enabled',
'enableEmoticons',
'expireBehavior',
'expireGracePeriod',
'name',
'revoked',
'roleID',
'subscriberCount',
'syncedAt',
'syncing',
'type',
'user',
...props,
])
}
}
export default GuildIntegration

View File

@@ -0,0 +1,220 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordMember, DiscordMemberWithUser } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { GUILD_AVATAR } from '../../Endpoints.js'
import type { MemberOptions } from '../../typings.js'
import User from '../users/User.js'
import type Guild from './Guild.js'
export class Member extends Base {
/** The client manager */
client: Client
/** An array of role IDs this member is a part of */
roles: BigString[]
/** The guild the member is in */
guild: Guild
/** The user object of the member */
user: User
/** The server nickname of the member. */
nick: string | null
/** The timestamp when this member joined the server. */
joinedAt?: number
/** Timestamp of when the member boosted the guild */
premiumSince?: number
/** Whether the user has not yet passed the guild's Membership Screening requirements */
pending?: boolean
/** Timestamp of timeout expiry. If `null`, the member is not timed out */
communicationDisabledUntil?: number | null
/** The compressed form of the members avatar. */
_avatar?: bigint
constructor(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser, guild: Guild, client: Client) {
super(client.isDiscordMemberWithUser(data) ? data.user.id : data.id)
this.client = client
this.guild = guild
this.nick = null
this.roles = data.roles ?? []
const userID = client.isDiscordMemberWithUser(data) ? data.user.id : data.id
this.user = client.users.get(userID)!
if (data.user) {
this.user = new User(data.user, client)
client.users.set(this.user.id, this.user)
}
if (!this.user) {
throw new Error('User associated with Member not found: ' + userID)
}
this.update(data)
}
update(data: (DiscordMember & { id: BigString }) | DiscordMemberWithUser) {
if (data.joined_at !== undefined) {
this.joinedAt = data.joined_at ? Date.parse(data.joined_at) : undefined
}
if (data.premium_since !== undefined) {
this.premiumSince = data.premium_since === null ? undefined : Date.parse(data.premium_since)
}
// eslint-disable-next-line no-prototype-builtins
if (data.hasOwnProperty('mute') && this.guild) {
// TODO: voice stuff
// const state = this.guild.voiceStates.get(this.id);
// if (
// data.channel_id === null &&
// !data.mute &&
// !data.deaf &&
// !data.suppress
// ) {
// this.guild.voiceStates.delete(this.id);
// } else if (state) {
// state.update(data);
// } else if (data.channel_id || data.mute || data.deaf || data.suppress) {
// this.guild.voiceStates.update(data);
// }
}
if (data.nick !== undefined) this.nick = data.nick
if (data.roles !== undefined) this.roles = data.roles
if (data.pending !== undefined) this.pending = data.pending
if (data.avatar !== undefined) this._avatar = this.client.iconHashToBigInt(data.avatar)
if (data.communication_disabled_until !== undefined) {
if (data.communication_disabled_until !== null) {
this.communicationDisabledUntil = Date.parse(data.communication_disabled_until)
} else {
this.communicationDisabledUntil = data.communication_disabled_until
}
}
}
get avatar(): string | undefined {
return this._avatar ? this.client.iconBigintToHash(this._avatar) : undefined
}
get accentColor() {
return this.user.accentColor
}
get avatarURL() {
return this.avatar ? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar)) : this.user.avatarURL
}
get banner() {
return this.user.banner
}
get bannerURL() {
return this.user.bannerURL
}
get bot() {
return this.user.bot
}
get createdAt() {
return this.user.createdAt
}
get defaultAvatar() {
return this.user.defaultAvatar
}
get defaultAvatarURL() {
return this.user.defaultAvatarURL
}
get discriminator() {
return this.user.discriminator
}
get mention() {
return `<@!${this.id}>`
}
get permissions() {
return this.guild.permissionsOf(this)
}
get staticAvatarURL() {
return this.user.staticAvatarURL
}
get username() {
return this.user.username
}
get voiceState() {
if (this.guild?.voiceStates.has(this.id)) {
return this.guild.voiceStates.get(this.id)
} else {
// @ts-expect-error some eris magic at play here
return new VoiceState({ id: this.id })
}
}
/** Add a role to the guild member */
async addRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.addGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason)
}
/** Ban the user from the guild */
async ban(deleteMessageDays = 0, reason?: string): Promise<void> {
return await this.client.banGuildMember.call(this.client, this.guild.id, this.id, deleteMessageDays, reason)
}
/** Edit the guild member */
async edit(options: MemberOptions, reason?: string): Promise<Member> {
return await this.client.editGuildMember.call(this.client, this.guild.id, this.id, options, reason)
}
/** Get the member's avatar with the given format and size */
dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string {
return this.avatar
? this.client._formatImage(GUILD_AVATAR(this.guild.id, this.id, this.avatar), format, size)
: this.user.dynamicAvatarURL(format, size)
}
/** Kick the member from the guild */
async kick(reason?: string): Promise<void> {
return await this.client.kickGuildMember.call(this.client, this.guild.id, this.id, reason)
}
/** Remove a role from the guild member */
async removeRole(roleID: BigString, reason?: string): Promise<void> {
return await this.client.removeGuildMemberRole.call(this.client, this.guild.id, this.id, roleID, reason)
}
/** Unban the user from the guild */
async unban(reason?: string): Promise<void> {
return await this.client.unbanGuildMember.call(this.client, this.guild.id, this.id, reason)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'activities',
'communicationDisabledUntil',
'joinedAt',
'nick',
'pending',
'premiumSince',
'roles',
'status',
'user',
'voiceState',
...props,
])
}
}
export default Member

View File

@@ -0,0 +1,113 @@
import type { DiscordEmoji, DiscordGuildPreview } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { GUILD_DISCOVERY_SPLASH, GUILD_ICON, GUILD_SPLASH } from '../../Endpoints.js'
export class GuildPreview extends Base {
/** The client object */
client: Client
/** The name of the guild. */
name: string
/** The description of the guild. */
description: string | null
/** An array of guild emojis. */
emojis: DiscordEmoji[]
/** The approximate number of members in the guild. */
approximateMemberCount: number
/** The approximate number of presences in the guild. */
approximatePresenceCount: number
/** The guild's icon image url. */
_icon: bigint | null
/** The guild's splash image url. */
_splash: bigint | null
/** The guild's discovery splash image url. */
_discoverySplash: bigint | null
/** The guild's features. */
_features: GuildToggles
constructor(data: DiscordGuildPreview, client: Client) {
super(data.id)
this.client = client
this.name = data.name
this.description = data.description
this._icon = data.icon ? client.iconHashToBigInt(data.icon) : null
this._splash = data.splash ? client.iconHashToBigInt(data.splash) : null
this._discoverySplash = data.discovery_splash ? client.iconHashToBigInt(data.discovery_splash) : null
this.approximateMemberCount = data.approximate_member_count
this.approximatePresenceCount = data.approximate_presence_count
this.emojis = data.emojis
// TODO: make dd version accept a specific subset of discord guild here
// @ts-expect-error this should not cause an issue
this._features = new GuildToggles(data)
}
/**
* @deprecated Use .client
*/
get _client(): Client {
return this.client
}
get icon(): string | undefined {
return this._icon ? this.client.iconBigintToHash(this._icon) : undefined
}
get iconURL(): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon)) : null
}
get splash(): string | undefined {
return this._splash ? this.client.iconBigintToHash(this._splash) : undefined
}
get splashURL(): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash)) : null
}
get discoverySplash(): string | undefined {
return this._discoverySplash ? this.client.iconBigintToHash(this._discoverySplash) : undefined
}
get discoverySplashURL(): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash)) : null
}
get features(): string[] {
return this._features.features.map((feature) => feature.replace(/([a-z])([A-Z])/, '$1_$2').toUpperCase())
}
/** Get the guild's splash with the given format and size */
dynamicDiscoverySplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.discoverySplash ? this.client._formatImage(GUILD_DISCOVERY_SPLASH(this.id, this.discoverySplash), format, size) : null
}
/** Get the guild's icon with the given format and size */
dynamicIconURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.icon ? this.client._formatImage(GUILD_ICON(this.id, this.icon), format, size) : null
}
/** Get the guild's splash with the given format and size */
dynamicSplashURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.splash ? this.client._formatImage(GUILD_SPLASH(this.id, this.splash), format, size) : null
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'approximateMemberCount',
'approximatePresenceCount',
'description',
'discoverySplash',
'emojis',
'features',
'icon',
'name',
'splash',
...props,
])
}
}
export default GuildPreview

View File

@@ -0,0 +1,116 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { DiscordRole, DiscordRoleTags } from '@discordeno/types'
import Base from '../../Base.js'
import { ROLE_ICON } from '../../Endpoints.js'
import type { RoleOptions } from '../../typings.js'
import Permission from '../Permission.js'
import type Guild from './Guild.js'
export class Role extends Base {
permissions: Permission
name: string
color: number
hoist: boolean
mentionable: boolean
managed: boolean
icon?: string
unicodeEmoji?: string
position: number
guild: Guild
tags?: Omit<DiscordRoleTags, 'premium_subscriber'> & { premium_subscriber?: boolean }
constructor(data: DiscordRole, guild: Guild) {
super(data.id)
this.guild = guild
this.name = data.name
this.permissions = new Permission(data.permissions)
this.color = data.color
this.hoist = !!data.hoist
this.mentionable = !!data.mentionable
this.managed = !!data.managed
this.icon = data.icon
this.unicodeEmoji = data.unicode_emoji
this.position = data.position
this.tags = data.tags
? {
bot_id: data.tags.bot_id,
integration_id: data.tags.integration_id,
premium_subscriber: data.tags.premium_subscriber === null,
}
: undefined
}
update(data: DiscordRole) {
if (data.name !== undefined) {
this.name = data.name
}
if (data.mentionable !== undefined) {
this.mentionable = data.mentionable
}
if (data.managed !== undefined) {
this.managed = data.managed
}
if (data.hoist !== undefined) {
this.hoist = data.hoist
}
if (data.color !== undefined) {
this.color = data.color
}
if (data.position !== undefined) {
this.position = data.position
}
if (data.permissions !== undefined) {
this.permissions = new Permission(data.permissions)
}
if (data.tags !== undefined) {
this.tags = {
bot_id: data.tags.bot_id,
integration_id: data.tags.integration_id,
premium_subscriber: data.tags.premium_subscriber === null,
}
}
if (data.icon !== undefined) {
this.icon = data.icon
}
if (data.unicode_emoji !== undefined) {
this.unicodeEmoji = data.unicode_emoji
}
}
get iconURL() {
return this.icon ? this.guild.client._formatImage(ROLE_ICON(this.id, this.icon)) : null
}
get json() {
return this.permissions.json
}
get mention() {
return `<@&${this.id}>`
}
/** Delete the role */
async delete(reason: string): Promise<void> {
return await this.guild.client.deleteRole.call(this.guild.client, this.guild.id, this.id, reason)
}
/** Edit the guild role */
async edit(options: RoleOptions, reason?: string): Promise<Role> {
return await this.guild.client.editRole.call(this.guild.client, this.guild.id, this.id, options, reason)
}
/** Edit the role's position. Note that role position numbers are highest on top and lowest at the bottom. */
async editPosition(position: number): Promise<void> {
return await this.guild.client.editRolePosition.call(this.guild.client, this.guild.id, this.id, position)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['color', 'hoist', 'icon', 'managed', 'mentionable', 'name', 'permissions', 'position', 'tags', 'unicodeEmoji', ...props])
}
}
export default Role

View File

@@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { DiscordStageInstance } from "@discordeno/types";
import Base from "../../Base.js";
import type Client from "../../Client.js";
import type { StageInstanceOptions } from "../../typings.js";
import type StageChannel from "../channels/Stage.js";
import type Guild from "./Guild.js";
export class StageInstance extends Base {
/** The client manager. */
client: Client;
/** The associated stage channel */
channel: StageChannel | { id: string };
/** The guild of the associated stage channel */
guild: Guild | { id: string };
/** The stage channel topic */
topic?: string | null;
constructor(data: DiscordStageInstance, client: Client) {
super(data.id);
this.client = client;
this.channel = client.getChannel(data.channel_id) || {
id: data.channel_id,
};
this.guild = client.guilds.get(data.guild_id) ?? { id: data.guild_id };
this.update(data);
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client;
}
update(data: DiscordStageInstance) {
if (data.topic !== undefined) {
this.topic = data.topic;
}
}
/** Delete this stage instance */
async delete(): Promise<void> {
return await this.client.deleteStageInstance.call(
this.client,
this.channel.id
);
}
/** Update this stage instance */
async edit(options: StageInstanceOptions): Promise<StageInstance> {
return await this.client.editStageInstance.call(
this.client,
this.channel.id,
options
);
}
}
export default StageInstance;

View File

@@ -0,0 +1,95 @@
/* eslint-disable no-useless-call */
import type { DiscordTemplate } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { GuildTemplateOptions } from '../../typings.js'
import User from '../users/User.js'
import Guild from './Guild.js'
export class GuildTemplate {
/** The client class itself. */
client: Client
/** The template code (unique Id) */
code: string
/** Template name */
name: string
/** The description for the template */
description: string | null
/** Number of times this template has been used */
usageCount: number
/** The user who created the template */
creator: User
/** When this template was created */
createdAt: number
/** When this template was last synced to the source guild */
updatedAt: number
/** The guild snapshot this template contains */
serializedSourceGuild: Guild
/** The guild this template is based on. If the guild is not cached, this will be an object with `id` key. No other property is guaranteed */
sourceGuild: Guild | { id: string }
/** Whether the template has un-synced changes */
isDirty: boolean | null
constructor(data: DiscordTemplate, client: Client) {
this.client = client
this.code = data.code
this.createdAt = Date.parse(data.created_at)
this.creator = new User(data.creator, client)
this.client.users.set(this.creator.id, this.creator)
this.description = data.description
this.isDirty = data.is_dirty
this.name = data.name
this.sourceGuild = client.guilds.get(data.source_guild_id) ?? { id: data.source_guild_id }
this.updatedAt = Date.parse(data.updated_at)
this.usageCount = data.usage_count
data.serialized_source_guild.features = []
// @ts-expect-error Hacks to get this to not error
this.serializedSourceGuild = new Guild(data.serialized_source_guild, client)
}
/**
* @deprecated Use .client instead.
*/
get _client(): Client {
return this.client
}
/** Create a guild based on this template. Only for bots in less than 10 guilds */
async createGuild(name: string, icon?: string): Promise<Guild> {
return await this.client.createGuildFromTemplate.call(this.client, this.code, name, icon)
}
/** Delete this template */
async delete(): Promise<void> {
return await this.client.deleteGuildTemplate.call(this.client, this.sourceGuild.id, this.code)
}
/** Edit this template */
async edit(options: GuildTemplateOptions): Promise<GuildTemplate> {
return await this.client.editGuildTemplate.call(this.client, this.sourceGuild.id, this.code, options)
}
/** Force this template to sync to the guild's current state */
async sync(): Promise<GuildTemplate> {
return await this.client.syncGuildTemplate.call(this.client, this.sourceGuild.id, this.code)
}
toJSON(props: string[] = []): Record<string, any> {
return Base.prototype.toJSON.call(this, [
'code',
'createdAt',
'creator',
'description',
'isDirty',
'name',
'serializedSourceGuild',
'sourceGuild',
'updatedAt',
'usageCount',
...props,
])
}
}
export default GuildTemplate

View File

@@ -0,0 +1,23 @@
import type { DiscordUnavailableGuild } from "@discordeno/types"
import Base from "../../Base.js"
import type Client from "../../Client.js"
export class UnavailableGuild extends Base {
/** Whether or not the guild is unavailable. */
unavailable: boolean
constructor(data: DiscordUnavailableGuild, client: Client) {
super(data.id)
// TODO: gateway
// this.shard = client.shards.get(client.guildShardMap[this.id]);
this.unavailable = !!data.unavailable
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['unavailable', ...props])
}
}
export default UnavailableGuild

View File

@@ -0,0 +1,132 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { DiscordVoiceState } from "@discordeno/types"
import Base from "../../Base.js"
export class VoiceState extends Base {
channelID: string | null = null
requestToSpeakTimestamp: number | null
sessionID!: string | null
bitfield: VoiceStateToggles
constructor(data: DiscordVoiceState & { id: string }) {
super(data.id)
this.requestToSpeakTimestamp = null
this.bitfield = new VoiceStateToggles(data)
this.update(data)
}
/** Whether or not the user is deafened by the server. */
get deaf(): boolean {
return this.bitfield.deaf
}
/** Set whether or not the user is deafened by the server. */
set deaf(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.deaf)
else this.bitfield.remove(VoiceStateToggle.deaf)
}
/** Whether or not the user is muted by the server. */
get mute(): boolean {
return this.bitfield.mute
}
/** Set whether or not the user is muted by the server. */
set mute(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.mute)
else this.bitfield.remove(VoiceStateToggle.mute)
}
/** Whether or not the user has deafened themself. */
get selfDeaf(): boolean {
return this.bitfield.selfDeaf
}
/** Set whether or not the user has deafened themself. */
set selfDeaf(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfDeaf)
else this.bitfield.remove(VoiceStateToggle.selfDeaf)
}
/** Whether or not the user has muted themself. */
get selfMute(): boolean {
return this.bitfield.selfMute
}
/** Set whether or not the user has muted themself. */
set selfMute(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfMute)
else this.bitfield.remove(VoiceStateToggle.selfMute)
}
/** Whether or not the user is streaming. */
get selfStream(): boolean {
return this.bitfield.selfStream
}
/** Set whether or not the user is streaming. */
set selfStream(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfStream)
else this.bitfield.remove(VoiceStateToggle.selfStream)
}
/** Whether or not the user is video calling. */
get selfVideo(): boolean {
return this.bitfield.selfVideo
}
/** Set whether or not the user is video calling. */
set selfVideo(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.selfVideo)
else this.bitfield.remove(VoiceStateToggle.selfVideo)
}
/** Whether or not the user is suppressed from speaking. */
get suppress(): boolean {
return this.bitfield.suppress
}
/** Set whether or not the user is suppressed from speaking. */
set suppress(value: boolean) {
if (value) this.bitfield.add(VoiceStateToggle.suppress)
else this.bitfield.remove(VoiceStateToggle.suppress)
}
update(data: DiscordVoiceState) {
if (data.channel_id !== undefined) {
this.channelID = data.channel_id
this.sessionID = data.channel_id === null ? null : data.session_id
} else if (this.channelID === undefined) {
this.channelID = this.sessionID = null
}
if (data.mute !== undefined) this.mute = data.mute
if (data.deaf !== undefined) this.deaf = data.deaf
if (data.request_to_speak_timestamp) this.requestToSpeakTimestamp = Date.parse(data.request_to_speak_timestamp)
if (data.self_mute !== undefined) this.selfMute = data.self_mute
if (data.self_deaf !== undefined) this.selfDeaf = data.self_deaf
if (data.self_video !== undefined) this.selfVideo = data.self_video
if (data.self_stream !== undefined) this.selfStream = data.self_stream
if (data.suppress !== undefined) this.suppress = data.suppress
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON([
'channelID',
'deaf',
'mute',
'requestToSpeakTimestamp',
'selfDeaf',
'selfMute',
'selfStream',
'selfVideo',
'sessionID',
'suppress',
...props,
])
}
}

View File

@@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/return-await */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type { BigString, DiscordInteraction, DiscordInteractionData } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type { ApplicationCommandOptionChoice } from '../../typings.js'
import type NewsChannel from '../channels/News.js'
import type PrivateChannel from '../channels/Private.js'
import type TextChannel from '../channels/Text.js'
import type Guild from '../guilds/Guild.js'
import Member from '../guilds/Member.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class AutocompleteInteraction extends Interaction {
/** The guild id if this interaction occurred in a guild. */
guildID?: BigString
/** The permissions the app or bot has within the channel, the interaction was sent from. */
appPermissions?: Permission
/** The channel id where this interaction was created in. */
channelID: BigString
/** The user who triggered the interaction. */
user: User
/** The data attached to this interaction. */
data?: DiscordInteractionData
/** The member who triggered the interaction. Sent when used in a guild. */
member?: Member
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
this.channelID = data.channel_id!
this.data = data.data
if (data.guild_id !== undefined) {
this.guildID = data.guild_id
}
if (data.member !== undefined && this.guild) {
this.member = new Member(data.member, this.guild, this.client)
this.guild.members.set(this.member.id, this.member)
}
this.user = new User(data.user ?? data.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/** The channel the interaction was created in. */
get channel(): PrivateChannel | TextChannel | NewsChannel {
return this.client.getChannel(this.channelID) as PrivateChannel | TextChannel | NewsChannel
}
/** The guild the interaction was created in. */
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
async acknowledge(choices: ApplicationCommandOptionChoice[]) {
return await this.result(choices)
}
async result(choices: ApplicationCommandOptionChoice[]) {
if (this.acknowledged) throw new Error('You have already acknowledged this interaction.')
return this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: { choices },
})
.then(() => this.update())
}
}
export default AutocompleteInteraction

View File

@@ -0,0 +1,318 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import {
ApplicationCommandTypes,
InteractionResponseTypes,
type BigString,
type DiscordAttachment,
type DiscordInteraction,
type DiscordInteractionDataOption,
type DiscordMessageComponents,
type MessageComponentTypes,
} from '@discordeno/types'
import type Client from '../../Client.js'
import Collection from '../../Collection.js'
import type { AnyChannel, FileContent, InteractionContent, InteractionContentEdit } from '../../typings.js'
import Channel from '../channels/Channel.js'
import Member from '../guilds/Member.js'
import Role from '../guilds/Role.js'
import Message from '../Message.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class CommandInteraction extends Interaction {
channel: AnyChannel
/** The type of component */
componentType?: MessageComponentTypes
/** The custom id provided for this component. */
customId?: string
/** The components if its a Modal Submit interaction. */
components?: DiscordMessageComponents
/** The values chosen by the user. */
values?: string[]
/** the type of the invoked command */
commandType: ApplicationCommandTypes = ApplicationCommandTypes.ChatInput
/** The Ids and Message objects */
messages = new Collection<BigString, Message>()
/** The Ids and User objects */
users = new Collection<BigString, User>()
/** The Ids and partial Member objects */
members = new Collection<BigString, Member>()
/** The Ids and Role objects */
roles = new Collection<BigString, Role>()
/** The Ids and partial Channel objects */
channels = new Collection<BigString, Channel>()
/** The ids and attachment objects */
attachments = new Collection<BigString, DiscordAttachment>()
/** The params + values from the user */
options?: DiscordInteractionDataOption[]
/** The target id if this is a context menu command. */
targetID?: string
/** the id of the guild the command is registered to */
guildID?: string
member?: Member
user: User
constructor(info: DiscordInteraction, client: Client) {
super(info, client)
this.channel = this.client.getChannel(info.channel_id!) || {
id: info.channel_id,
}
// this.data = info.data!;
const guild = this.client.guilds.get(info.guild_id!)
if (info.data?.resolved !== undefined) {
// Users
if (info.data.resolved.users !== undefined) {
for (const u of Object.values(info.data.resolved.users)) {
const user = new User(u, this.client)
this.users.set(user.id, user)
}
}
// Members
if (info.data.resolved.members !== undefined) {
for (const [, m] of Object.entries(info.data.resolved.members)) {
// @ts-expect-error some eris magic at play here
m.id = m
// @ts-expect-error some eris magic at play here
const member = new Member(m, guild, this.client)
this.members.set(member.id, member)
}
}
// Roles
if (info.data.resolved.roles !== undefined) {
for (const r of Object.values(info.data.resolved.roles)) {
// @ts-expect-error some eris magic at play here
const role = new Role(r, guild)
this.roles.set(role.id, role)
}
}
// Channels
if (info.data.resolved.channels !== undefined) {
for (const c of Object.values(info.data.resolved.channels)) {
const channel = new Channel(c, this.client)
this.channels.set(channel.id, channel)
}
}
// Messages
if (info.data.resolved.messages !== undefined) {
for (const m of Object.values(info.data.resolved.messages)) {
const message = new Message(m, this.client)
this.messages.set(message.id, message)
}
}
// Attachments
if (info.data.resolved.attachments !== undefined) {
for (const attachment of Object.values(info.data.resolved.attachments)) {
this.attachments.set(attachment.id, attachment)
}
}
}
this.guildID = info.guild_id
if (info.member !== undefined) {
// @ts-expect-error some eris magic at play here
this.member = new Member(info.member, guild, this.client)
guild?.members.set(this.member.id, this.member)
}
this.user = new User(info.user ?? info.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (info.data) {
this.componentType = info.data.component_type
this.customId = info.data.custom_id
this.components = info.data.components
this.values = info.data.values
this.commandType = info.data.type
this.options = info.data.options
this.targetID = info.data.target_id
}
}
get data() {
return {
component_type: this.componentType,
custom_id: this.customId,
components: this.components,
values: this.values,
type: this.commandType,
resolved: {
messages: this.messages.toRecord(),
users: this.users.toRecord(),
members: this.members.toRecord(),
roles: this.roles.toRecord(),
channels: this.channels.toRecord(),
attachments: this.attachments.toRecord(),
},
options: this.options,
target_id: this.targetID,
guild_id: this.guildID,
}
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async acknowledge(flags?: number): Promise<void> {
return this.defer(flags)
}
/** Respond to the interaction with a followup message */
async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, {
...content,
wait: true,
file,
})
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
*/
async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]): Promise<void> {
if (this.acknowledged) {
// @ts-expect-error some eris magic at play here
return await this.createFollowup(content, file)
}
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
return this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async defer(flags?: number): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags ?? 0,
},
})
.then(() => this.update())
}
/** Delete a message */
async deleteMessage(messageID: BigString): Promise<void> {
if (!this.acknowledged) {
throw new Error('deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the Original message
* Warning: Will error with ephemeral messages.
*/
async deleteOriginalMessage(): Promise<void> {
if (!this.acknowledged) {
throw new Error('deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/** Edit a message */
async editMessage(messageID: BigString, content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, {
...content,
file,
})
}
/** Edit the Original response message */
async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error('editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', {
...content,
file,
})
}
/**
* Get the Original response message
* Warning: Will error with ephemeral messages.
*/
async getOriginalMessage(): Promise<Message> {
if (!this.acknowledged) {
throw new Error('getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, or defer first.')
}
return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
}
export default CommandInteraction

View File

@@ -0,0 +1,296 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import type { BigString, DiscordInteraction, MessageComponentTypes } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type { AnyChannel, FileContent, InteractionApplicationCommandCallbackData, MessageWebhookContent, WebhookPayload } from '../../typings.js'
import type Guild from '../guilds/Guild.js'
import Member from '../guilds/Member.js'
import Message from '../Message.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class ComponentInteraction extends Interaction {
/** The channel id where this interaction occurred in. */
channelID: BigString
/** The guild id where this interaction occurred in. */
guildID?: BigString
/** The member object if this interaction occurred in a guild. */
member?: Member
/** The user object for the user that created this interaction. */
user: User
/** The permissions the app or bot has within the channel, the interaction was sent from. */
appPermissions?: Permission
/** The message object, if this interaction occurred on a message. */
message?: Message
/** The custom id of the component. */
customID: string
/** The type of component. */
componentType: MessageComponentTypes
/** The values from a selector component. */
values?: string[]
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
this.channelID = data.channel_id!
// this.data = data.data;
this.guildID = data.guild_id
// Required to make a component
this.customID = data.data!.custom_id!
this.componentType = data.data!.component_type!
this.values = data.data!.values
if (data.member !== undefined && this.guild) {
this.member = new Member(data.member, this.guild, this.client)
this.guild.members.set(this.member.id, this.member)
}
if (data.message !== undefined) {
this.message = new Message(data.message, this.client)
}
this.user = new User(data.user ?? data.member!.user, this.client)
this.client.users.set(this.user.id, this.user)
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/** The channel if cached, where this interaction occurred. */
get channel(): AnyChannel | undefined {
return this.channelID ? this.client.getChannel(this.channelID) : undefined
}
/** The guild if cached, where this interaction occurred. */
get guild(): Guild | undefined {
return this.guildID ? this.client.guilds.get(this.guildID) : undefined
}
/** Acknowledges the interaction with a defer message update response */
async acknowledge(): Promise<void> {
return await this.deferUpdate()
}
/** Respond to the interaction with a followup message. */
async createFollowup(content: WebhookPayload, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, { ...content, wait: true })
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
*/
async createMessage(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise<Message | undefined> {
if (this.acknowledged) {
return await this.createFollowup(content, file)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async defer(flags: number): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags || 0,
},
})
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer message update response
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async deferUpdate(): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
})
.then(() => this.update())
}
/** Delete a message */
async deleteMessage(messageID: BigString): Promise<void> {
if (!this.acknowledged) {
throw new Error(
'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the parent message
* Warning: Will error with ephemeral messages.
*/
async deleteOriginalMessage(): Promise<void> {
if (!this.acknowledged) {
throw new Error(
'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/** Edit a message */
async editMessage(messageID: BigString, content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content)
}
/** Edit the parent message */
async editOriginalMessage(content: MessageWebhookContent, file?: FileContent | FileContent[]): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content)
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
*/
async editParent(content: InteractionApplicationCommandCallbackData, file?: FileContent | FileContent[]): Promise<Message | undefined> {
if (this.acknowledged) {
return this.editOriginalMessage(content)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.UpdateMessage,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Get the parent message
* Warning: Will error with ephemeral messages.
*/
async getOriginalMessage(): Promise<Message> {
if (!this.acknowledged) {
throw new Error(
'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, or editParent first.',
)
}
return this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
}
export default ComponentInteraction

View File

@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { InteractionTypes, type BigString, type DiscordInteraction } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import AutocompleteInteraction from './Autocomplete.js'
import CommandInteraction from './Command.js'
import ComponentInteraction from './Component.js'
import PingInteraction from './Ping.js'
import UnknownInteraction from './Unknown.js'
export class Interaction extends Base {
client: Client
applicationID: BigString
token: string
type: InteractionTypes
version: 1
acknowledged: boolean
constructor(data: DiscordInteraction, client: Client) {
super(data.id)
this.client = client
this.applicationID = data.application_id
this.token = data.token
this.type = data.type
this.version = data.version
this.acknowledged = false
}
/**
* @deprecated Use `.client`
*/
get _client(): Client {
return this.client
}
update() {
this.acknowledged = true
}
static from(data: DiscordInteraction, client: Client) {
switch (data.type) {
case InteractionTypes.Ping: {
return new PingInteraction(data, client)
}
case InteractionTypes.ApplicationCommand: {
return new CommandInteraction(data, client)
}
case InteractionTypes.MessageComponent: {
return new ComponentInteraction(data, client)
}
case InteractionTypes.ApplicationCommandAutocomplete: {
return new AutocompleteInteraction(data, client)
}
}
client.emit('warn', new Error(`Unknown interaction type: ${data.type}\n${JSON.stringify(data)}`))
return new UnknownInteraction(data, client)
}
}
export default Interaction

View File

@@ -0,0 +1,33 @@
/* eslint-disable no-useless-call */
/* eslint-disable @typescript-eslint/return-await */
import { InteractionResponseTypes } from '@discordeno/types'
import Interaction from './Interaction.js'
export class PingInteraction extends Interaction {
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async acknowledge(): Promise<void> {
return this.pong()
}
/**
* Acknowledges the ping interaction with a pong response.
* Note: You can **not** use more than 1 initial interaction response per interaction.
*/
async pong(): Promise<void> {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.Pong,
})
.then(() => this.update())
}
}
export default PingInteraction

View File

@@ -0,0 +1,493 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable no-useless-call */
import type { DiscordInteraction } from '@discordeno/types'
import { InteractionResponseTypes } from '@discordeno/types'
import type Client from '../../Client.js'
import type {
ApplicationCommandOptionChoice,
FileContent,
InteractionContent,
InteractionContentEdit,
InteractionResponse,
PossiblyUncachedTextable,
TextableChannel,
} from '../../typings.js'
import Member from '../guilds/Member.js'
import Message from '../Message.js'
import Permission from '../Permission.js'
import User from '../users/User.js'
import Interaction from './Interaction.js'
export class UnknownInteraction<T extends PossiblyUncachedTextable = TextableChannel> extends Interaction {
channel?: T
data?: unknown
guildID?: string
member?: Member
message?: Message
type: number = 0
user?: User
appPermissions?: Permission
constructor(data: DiscordInteraction, client: Client) {
super(data, client)
if (data.channel_id !== undefined) {
this.channel = (this.client.getChannel(data.channel_id) as unknown as T) || {
id: data.channel_id,
}
}
if (data.data !== undefined) {
this.data = data.data
}
if (data.guild_id !== undefined) {
this.guildID = data.guild_id
}
if (data.member !== undefined) {
const guild = this.client.guilds.get(data.guild_id ?? '')!
this.member = new Member(data.member, guild, this.client)
guild.members.set(this.member.id, this.member)
}
if (data.message !== undefined) {
this.message = new Message(data.message, this.client)
}
if (data.user !== undefined) {
this.user = new User(data.user, this.client)
this.client.users.set(this.user.id, this.user)
}
if (data.app_permissions !== undefined) {
this.appPermissions = new Permission(data.app_permissions)
}
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Object} data The data object
* @arg {Number} data.type The type of [interaction response](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type) to send
* @arg {Object} data.data The data to return to discord
* @returns {Promise}
*/
async acknowledge(data: InteractionResponse) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse.call(this.client, this.id, this.token, data).then(() => this.update())
}
/**
* Respond to the interaction with a followup message
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [options.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Number} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message?>}
*/
async createFollowup(content: string | InteractionContent, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'createFollowup cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.executeWebhook.call(this.client, this.applicationID, this.token, Object.assign({ wait: true as true }, content))
}
/**
* Acknowledges the interaction with a message. If already acknowledged runs createFollowup
* Note: You can **not** use more than 1 initial interaction response per interaction, use createFollowup if you have already responded with a different interaction response.
* @arg {String | Object} content A string or object. If an object is passed:
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async createMessage(content: string | InteractionContent, file?: FileContent | FileContent[]) {
if (this.acknowledged) {
return await this.createFollowup(content, file)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.ChannelMessageWithSource,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer response
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Number} [flags] 64 for Ephemeral
* @returns {Promise}
*/
async defer(flags: number) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: flags || 0,
},
})
.then(() => this.update())
}
/**
* Acknowledges the interaction with a defer message update response (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async deferUpdate() {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
})
.then(() => this.update())
}
/**
* Delete a message
* @arg {String} messageID the id of the message to delete, or "@original" for the original response.
* @returns {Promise}
*/
async deleteMessage(messageID: string) {
if (!this.acknowledged) {
throw new Error(
'deleteMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, messageID)
}
/**
* Delete the Original message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise}
*/
async deleteOriginalMessage() {
if (!this.acknowledged) {
throw new Error(
'deleteOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.deleteWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/**
* Edit a message
* @arg {String} messageID the id of the message to edit, or "@original" for the original response.
* @arg {Object} content Interaction message edit options
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editMessage(messageID: string, content: string | InteractionContentEdit, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'editMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, messageID, content)
}
/**
* Edit the Original response message
* @arg {Object} content Interaction message edit options (or the parent message for components)
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise<Message>}
*/
async editOriginalMessage(content: string | InteractionContentEdit, file?: FileContent | FileContent[]) {
if (!this.acknowledged) {
throw new Error(
'editOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, pong, or result first.',
)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
if (file) {
content.file = file
}
return await this.client.editWebhookMessage.call(this.client, this.applicationID, this.token, '@original', content)
}
/**
* Acknowledges the interaction by editing the parent message. If already acknowledged runs editOriginalMessage (Message Component only)
* Note: You can **not** use more than 1 initial interaction response per interaction, use edit if you have already responded with a different interaction response.
* Warning: Will error with ephemeral messages.
* @arg {String | Object} content What to edit the message with
* @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default)
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [content.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [content.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [content.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [content.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [content.components[].options[].description] The description for this option
* @arg {Object} [content.components[].options[].emoji] The emoji to be displayed in this option
* @arg {String} content.components[].options[].label The label for this option
* @arg {Number | String} content.components[].options[].value The value for this option
* @arg {String} [content.components[].placeholder] The placeholder text for the component when no option is selected (type 3 only)
* @arg {Number} [content.components[].style] The style of the component (type 2 only) - If 0-4, `custom_id` is required; if 5, `url` is required
* @arg {Number} content.components[].type The type of component - If 1, it is a collection and a `components` array (nested) is required; if 2, it is a button; if 3, it is a select menu
* @arg {String} [content.components[].url] The URL that the component should open for users (type 2 style 5 only)
* @arg {String} [content.content] A content string
* @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Array<Object>} [content.embeds] An array of embed objects. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#embed-object) for object structure
* @arg {Boolean} [content.flags] 64 for Ephemeral
* @arg {Boolean} [content.tts] Set the message TTS flag
* @arg {Object | Array<Object>} [file] A file object (or an Array of them)
* @arg {Buffer} file.file A buffer containing file data
* @arg {String} file.name What to name the file
* @returns {Promise}
*/
async editParent(content: InteractionContentEdit, file?: FileContent | FileContent[]) {
if (this.acknowledged) {
return await this.editOriginalMessage(content)
}
if (content !== undefined) {
if (typeof content !== 'object' || content === null) {
content = {
content: '' + content,
}
} else if (content.content !== undefined && typeof content.content !== 'string') {
content.content = '' + content.content
}
}
return await this.client.createInteractionResponse
.call(
this.client,
this.id,
this.token,
{
type: InteractionResponseTypes.UpdateMessage,
data: content,
},
file,
)
.then(() => this.update())
}
/**
* Get the Original response message (or the parent message for components)
* Warning: Will error with ephemeral messages.
* @returns {Promise<Message>}
*/
async getOriginalMessage() {
if (!this.acknowledged) {
throw new Error(
'getOriginalMessage cannot be used to acknowledge an interaction, please use acknowledge, createMessage, defer, deferUpdate, editParent, or pong first.',
)
}
return await this.client.getWebhookMessage.call(this.client, this.applicationID, this.token, '@original')
}
/**
* Acknowledges the ping interaction with a pong response (Ping Only)
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @returns {Promise}
*/
async pong() {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.Pong,
})
.then(() => this.update())
}
/**
* Acknowledges the autocomplete interaction with a result of choices.
* Note: You can **not** use more than 1 initial interaction response per interaction.
* @arg {Array<Object>} choices The autocomplete choices to return to the user
* @arg {String | Number} choices[].name The choice display name
* @arg {String} choices[].value The choice value to return to the bot
* @returns {Promise}
*/
async result(choices: ApplicationCommandOptionChoice[]) {
if (this.acknowledged) {
throw new Error('You have already acknowledged this interaction.')
}
return await this.client.createInteractionResponse
.call(this.client, this.id, this.token, {
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
data: { choices },
})
.then(() => this.update())
}
}
export default UnknownInteraction

View File

@@ -0,0 +1,38 @@
import type { PremiumTypes, DiscordUser } from "@discordeno/types"
import type Client from "../../Client.js"
import User from "./User.js"
export class ExtendedUser extends User {
email?: string | null
verified?: boolean
mfaEnabled?: boolean
premiumType?: PremiumTypes
constructor(data: DiscordUser, client: Client) {
super(data, client)
this.update(data)
}
update(data: DiscordUser): void {
super.update(data)
if (data.email !== undefined) {
this.email = data.email
}
if (data.verified !== undefined) {
this.verified = data.verified
}
if (data.mfa_enabled !== undefined) {
this.mfaEnabled = data.mfa_enabled
}
if (data.premium_type !== undefined) {
this.premiumType = data.premium_type
}
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['email', 'mfaEnabled', 'premium', 'verified', ...props])
}
}
export default ExtendedUser

View File

@@ -0,0 +1,128 @@
/* eslint-disable no-useless-call */
import type { BigString, DiscordUser, UserFlags } from '@discordeno/types'
import Base from '../../Base.js'
import type Client from '../../Client.js'
import type { ImageFormat, ImageSize } from '../../Client.js'
import { BANNER, DEFAULT_USER_AVATAR, USER_AVATAR } from '../../Endpoints.js'
import type PrivateChannel from '../channels/Private.js'
export class User extends Base {
client: Client
bot: boolean
system: boolean
_avatar!: bigint | null
username!: string
discriminator!: string
publicFlags?: UserFlags
_banner!: bigint | null
accentColor?: number
constructor(data: DiscordUser, client: Client) {
super(data.id)
this.client = client
this.bot = !!data.bot
this.system = !!data.system
this.update(data)
}
/** @deprecated Use User.client Supported for Eris api compatibility. */
get _client(): Client {
return this.client
}
get avatar(): string | null {
if (!this._avatar) return null
return this.client.iconBigintToHash(this._avatar)
}
set avatar(value: BigString | null) {
this._avatar = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value
}
get banner(): string | null {
if (!this._banner) return null
return this.client.iconBigintToHash(this._banner)
}
set banner(value: BigString | null) {
this._banner = typeof value === 'string' ? this.client.iconHashToBigInt(value) : value
}
get avatarURL(): string | null {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL
}
get bannerURL(): string | null {
if (!this.banner) {
return null
}
return this.client._formatImage(BANNER(this.id, this.banner))
}
get defaultAvatar(): string {
// @ts-expect-error some eris magic at play here
return (this.discriminator % 5).toString()
}
get defaultAvatarURL(): string {
return `${this.client.CDN_URL}${DEFAULT_USER_AVATAR(this.defaultAvatar)}.png`
}
get mention(): string {
return `<@${this.id}>`
}
get staticAvatarURL(): string {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), 'jpg') : this.defaultAvatarURL
}
update(data: DiscordUser): void {
if (data.avatar !== undefined) {
this.avatar = data.avatar
}
if (data.username !== undefined) {
this.username = data.username
}
if (data.discriminator !== undefined) {
this.discriminator = data.discriminator
}
if (data.public_flags !== undefined) {
this.publicFlags = data.public_flags
}
if (data.banner !== undefined) {
this.banner = data.banner
}
if (data.accent_color !== undefined) {
this.accentColor = data.accent_color
}
}
/** Get the user's avatar with the given format and size */
dynamicAvatarURL(format?: ImageFormat, size?: ImageSize): string {
return this.avatar ? this.client._formatImage(USER_AVATAR(this.id, this.avatar), format, size) : this.defaultAvatarURL
}
/** Get the user's banner with the given format and size */
dynamicBannerURL(format?: ImageFormat, size?: ImageSize): string | null {
return this.banner ? this.client._formatImage(BANNER(this.id, this.banner), format, size) : null
}
/**
* Get a DM channel with the user, or create one if it does not exist
* @returns {Promise<PrivateChannel>}
*/
async getDMChannel(): Promise<PrivateChannel> {
return await this.client.getDMChannel.call(this.client, this.id)
}
toJSON(props: string[] = []): Record<string, any> {
return super.toJSON(['accentColor', 'avatar', 'banner', 'bot', 'discriminator', 'publicFlags', 'system', 'username', ...props])
}
}
export default User

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import Base from '../Base.js'
import type Client from '../Client.js'
import Collection from '../Collection.js'
import type { ShardManagerOptions } from '../typings.js'
import Shard from './Shard.js'
export class ShardManager extends Collection<number, Shard> {
/** The client manager */
client: Client
/** The options that were used to configure this manager. */
options: ShardManagerOptions
/** The buckets that this manager is handling. */
buckets: Map<number, number>
/** The queue in which to connect a shard. */
connectQueue: Shard[]
/** The timeout to use for connecting a shard. */
connectTimeout: NodeJS.Timeout | null
constructor(client: Client, options: ShardManagerOptions = {}) {
super()
this.client = client
this.options = Object.assign(
{
concurrency: 1,
},
options,
)
this.buckets = new Map()
this.connectQueue = []
this.connectTimeout = null
}
/**
* @deprecated Use `.client` instead.
*/
get _client(): Client {
return this.client
}
connect(shard: Shard) {
this.connectQueue.push(shard)
this.tryConnect()
}
get concurrency(): number {
return this.options.concurrency as number
}
setConcurrency(concurrency: number) {
this.options.concurrency = concurrency
}
spawn(id: number) {
let shard = this.get(id)
if (!shard) {
shard = new Shard(id, this.client)
this.set(id, shard)
shard
.on('ready', () => {
this.client.emit('shardReady', shard!.id)
if (this.client.ready) return
for (const other of this.values()) if (!other.ready) return
this.client.ready = true
this.client.startTime = Date.now()
this.client.emit('ready')
})
.on('resume', () => {
this.client.emit('shardResume', shard!.id)
if (this.client.ready) return
for (const other of this.values()) if (!other.ready) return
this.client.ready = true
this.client.startTime = Date.now()
this.client.emit('ready')
})
.on('disconnect', (error) => {
this.client.emit('shardDisconnect', error, shard!.id)
for (const other of this.values()) if (other.ready) return
this.client.ready = false
this.client.startTime = 0
this.client.emit('disconnect')
})
}
if (shard.status === 'disconnected') {
return this.connect(shard)
}
}
tryConnect() {
// nothing in queue
if (this.connectQueue.length === 0) {
return
}
// loop over the connectQueue
for (const shard of this.connectQueue) {
// find the bucket for our shard
const rateLimitKey = shard.id % this.concurrency || 0
const lastConnect = this.buckets.get(rateLimitKey) ?? 0
// has enough time passed since the last connect for this bucket (5s/bucket)?
// alternatively if we have a sessionID, we can skip this check
if (!shard.sessionID && Date.now() - lastConnect < 5000) {
continue
}
// Are there any connecting shards in the same bucket we should wait on?
if (this.some((s) => s.connecting && (s.id % this.concurrency || 0) === rateLimitKey)) {
continue
}
// connect the shard
shard.connect()
this.buckets.set(rateLimitKey, Date.now())
// remove the shard from the queue
const index = this.connectQueue.findIndex((s) => s.id === shard.id)
this.connectQueue.splice(index, 1)
}
// set the next timeout if we have more shards to connect
if (!this.connectTimeout && this.connectQueue.length > 0) {
this.connectTimeout = setTimeout(() => {
this.connectTimeout = null
this.tryConnect()
}, 500)
}
}
_readyPacketCB(shardID: number) {
const rateLimitKey = shardID % this.concurrency || 0
this.buckets.set(rateLimitKey, Date.now())
this.tryConnect()
}
toString() {
return `[ShardManager ${this.size}]`
}
toJSON(props = []) {
return Base.prototype.toJSON.call(this, ['buckets', 'connectQueue', 'connectTimeout', 'options', ...props])
}
}
export default ShardManager

View File

@@ -0,0 +1,48 @@
import Client from './Client.js'
export default Client
export * from './Base.js'
export * from './Client.js'
export * from './Collection.js'
export * from './Endpoints.js'
export * from './gateway/Shard.js'
export * from './gateway/ShardManager.js'
export * from './Structures/channels/Category.js'
export * from './Structures/channels/Channel.js'
export * from './Structures/channels/Guild.js'
export * from './Structures/channels/News.js'
export * from './Structures/channels/Private.js'
export * from './Structures/channels/Stage.js'
export * from './Structures/channels/Text.js'
export * from './Structures/channels/TextVoice.js'
export * from './Structures/channels/threads/Member.js'
export * from './Structures/channels/threads/NewsThread.js'
export * from './Structures/channels/threads/PrivateThread.js'
export * from './Structures/channels/threads/PublicThread.js'
export * from './Structures/channels/threads/Thread.js'
export * from './Structures/channels/Voice.js'
export * from './Structures/guilds/AuditLogEntry.js'
export * from './Structures/guilds/Guild.js'
export * from './Structures/guilds/Integration.js'
export * from './Structures/guilds/Member.js'
export * from './Structures/guilds/Preview.js'
export * from './Structures/guilds/Role.js'
export * from './Structures/guilds/StageInstance.js'
export * from './Structures/guilds/Template.js'
export * from './Structures/guilds/Unavailable.js'
export * from './Structures/guilds/VoiceState.js'
export * from './Structures/interactions/Autocomplete.js'
export * from './Structures/interactions/Command.js'
export * from './Structures/interactions/Component.js'
export * from './Structures/interactions/Interaction.js'
export * from './Structures/interactions/Ping.js'
export * from './Structures/interactions/Unknown.js'
export * from './Structures/Invite.js'
export * from './Structures/Message.js'
export * from './Structures/Permission.js'
export * from './Structures/PermissionOverwrite.js'
export * from './Structures/users/Extended.js'
export * from './Structures/users/User.js'
export * from './typings.js'
export * from './utils/BrowserWebSocket.js'
export * from './utils/Bucket.js'

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-var-requires */
let EventEmitter
try {
EventEmitter = require('eventemitter3')
} catch (err) {
EventEmitter = require('events').EventEmitter
}
class BrowserWebSocketError extends Error {
static CONNECTING: 0
static OPEN: 1
static CLOSING: 2
static CLOSED: 3
readyState: number = 0
event: Event
constructor(message: string | undefined, event: Event) {
super(message)
this.event = event
}
}
/**
* Represents a browser's websocket usable by Eris
* @extends EventEmitter
* @prop {String} url The URL to connect to
*/
class BrowserWebSocket extends EventEmitter {
_ws: WebSocket
constructor(url: string) {
super()
if (typeof window === 'undefined') {
throw new Error('BrowserWebSocket cannot be used outside of a browser environment')
}
this._ws = new window.WebSocket(url)
this._ws.onopen = () => this.emit('open')
this._ws.onmessage = this._onMessage.bind(this)
this._ws.onerror = (event) => this.emit('error', new BrowserWebSocketError('Unknown error', event))
this._ws.onclose = (event) => this.emit('close', event.code, event.reason)
}
get readyState() {
return this._ws.readyState
}
close(code?: number, reason?: string) {
return this._ws.close(code, reason)
}
removeEventListener(type: string | symbol, listener: (...args: any[]) => void): this {
return this.removeListener(type, listener)
}
send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
return this._ws.send(data)
}
terminate() {
return this._ws.close()
}
async _onMessage(event: MessageEvent<any>) {
if (event.data instanceof window.Blob) {
this.emit('message', await event.data.arrayBuffer())
} else {
this.emit('message', event.data)
}
}
}
BrowserWebSocket.CONNECTING = 0
BrowserWebSocket.OPEN = 1
BrowserWebSocket.CLOSING = 2
BrowserWebSocket.CLOSED = 3
export default BrowserWebSocket

View File

@@ -0,0 +1,101 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
interface BucketOptions {
latencyRef?: { latency: number }
reservedTokens?: number
}
/**
* Handle ratelimiting something
* @prop {Number} interval How long (in ms) to wait between clearing used tokens
* @prop {Number} lastReset Timestamp of last token clearing
* @prop {Number} lastSend Timestamp of last token consumption
* @prop {Number} tokenLimit The max number tokens the bucket can consume per interval
* @prop {Number} tokens How many tokens the bucket has consumed in this interval
*/
class Bucket {
interval: number
latencyRef: { latency: number }
lastReset: number
lastSend: number
tokenLimit: number
tokens: number
reservedTokens: number
_queue: Array<{ func: Function; priority: boolean }>
timeout: NodeJS.Timeout | null = null
/**
* Construct a Bucket
* @arg {Number} tokenLimit The max number of tokens the bucket can consume per interval
* @arg {Number} interval How long (in ms) to wait between clearing used tokens
* @arg {Object} [options] Optional parameters
* @arg {Object} options.latencyRef A latency reference object
* @arg {Number} options.latencyRef.latency Interval between consuming tokens
* @arg {Number} options.reservedTokens How many tokens to reserve for priority operations
*/
constructor(tokenLimit: number, interval: number, options: BucketOptions = {} as BucketOptions) {
this.tokenLimit = tokenLimit
this.interval = interval
this.latencyRef = options.latencyRef ?? { latency: 0 }
this.lastReset = this.tokens = this.lastSend = 0
this.reservedTokens = options.reservedTokens ?? 0
this._queue = []
}
check() {
if (this.timeout ?? this._queue.length === 0) {
return
}
if (this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency < Date.now()) {
this.lastReset = Date.now()
this.tokens = Math.max(0, this.tokens - this.tokenLimit)
}
let val
let tokensAvailable = this.tokens < this.tokenLimit
let unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens
while (this._queue.length > 0 && (unreservedTokensAvailable || (tokensAvailable && this._queue[0].priority))) {
this.tokens++
tokensAvailable = this.tokens < this.tokenLimit
unreservedTokensAvailable = this.tokens < this.tokenLimit - this.reservedTokens
const item = this._queue.shift()
val = this.latencyRef.latency - Date.now() + this.lastSend
if (this.latencyRef.latency === 0 || val <= 0) {
item!.func()
this.lastSend = Date.now()
} else {
setTimeout(() => {
item!.func()
}, val)
this.lastSend = Date.now() + val
}
}
if (this._queue.length > 0 && !this.timeout) {
this.timeout = setTimeout(
() => {
this.timeout = null
this.check()
},
this.tokens < this.tokenLimit
? this.latencyRef.latency
: Math.max(0, this.lastReset + this.interval + this.tokenLimit * this.latencyRef.latency - Date.now()),
)
}
}
/**
* Queue something in the Bucket
* @arg {Function} func A callback to call when a token can be consumed
* @arg {Boolean} [priority=false] Whether or not the callback should use reserved tokens
*/
queue(func: () => void, priority = false) {
if (priority) {
this._queue.unshift({ func, priority })
} else {
this._queue.push({ func, priority })
}
this.check()
}
}
export default Bucket

View File

@@ -0,0 +1 @@
export { }

View File

@@ -0,0 +1,16 @@
{
"extends": "tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist",
},
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
],
"exclude": [
"node_modules",
"dist",
"test",
"tests"
]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "tsconfig/test.json",
"include": [
"tests",
],
"exclude": [
"node_modules",
"dist",
"src"
]
}

View File

@@ -58,7 +58,7 @@ import type {
*/
export function createBot(options: CreateBotOptions): Bot {
if (!options.rest) options.rest = { token: options.token }
if (!options.gateway)
if (!options.gateway) {
options.gateway = {
token: options.token,
events: {
@@ -79,6 +79,7 @@ export function createBot(options: CreateBotOptions): Bot {
},
},
}
}
options.rest.token = options.token
options.gateway.token = options.token

File diff suppressed because it is too large Load Diff

View File

@@ -500,3 +500,19 @@ export interface ModifyGuildEmoji extends WithReason {
/** Roles allowed to use this emoji */
roles?: BigString[] | null
}
/** https://discord.com/developers/docs/topics/gateway#request-guild-members */
export interface RequestGuildMembers {
/** id of the guild to get members for */
guildId: BigString
/** String that username starts with, or an empty string to return all members */
query?: string
/** Maximum number of members to send matching the query; a limit of 0 can be used with an empty string query to return all members */
limit: number
/** Used to specify if we want the presences of the matched members */
presences?: boolean
/** Used to specify which users you wish to fetch */
userIds?: BigString[]
/** Nonce to identify the Guild Members Chunk response */
nonce?: string
}

View File

@@ -133,34 +133,28 @@ export enum ButtonStyles {
Link,
}
// /** https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types */
// export enum AllowedMentionsTypes {
// /** Controls role mentions */
// RoleMentions = 'roles',
// /** Controls user mentions */
// UserMentions = 'users',
// /** Controls @everyone and @here mentions */
// EveryoneMentions = 'everyone',
// }
/** https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types */
export enum AllowedMentionsTypes {
/** Controls role mentions */
RoleMentions = 'roles',
/** Controls user mentions */
UserMentions = 'users',
/** Controls @everyone and @here mentions */
EveryoneMentions = 'everyone',
}
// /** https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types */
// export enum WebhookTypes {
// /** Incoming Webhooks can post messages to channels with a generated token */
// Incoming = 1,
// /** Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels */
// ChannelFollower,
// /** Application webhooks are webhooks used with Interactions */
// Application,
// }
/** https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types */
export enum WebhookTypes {
/** Incoming Webhooks can post messages to channels with a generated token */
Incoming = 1,
/** Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels */
ChannelFollower,
/** Application webhooks are webhooks used with Interactions */
Application,
}
// /** https://discord.com/developers/docs/resources/channel#embed-object-embed-types */
// export type EmbedTypes =
// | 'rich'
// | 'image'
// | 'video'
// | 'gifv'
// | 'article'
// | 'link'
/** https://discord.com/developers/docs/resources/channel#embed-object-embed-types */
export type EmbedTypes = 'rich' | 'image' | 'video' | 'gifv' | 'article' | 'link'
/** https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level */
export enum DefaultMessageNotificationLevels {
@@ -374,13 +368,13 @@ export enum MessageTypes {
AutoModerationAction,
}
// /** https://discord.com/developers/docs/resources/channel#message-object-message-activity-types */
// export enum MessageActivityTypes {
// Join = 1,
// Spectate,
// Listen,
// JoinRequest,
// }
/** https://discord.com/developers/docs/resources/channel#message-object-message-activity-types */
export enum MessageActivityTypes {
Join = 1,
Spectate,
Listen,
JoinRequest,
}
/** https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types */
export enum StickerTypes {
@@ -421,113 +415,113 @@ export enum ApplicationCommandOptionTypes {
Attachment,
}
// /** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events */
// export enum AuditLogEvents {
// /** Server settings were updated */
// GuildUpdate = 1,
// /** Channel was created */
// ChannelCreate = 10,
// /** Channel settings were updated */
// ChannelUpdate,
// /** Channel was deleted */
// ChannelDelete,
// /** Permission overwrite was added to a channel */
// ChannelOverwriteCreate,
// /** Permission overwrite was updated for a channel */
// ChannelOverwriteUpdate,
// /** Permission overwrite was deleted from a channel */
// ChannelOverwriteDelete,
// /** Member was removed from server */
// MemberKick = 20,
// /** Members were pruned from server */
// MemberPrune,
// /** Member was banned from server */
// MemberBanAdd,
// /** Server ban was lifted for a member */
// MemberBanRemove,
// /** Member was updated in server */
// MemberUpdate,
// /** Member was added or removed from a role */
// MemberRoleUpdate,
// /** Member was moved to a different voice channel */
// MemberMove,
// /** Member was disconnected from a voice channel */
// MemberDisconnect,
// /** Bot user was added to server */
// BotAdd,
// /** Role was created */
// RoleCreate = 30,
// /** Role was edited */
// RoleUpdate,
// /** Role was deleted */
// RoleDelete,
// /** Server invite was created */
// InviteCreate = 40,
// /** Server invite was updated */
// InviteUpdate,
// /** Server invite was deleted */
// InviteDelete,
// /** Webhook was created */
// WebhookCreate = 50,
// /** Webhook properties or channel were updated */
// WebhookUpdate,
// /** Webhook was deleted */
// WebhookDelete,
// /** Emoji was created */
// EmojiCreate = 60,
// /** Emoji name was updated */
// EmojiUpdate,
// /** Emoji was deleted */
// EmojiDelete,
// /** Single message was deleted */
// MessageDelete = 72,
// /** Multiple messages were deleted */
// MessageBulkDelete,
// /** Messaged was pinned to a channel */
// MessagePin,
// /** Message was unpinned from a channel */
// MessageUnpin,
// /** App was added to server */
// IntegrationCreate = 80,
// /** App was updated (as an example, its scopes were updated) */
// IntegrationUpdate,
// /** App was removed from server */
// IntegrationDelete,
// /** Stage instance was created (stage channel becomes live) */
// StageInstanceCreate,
// /** Stage instace details were updated */
// StageInstanceUpdate,
// /** Stage instance was deleted (stage channel no longer live) */
// StageInstanceDelete,
// /** Sticker was created */
// StickerCreate = 90,
// /** Sticker details were updated */
// StickerUpdate,
// /** Sticker was deleted */
// StickerDelete,
// /** Event was created */
// GuildScheduledEventCreate = 100,
// /** Event was updated */
// GuildScheduledEventUpdate,
// /** Event was cancelled */
// GuildScheduledEventDelete,
// /** Thread was created in a channel */
// ThreadCreate = 110,
// /** Thread was updated */
// ThreadUpdate,
// /** Thread was deleted */
// ThreadDelete,
// /** Permissions were updated for a command */
// ApplicationCommandPermissionUpdate = 121,
// /** Auto moderation rule was created */
// AutoModerationRuleCreate = 140,
// /** Auto moderation rule was updated */
// AutoModerationRuleUpdate,
// /** Auto moderation rule was deleted */
// AutoModerationRuleDelete,
// /** Message was blocked by AutoMod according to a rule. */
// AutoModerationBlockMessage,
// }
/** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events */
export enum AuditLogEvents {
/** Server settings were updated */
GuildUpdate = 1,
/** Channel was created */
ChannelCreate = 10,
/** Channel settings were updated */
ChannelUpdate,
/** Channel was deleted */
ChannelDelete,
/** Permission overwrite was added to a channel */
ChannelOverwriteCreate,
/** Permission overwrite was updated for a channel */
ChannelOverwriteUpdate,
/** Permission overwrite was deleted from a channel */
ChannelOverwriteDelete,
/** Member was removed from server */
MemberKick = 20,
/** Members were pruned from server */
MemberPrune,
/** Member was banned from server */
MemberBanAdd,
/** Server ban was lifted for a member */
MemberBanRemove,
/** Member was updated in server */
MemberUpdate,
/** Member was added or removed from a role */
MemberRoleUpdate,
/** Member was moved to a different voice channel */
MemberMove,
/** Member was disconnected from a voice channel */
MemberDisconnect,
/** Bot user was added to server */
BotAdd,
/** Role was created */
RoleCreate = 30,
/** Role was edited */
RoleUpdate,
/** Role was deleted */
RoleDelete,
/** Server invite was created */
InviteCreate = 40,
/** Server invite was updated */
InviteUpdate,
/** Server invite was deleted */
InviteDelete,
/** Webhook was created */
WebhookCreate = 50,
/** Webhook properties or channel were updated */
WebhookUpdate,
/** Webhook was deleted */
WebhookDelete,
/** Emoji was created */
EmojiCreate = 60,
/** Emoji name was updated */
EmojiUpdate,
/** Emoji was deleted */
EmojiDelete,
/** Single message was deleted */
MessageDelete = 72,
/** Multiple messages were deleted */
MessageBulkDelete,
/** Messaged was pinned to a channel */
MessagePin,
/** Message was unpinned from a channel */
MessageUnpin,
/** App was added to server */
IntegrationCreate = 80,
/** App was updated (as an example, its scopes were updated) */
IntegrationUpdate,
/** App was removed from server */
IntegrationDelete,
/** Stage instance was created (stage channel becomes live) */
StageInstanceCreate,
/** Stage instace details were updated */
StageInstanceUpdate,
/** Stage instance was deleted (stage channel no longer live) */
StageInstanceDelete,
/** Sticker was created */
StickerCreate = 90,
/** Sticker details were updated */
StickerUpdate,
/** Sticker was deleted */
StickerDelete,
/** Event was created */
GuildScheduledEventCreate = 100,
/** Event was updated */
GuildScheduledEventUpdate,
/** Event was cancelled */
GuildScheduledEventDelete,
/** Thread was created in a channel */
ThreadCreate = 110,
/** Thread was updated */
ThreadUpdate,
/** Thread was deleted */
ThreadDelete,
/** Permissions were updated for a command */
ApplicationCommandPermissionUpdate = 121,
/** Auto moderation rule was created */
AutoModerationRuleCreate = 140,
/** Auto moderation rule was updated */
AutoModerationRuleUpdate,
/** Auto moderation rule was deleted */
AutoModerationRuleDelete,
/** Message was blocked by AutoMod according to a rule. */
AutoModerationBlockMessage,
}
export enum ScheduledEventPrivacyLevel {
/** the scheduled event is public and available in discovery. DISCORD DEVS DISABLED THIS! WILL ERROR IF USED! */
@@ -583,91 +577,91 @@ export enum ApplicationCommandPermissionTypes {
// Embedded = 1 << 8,
// }
// /** https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags */
// export enum BitwisePermissionFlags {
// /** Allows creation of instant invites */
// CREATE_INSTANT_INVITE = 0x0000000000000001,
// /** Allows kicking members */
// KICK_MEMBERS = 0x0000000000000002,
// /** Allows banning members */
// BAN_MEMBERS = 0x0000000000000004,
// /** Allows all permissions and bypasses channel permission overwrites */
// ADMINISTRATOR = 0x0000000000000008,
// /** Allows management and editing of channels */
// MANAGE_CHANNELS = 0x0000000000000010,
// /** Allows management and editing of the guild */
// MANAGE_GUILD = 0x0000000000000020,
// /** Allows for the addition of reactions to messages */
// ADD_REACTIONS = 0x0000000000000040,
// /** Allows for viewing of audit logs */
// VIEW_AUDIT_LOG = 0x0000000000000080,
// /** Allows for using priority speaker in a voice channel */
// PRIORITY_SPEAKER = 0x0000000000000100,
// /** Allows the user to go live */
// STREAM = 0x0000000000000200,
// /** Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels */
// VIEW_CHANNEL = 0x0000000000000400,
// /** Allows for sending messages in a channel. (does not allow sending messages in threads) */
// SEND_MESSAGES = 0x0000000000000800,
// /** Allows for sending of /tts messages */
// SEND_TTS_MESSAGES = 0x0000000000001000,
// /** Allows for deletion of other users messages */
// MANAGE_MESSAGES = 0x0000000000002000,
// /** Links sent by users with this permission will be auto-embedded */
// EMBED_LINKS = 0x0000000000004000,
// /** Allows for uploading images and files */
// ATTACH_FILES = 0x0000000000008000,
// /** Allows for reading of message history */
// READ_MESSAGE_HISTORY = 0x0000000000010000,
// /** Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel */
// MENTION_EVERYONE = 0x0000000000020000,
// /** Allows the usage of custom emojis from other servers */
// USE_EXTERNAL_EMOJIS = 0x0000000000040000,
// /** Allows for viewing guild insights */
// VIEW_GUILD_INSIGHTS = 0x0000000000080000,
// /** Allows for joining of a voice channel */
// CONNECT = 0x0000000000100000,
// /** Allows for speaking in a voice channel */
// SPEAK = 0x0000000000200000,
// /** Allows for muting members in a voice channel */
// MUTE_MEMBERS = 0x0000000000400000,
// /** Allows for deafening of members in a voice channel */
// DEAFEN_MEMBERS = 0x0000000000800000,
// /** Allows for moving of members between voice channels */
// MOVE_MEMBERS = 0x0000000001000000,
// /** Allows for using voice-activity-detection in a voice channel */
// USE_VAD = 0x0000000002000000,
// /** Allows for modification of own nickname */
// CHANGE_NICKNAME = 0x0000000004000000,
// /** Allows for modification of other users nicknames */
// MANAGE_NICKNAMES = 0x0000000008000000,
// /** Allows management and editing of roles */
// MANAGE_ROLES = 0x0000000010000000,
// /** Allows management and editing of webhooks */
// MANAGE_WEBHOOKS = 0x0000000020000000,
// /** Allows management and editing of emojis and stickers */
// MANAGE_EMOJIS_AND_STICKERS = 0x0000000040000000,
// /** Allows members to use application commands in text channels */
// USE_SLASH_COMMANDS = 0x0000000080000000,
// /** Allows for requesting to speak in stage channels. */
// REQUEST_TO_SPEAK = 0x0000000100000000,
// /** Allows for creating, editing, and deleting scheduled events */
// MANAGE_EVENTS = 0x0000000200000000,
// /** Allows for deleting and archiving threads, and viewing all private threads */
// MANAGE_THREADS = 0x0000000400000000,
// /** Allows for creating public and announcement threads */
// CREATE_PUBLIC_THREADS = 0x0000000800000000,
// /** Allows for creating private threads */
// CREATE_PRIVATE_THREADS = 0x0000001000000000,
// /** Allows the usage of custom stickers from other servers */
// USE_EXTERNAL_STICKERS = 0x0000002000000000,
// /** Allows for sending messages in threads */
// SEND_MESSAGES_IN_THREADS = 0x0000004000000000,
// /** Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. */
// USE_EMBEDDED_ACTIVITIES = 0x0000008000000000,
// /** Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels */
// MODERATE_MEMBERS = 0x0000010000000000,
// }
/** https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags */
export enum BitwisePermissionFlags {
/** Allows creation of instant invites */
CREATE_INSTANT_INVITE = 0x0000000000000001,
/** Allows kicking members */
KICK_MEMBERS = 0x0000000000000002,
/** Allows banning members */
BAN_MEMBERS = 0x0000000000000004,
/** Allows all permissions and bypasses channel permission overwrites */
ADMINISTRATOR = 0x0000000000000008,
/** Allows management and editing of channels */
MANAGE_CHANNELS = 0x0000000000000010,
/** Allows management and editing of the guild */
MANAGE_GUILD = 0x0000000000000020,
/** Allows for the addition of reactions to messages */
ADD_REACTIONS = 0x0000000000000040,
/** Allows for viewing of audit logs */
VIEW_AUDIT_LOG = 0x0000000000000080,
/** Allows for using priority speaker in a voice channel */
PRIORITY_SPEAKER = 0x0000000000000100,
/** Allows the user to go live */
STREAM = 0x0000000000000200,
/** Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels */
VIEW_CHANNEL = 0x0000000000000400,
/** Allows for sending messages in a channel. (does not allow sending messages in threads) */
SEND_MESSAGES = 0x0000000000000800,
/** Allows for sending of /tts messages */
SEND_TTS_MESSAGES = 0x0000000000001000,
/** Allows for deletion of other users messages */
MANAGE_MESSAGES = 0x0000000000002000,
/** Links sent by users with this permission will be auto-embedded */
EMBED_LINKS = 0x0000000000004000,
/** Allows for uploading images and files */
ATTACH_FILES = 0x0000000000008000,
/** Allows for reading of message history */
READ_MESSAGE_HISTORY = 0x0000000000010000,
/** Allows for using the @everyone tag to notify all users in a channel, and the @here tag to notify all online users in a channel */
MENTION_EVERYONE = 0x0000000000020000,
/** Allows the usage of custom emojis from other servers */
USE_EXTERNAL_EMOJIS = 0x0000000000040000,
/** Allows for viewing guild insights */
VIEW_GUILD_INSIGHTS = 0x0000000000080000,
/** Allows for joining of a voice channel */
CONNECT = 0x0000000000100000,
/** Allows for speaking in a voice channel */
SPEAK = 0x0000000000200000,
/** Allows for muting members in a voice channel */
MUTE_MEMBERS = 0x0000000000400000,
/** Allows for deafening of members in a voice channel */
DEAFEN_MEMBERS = 0x0000000000800000,
/** Allows for moving of members between voice channels */
MOVE_MEMBERS = 0x0000000001000000,
/** Allows for using voice-activity-detection in a voice channel */
USE_VAD = 0x0000000002000000,
/** Allows for modification of own nickname */
CHANGE_NICKNAME = 0x0000000004000000,
/** Allows for modification of other users nicknames */
MANAGE_NICKNAMES = 0x0000000008000000,
/** Allows management and editing of roles */
MANAGE_ROLES = 0x0000000010000000,
/** Allows management and editing of webhooks */
MANAGE_WEBHOOKS = 0x0000000020000000,
/** Allows management and editing of emojis and stickers */
MANAGE_EMOJIS_AND_STICKERS = 0x0000000040000000,
/** Allows members to use application commands in text channels */
USE_SLASH_COMMANDS = 0x0000000080000000,
/** Allows for requesting to speak in stage channels. */
REQUEST_TO_SPEAK = 0x0000000100000000,
/** Allows for creating, editing, and deleting scheduled events */
MANAGE_EVENTS = 0x0000000200000000,
/** Allows for deleting and archiving threads, and viewing all private threads */
MANAGE_THREADS = 0x0000000400000000,
/** Allows for creating public and announcement threads */
CREATE_PUBLIC_THREADS = 0x0000000800000000,
/** Allows for creating private threads */
CREATE_PRIVATE_THREADS = 0x0000001000000000,
/** Allows the usage of custom stickers from other servers */
USE_EXTERNAL_STICKERS = 0x0000002000000000,
/** Allows for sending messages in threads */
SEND_MESSAGES_IN_THREADS = 0x0000004000000000,
/** Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel. */
USE_EMBEDDED_ACTIVITIES = 0x0000008000000000,
/** Allows for timing out users to prevent them from sending or reacting to messages in chat and threads, and from speaking in voice and stage channels */
MODERATE_MEMBERS = 0x0000010000000000,
}
// export type PermissionStrings = keyof typeof BitwisePermissionFlags
@@ -1108,152 +1102,152 @@ export type GatewayDispatchEventNames =
export type GatewayEventNames = GatewayDispatchEventNames | 'READY' | 'RESUMED'
// /** https://discord.com/developers/docs/topics/gateway#list-of-intents */
// export enum GatewayIntents {
// /**
// * - GUILD_CREATE
// * - GUILD_DELETE
// * - GUILD_ROLE_CREATE
// * - GUILD_ROLE_UPDATE
// * - GUILD_ROLE_DELETE
// * - CHANNEL_CREATE
// * - CHANNEL_UPDATE
// * - CHANNEL_DELETE
// * - CHANNEL_PINS_UPDATE
// * - THREAD_CREATE
// * - THREAD_UPDATE
// * - THREAD_DELETE
// * - THREAD_LIST_SYNC
// * - THREAD_MEMBER_UPDATE
// * - THREAD_MEMBERS_UPDATE
// * - STAGE_INSTANCE_CREATE
// * - STAGE_INSTANCE_UPDATE
// * - STAGE_INSTANCE_DELETE
// */
// Guilds = 1 << 0,
// /**
// * - GUILD_MEMBER_ADD
// * - GUILD_MEMBER_UPDATE
// * - GUILD_MEMBER_REMOVE
// */
// GuildMembers = 1 << 1,
// /**
// * - GUILD_BAN_ADD
// * - GUILD_BAN_REMOVE
// */
// GuildBans = 1 << 2,
// /**
// * - GUILD_EMOJIS_UPDATE
// */
// GuildEmojis = 1 << 3,
// /**
// * - GUILD_INTEGRATIONS_UPDATE
// * - INTEGRATION_CREATE
// * - INTEGRATION_UPDATE
// * - INTEGRATION_DELETE
// */
// GuildIntegrations = 1 << 4,
// /** Enables the following events:
// * - WEBHOOKS_UPDATE
// */
// GuildWebhooks = 1 << 5,
// /**
// * - INVITE_CREATE
// * - INVITE_DELETE
// */
// GuildInvites = 1 << 6,
// /**
// * - VOICE_STATE_UPDATE
// */
// GuildVoiceStates = 1 << 7,
// /**
// * - PRESENCE_UPDATE
// */
// GuildPresences = 1 << 8,
// /**
// * - MESSAGE_CREATE
// * - MESSAGE_UPDATE
// * - MESSAGE_DELETE
// */
// GuildMessages = 1 << 9,
// /**
// * - MESSAGE_REACTION_ADD
// * - MESSAGE_REACTION_REMOVE
// * - MESSAGE_REACTION_REMOVE_ALL
// * - MESSAGE_REACTION_REMOVE_EMOJI
// */
// GuildMessageReactions = 1 << 10,
// /**
// * - TYPING_START
// */
// GuildMessageTyping = 1 << 11,
// /**
// * - CHANNEL_CREATE
// * - MESSAGE_CREATE
// * - MESSAGE_UPDATE
// * - MESSAGE_DELETE
// * - CHANNEL_PINS_UPDATE
// */
// DirectMessages = 1 << 12,
// /**
// * - MESSAGE_REACTION_ADD
// * - MESSAGE_REACTION_REMOVE
// * - MESSAGE_REACTION_REMOVE_ALL
// * - MESSAGE_REACTION_REMOVE_EMOJI
// */
// DirectMessageReactions = 1 << 13,
// /**
// * - TYPING_START
// */
// DirectMessageTyping = 1 << 14,
/** https://discord.com/developers/docs/topics/gateway#list-of-intents */
export enum GatewayIntents {
/**
* - GUILD_CREATE
* - GUILD_DELETE
* - GUILD_ROLE_CREATE
* - GUILD_ROLE_UPDATE
* - GUILD_ROLE_DELETE
* - CHANNEL_CREATE
* - CHANNEL_UPDATE
* - CHANNEL_DELETE
* - CHANNEL_PINS_UPDATE
* - THREAD_CREATE
* - THREAD_UPDATE
* - THREAD_DELETE
* - THREAD_LIST_SYNC
* - THREAD_MEMBER_UPDATE
* - THREAD_MEMBERS_UPDATE
* - STAGE_INSTANCE_CREATE
* - STAGE_INSTANCE_UPDATE
* - STAGE_INSTANCE_DELETE
*/
Guilds = 1 << 0,
/**
* - GUILD_MEMBER_ADD
* - GUILD_MEMBER_UPDATE
* - GUILD_MEMBER_REMOVE
*/
GuildMembers = 1 << 1,
/**
* - GUILD_BAN_ADD
* - GUILD_BAN_REMOVE
*/
GuildBans = 1 << 2,
/**
* - GUILD_EMOJIS_UPDATE
*/
GuildEmojis = 1 << 3,
/**
* - GUILD_INTEGRATIONS_UPDATE
* - INTEGRATION_CREATE
* - INTEGRATION_UPDATE
* - INTEGRATION_DELETE
*/
GuildIntegrations = 1 << 4,
/** Enables the following events:
* - WEBHOOKS_UPDATE
*/
GuildWebhooks = 1 << 5,
/**
* - INVITE_CREATE
* - INVITE_DELETE
*/
GuildInvites = 1 << 6,
/**
* - VOICE_STATE_UPDATE
*/
GuildVoiceStates = 1 << 7,
/**
* - PRESENCE_UPDATE
*/
GuildPresences = 1 << 8,
/**
* - MESSAGE_CREATE
* - MESSAGE_UPDATE
* - MESSAGE_DELETE
*/
GuildMessages = 1 << 9,
/**
* - MESSAGE_REACTION_ADD
* - MESSAGE_REACTION_REMOVE
* - MESSAGE_REACTION_REMOVE_ALL
* - MESSAGE_REACTION_REMOVE_EMOJI
*/
GuildMessageReactions = 1 << 10,
/**
* - TYPING_START
*/
GuildMessageTyping = 1 << 11,
/**
* - CHANNEL_CREATE
* - MESSAGE_CREATE
* - MESSAGE_UPDATE
* - MESSAGE_DELETE
* - CHANNEL_PINS_UPDATE
*/
DirectMessages = 1 << 12,
/**
* - MESSAGE_REACTION_ADD
* - MESSAGE_REACTION_REMOVE
* - MESSAGE_REACTION_REMOVE_ALL
* - MESSAGE_REACTION_REMOVE_EMOJI
*/
DirectMessageReactions = 1 << 13,
/**
* - TYPING_START
*/
DirectMessageTyping = 1 << 14,
// /**
// * This intent will add `content` values to all message objects.
// */
// MessageContent = 1 << 15,
// /**
// * - GUILD_SCHEDULED_EVENT_CREATE
// * - GUILD_SCHEDULED_EVENT_UPDATE
// * - GUILD_SCHEDULED_EVENT_DELETE
// * - GUILD_SCHEDULED_EVENT_USER_ADD this is experimental and unstable.
// * - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable.
// */
// GuildScheduledEvents = 1 << 16,
/**
* This intent will add `content` values to all message objects.
*/
MessageContent = 1 << 15,
/**
* - GUILD_SCHEDULED_EVENT_CREATE
* - GUILD_SCHEDULED_EVENT_UPDATE
* - GUILD_SCHEDULED_EVENT_DELETE
* - GUILD_SCHEDULED_EVENT_USER_ADD this is experimental and unstable.
* - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable.
*/
GuildScheduledEvents = 1 << 16,
// /**
// * - AUTO_MODERATION_RULE_CREATE
// * - AUTO_MODERATION_RULE_UPDATE
// * - AUTO_MODERATION_RULE_DELETE
// */
// AutoModerationConfiguration = 1 << 20,
// /**
// * - AUTO_MODERATION_ACTION_EXECUTION
// */
// AutoModerationExecution = 1 << 21,
// }
/**
* - AUTO_MODERATION_RULE_CREATE
* - AUTO_MODERATION_RULE_UPDATE
* - AUTO_MODERATION_RULE_DELETE
*/
AutoModerationConfiguration = 1 << 20,
/**
* - AUTO_MODERATION_ACTION_EXECUTION
*/
AutoModerationExecution = 1 << 21,
}
// // ALIASES JUST FOR BETTER UX IN THIS CASE
// ALIASES JUST FOR BETTER UX IN THIS CASE
// /** https://discord.com/developers/docs/topics/gateway#list-of-intents */
// export const Intents = GatewayIntents
/** https://discord.com/developers/docs/topics/gateway#list-of-intents */
export const Intents = GatewayIntents
// /** https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionresponsetype */
// export enum InteractionResponseTypes {
// /** ACK a `Ping` */
// Pong = 1,
// /** Respond to an interaction with a message */
// ChannelMessageWithSource = 4,
// /** ACK an interaction and edit a response later, the user sees a loading state */
// DeferredChannelMessageWithSource = 5,
// /** For components, ACK an interaction and edit the original message later; the user does not see a loading state */
// DeferredUpdateMessage = 6,
// /** For components, edit the message the component was attached to */
// UpdateMessage = 7,
// /** For Application Command Options, send an autocomplete result */
// ApplicationCommandAutocompleteResult = 8,
// /** For Command or Component interactions, send a Modal response */
// Modal = 9,
// }
/** https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionresponsetype */
export enum InteractionResponseTypes {
/** ACK a `Ping` */
Pong = 1,
/** Respond to an interaction with a message */
ChannelMessageWithSource = 4,
/** ACK an interaction and edit a response later, the user sees a loading state */
DeferredChannelMessageWithSource = 5,
/** For components, ACK an interaction and edit the original message later; the user does not see a loading state */
DeferredUpdateMessage = 6,
/** For components, edit the message the component was attached to */
UpdateMessage = 7,
/** For Application Command Options, send an autocomplete result */
ApplicationCommandAutocompleteResult = 8,
/** For Command or Component interactions, send a Modal response */
Modal = 9,
}
export enum SortOrderTypes {
/** Sort forum posts by activity */
@@ -1443,8 +1437,8 @@ export type Camelize<T> = T extends any[]
? Array<Camelize<T[number]>>
: T
: T extends Record<any, any>
? { [K in keyof T as CamelCase<K & string>]: Camelize<T[K]> }
: T
? { [K in keyof T as CamelCase<K & string>]: Camelize<T[K]> }
: T
// /** Non object primitives */
// export type Primitive =

View File

@@ -0,0 +1,19 @@
export function iconHashToBigInt (hash: string): bigint {
// The icon is animated so it needs special handling
if (hash.startsWith('a_')) {
// Change the `a_` to just be `a`
hash = `a${hash.substring(2)}`
} else {
// The icon is not animated but it could be that it starts with a 0 so we just put a `b` in front so nothing breaks
hash = `b${hash}`
}
return BigInt(`0x${hash}`)
}
export function iconBigintToHash (icon: bigint): string {
// Convert the bigint back to a hash
const hash = icon.toString(16)
// Hashes starting with a are animated and with b are not so need to handle that
return hash.startsWith('a') ? `a_${hash.substring(1)}` : hash.substring(1)
}

View File

@@ -0,0 +1,23 @@
import { Buffer } from 'node:buffer'
/** Removes the Bot before the token. */
export function removeTokenPrefix (
token?: string,
type: 'GATEWAY' | 'REST' = 'REST'
): string {
// If no token is provided, throw an error
if (token === undefined) {
throw new Error(
`The ${type} was not given a token. Please provide a token and try again.`
)
}
// If the token does not have a prefix just return token
if (!token.startsWith('Bot ')) return token
// Remove the prefix and return only the token.
return token.substring(token.indexOf(' ') + 1)
}
/** Get the bot id from the bot token. WARNING: Discord staff has mentioned this may not be stable forever. Use at your own risk. However, note for over 5 years this has never broken. */
export function getBotIdFromToken (token: string): bigint {
return BigInt(Buffer.from(token.split('.')[0], 'base64').toString())
}

View File

@@ -21,6 +21,31 @@ __metadata:
languageName: node
linkType: hard
"@discordeno/client@workspace:packages/client":
version: 0.0.0-use.local
resolution: "@discordeno/client@workspace:packages/client"
dependencies:
"@discordeno/gateway": 18.0.0-alpha.1
"@discordeno/rest": 18.0.0-alpha.1
"@discordeno/types": 18.0.0-alpha.1
"@discordeno/utils": 18.0.0-alpha.1
"@swc/cli": ^0.1.57
"@swc/core": ^1.3.21
"@types/chai": ^4
"@types/mocha": ^10
"@types/node": ^18.11.9
"@types/sinon": ^10.0.13
c8: ^7.12.0
chai: ^4.3.7
eslint: ^8.0.1
eslint-config-discordeno: "*"
mocha: ^10.1.0
sinon: ^15.0.0
tsconfig: "*"
typescript: ^4.9.3
languageName: unknown
linkType: soft
"@discordeno/gateway@18.0.0-alpha.1, @discordeno/gateway@workspace:packages/gateway":
version: 0.0.0-use.local
resolution: "@discordeno/gateway@workspace:packages/gateway"