mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 00:40:07 +00:00
Merge branch 'main' into more-thread-stuff
This commit is contained in:
@@ -30,7 +30,7 @@ export const cache = {
|
||||
executedSlashCommands: new Set<string>(),
|
||||
get emojis() {
|
||||
return new Collection<bigint, Emoji>(
|
||||
this.guilds.reduce((a, b) => [...a, ...b.emojis.map((e) => [e.id, e])], [] as any[])
|
||||
this.guilds.reduce((a, b) => [...a, ...b.emojis.map((e, id) => [id, e])], [] as any[])
|
||||
);
|
||||
},
|
||||
activeGuildIds: new Set<bigint>(),
|
||||
|
||||
@@ -6,7 +6,8 @@ import { snowflakeToBigint } from "../../util/bigint.ts";
|
||||
|
||||
export async function handleThreadMemberUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = data.d as ThreadMember;
|
||||
const thread = await cacheHandlers.get("threads", snowflakeToBigint(payload.id));
|
||||
// The id field is omitted from the thread member dispatched within the GUILD_CREATE gateway event.
|
||||
const thread = await cacheHandlers.get("threads", snowflakeToBigint(payload.id!));
|
||||
if (!thread) return;
|
||||
|
||||
thread.botIsMember = true;
|
||||
|
||||
@@ -5,13 +5,18 @@ import type { DiscordenoInteractionResponse } from "../../types/discordeno/inter
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { snakelize, validateComponents } from "../../util/utils.ts";
|
||||
|
||||
// TODO: v12 remove | string
|
||||
/**
|
||||
* Send a response to a users slash command. The command data will have the id and token necessary to respond.
|
||||
* Interaction `tokens` are valid for **15 minutes** and can be used to send followup messages.
|
||||
*
|
||||
* NOTE: By default we will suppress mentions. To enable mentions, just pass any mentions object.
|
||||
*/
|
||||
export async function sendInteractionResponse(id: bigint, token: string, options: DiscordenoInteractionResponse) {
|
||||
export async function sendInteractionResponse(
|
||||
id: bigint | string,
|
||||
token: string,
|
||||
options: DiscordenoInteractionResponse
|
||||
) {
|
||||
// TODO: add more options validations
|
||||
if (options.data?.components) validateComponents(options.data?.components);
|
||||
|
||||
@@ -37,5 +42,9 @@ export async function sendInteractionResponse(id: bigint, token: string, options
|
||||
cache.executedSlashCommands.delete(token);
|
||||
}, 900000);
|
||||
|
||||
return await rest.runMethod("post", endpoints.INTERACTION_ID_TOKEN(id, token), snakelize(options));
|
||||
return await rest.runMethod(
|
||||
"post",
|
||||
endpoints.INTERACTION_ID_TOKEN(typeof id === "bigint" ? id : BigInt(id), token),
|
||||
snakelize(options)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ export async function editMessage(channelId: bigint, messageId: bigint, content:
|
||||
validateComponents(content.components);
|
||||
}
|
||||
|
||||
// TODO: v12 remove
|
||||
if (content.embed) {
|
||||
content.embeds = [content.embed, ...(content.embeds || [])];
|
||||
content.embed = undefined;
|
||||
}
|
||||
content.embeds?.splice(10);
|
||||
|
||||
if (content.content && content.content.length > 2000) {
|
||||
throw new Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,16 @@ export async function sendMessage(channelId: bigint, content: string | CreateMes
|
||||
const requiredPerms: Set<PermissionStrings> = new Set(["SEND_MESSAGES", "VIEW_CHANNEL"]);
|
||||
|
||||
if (content.tts) requiredPerms.add("SEND_TTS_MESSAGES");
|
||||
if (content.embed) requiredPerms.add("EMBED_LINKS");
|
||||
// TODO: v12 remove
|
||||
if (content.embed) {
|
||||
content.embeds = [content.embed, ...(content.embeds || [])];
|
||||
content.embed = undefined;
|
||||
}
|
||||
if (content.embeds?.length) {
|
||||
requiredPerms.add("EMBED_LINKS");
|
||||
content.embeds?.splice(10);
|
||||
}
|
||||
|
||||
if (content.messageReference?.messageId || content.allowedMentions?.repliedUser) {
|
||||
requiredPerms.add("READ_MESSAGE_HISTORY");
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { urlToBase64 } from "../../util/utils.ts";
|
||||
/** Modifies the bot's username or avatar.
|
||||
* NOTE: username: if changed may cause the bot's discriminator to be randomized.
|
||||
*/
|
||||
export async function editBotProfile(options: { username?: string; botAvatarURL?: string }) {
|
||||
export async function editBotProfile(options: { username?: string; botAvatarURL?: string | null }) {
|
||||
// Nothing was edited
|
||||
if (!options.username && !options.botAvatarURL) return;
|
||||
if (!options.username && options.botAvatarURL === undefined) return;
|
||||
// Check username requirements if username was provided
|
||||
if (options.username) {
|
||||
if (options.username.length > 32) {
|
||||
@@ -26,7 +26,7 @@ export async function editBotProfile(options: { username?: string; botAvatarURL?
|
||||
}
|
||||
}
|
||||
|
||||
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : undefined;
|
||||
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL;
|
||||
|
||||
return await rest.runMethod<User>("patch", endpoints.USER_BOT, {
|
||||
username: options.username?.trim(),
|
||||
|
||||
@@ -133,6 +133,8 @@ import { createStageInstance } from "./channels/create_stage_instance.ts";
|
||||
import { updateStageInstance } from "./channels/update_stage_instance.ts";
|
||||
import { getStageInstance } from "./channels/get_stage_instance.ts";
|
||||
import { deleteStageInstance } from "./channels/delete_stage_instance.ts";
|
||||
import { isSlashCommand } from "./type_guards/is_slash_command.ts";
|
||||
import { connectToVoiceChannel } from "./voice/connect_to_voice_channel.ts";
|
||||
|
||||
export {
|
||||
addDiscoverySubcategory,
|
||||
@@ -145,6 +147,7 @@ export {
|
||||
batchEditSlashCommandPermissions,
|
||||
categoryChildren,
|
||||
channelOverwriteHasPermission,
|
||||
connectToVoiceChannel,
|
||||
createChannel,
|
||||
createEmoji,
|
||||
createGuild,
|
||||
@@ -242,6 +245,7 @@ export {
|
||||
guildSplashURL,
|
||||
isButton,
|
||||
isSelectMenu,
|
||||
isSlashCommand,
|
||||
isChannelSynced,
|
||||
kick,
|
||||
kickMember,
|
||||
@@ -405,6 +409,8 @@ export let helpers = {
|
||||
getGuildTemplates,
|
||||
getTemplate,
|
||||
syncGuildTemplate,
|
||||
// voice
|
||||
connectToVoiceChannel,
|
||||
// webhooks
|
||||
createWebhook,
|
||||
deleteWebhookMessage,
|
||||
|
||||
7
src/helpers/type_guards/is_slash_command.ts
Normal file
7
src/helpers/type_guards/is_slash_command.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Interaction, SlashCommandInteraction } from "../../types/interactions/interaction.ts";
|
||||
import { DiscordInteractionTypes } from "../../types/interactions/interaction_types.ts";
|
||||
|
||||
/** A type guard function to tell if it is a slash command interaction */
|
||||
export function isSlashCommand(interaction: Interaction): interaction is SlashCommandInteraction {
|
||||
return interaction.type === DiscordInteractionTypes.ApplicationCommand;
|
||||
}
|
||||
21
src/helpers/voice/connect_to_voice_channel.ts
Normal file
21
src/helpers/voice/connect_to_voice_channel.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DiscordGatewayOpcodes } from "../../types/codes/gateway_opcodes.ts";
|
||||
import type { UpdateVoiceState } from "../../types/voice/update_voice_state.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
import { calculateShardId } from "../../util/calculate_shard_id.ts";
|
||||
import { snakelize } from "../../util/utils.ts";
|
||||
import { ws } from "../../ws/ws.ts";
|
||||
import type { AtLeastOne } from "../../types/util.ts";
|
||||
|
||||
/** Connect or join a voice channel inside a guild. By default, the "selfDeaf" option is true. Requires `CONNECT` and `VIEW_CHANNEL` permissions. */
|
||||
export async function connectToVoiceChannel(
|
||||
guildId: bigint,
|
||||
channelId: bigint,
|
||||
options?: AtLeastOne<Omit<UpdateVoiceState, "guildId" | "channelId">>
|
||||
) {
|
||||
await requireBotChannelPermissions(channelId, ["CONNECT", "VIEW_CHANNEL"]);
|
||||
|
||||
ws.sendShardMessage(calculateShardId(guildId), {
|
||||
op: DiscordGatewayOpcodes.VoiceStateUpdate,
|
||||
d: snakelize<UpdateVoiceState>({guildId, channelId, selfMute: Boolean(options?.selfMute), selfDeaf: options.selfDeaf ?? true }),
|
||||
});
|
||||
}
|
||||
@@ -17,9 +17,7 @@ export async function sendWebhook(webhookId: bigint, webhookToken: string, optio
|
||||
throw Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
}
|
||||
|
||||
if (options.embeds && options.embeds.length > 10) {
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
options.embeds?.splice(10);
|
||||
|
||||
if (options.allowedMentions) {
|
||||
if (options.allowedMentions.users?.length) {
|
||||
|
||||
@@ -46,7 +46,7 @@ const baseMember: Partial<DiscordenoMember> = {
|
||||
return `<@!${this.id!}>`;
|
||||
},
|
||||
get tag() {
|
||||
return `${this.username!}#${this.discriminator!}`;
|
||||
return `${this.username!}#${this.discriminator!.toString().padStart(4, "0")}`;
|
||||
},
|
||||
|
||||
// METHODS
|
||||
@@ -232,7 +232,9 @@ export interface DiscordenoMember extends Omit<User, "discriminator" | "id" | "a
|
||||
/** Get the nickname or the username if no nickname */
|
||||
name(guildId: bigint): string;
|
||||
/** Get the guild member object for the specified guild */
|
||||
guildMember(guildId: bigint):
|
||||
guildMember(
|
||||
guildId: bigint
|
||||
):
|
||||
| (Omit<GuildMember, "joinedAt" | "premiumSince" | "roles"> & {
|
||||
joinedAt?: number;
|
||||
premiumSince?: number;
|
||||
|
||||
@@ -228,7 +228,7 @@ export async function createDiscordenoMessage(data: Message) {
|
||||
|
||||
props.authorId = createNewProp(snowflakeToBigint(author.id));
|
||||
props.isBot = createNewProp(author.bot || false);
|
||||
props.tag = createNewProp(`${author.username}#${author.discriminator}`);
|
||||
props.tag = createNewProp(`${author.username}#${author.discriminator.toString().padStart(4, "0")}`);
|
||||
|
||||
// Discord doesnt give guild id for getMessage() so this will fill it in
|
||||
const guildIdFinal =
|
||||
|
||||
@@ -56,6 +56,6 @@ export interface Channel {
|
||||
threadMetadata?: ThreadMetadata;
|
||||
/** Thread member object for the current user, if they have joined the thread, only included on certain API endpoints */
|
||||
member?: ThreadMember;
|
||||
/** the default duration for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity */
|
||||
defaultAutoArchiveDuration: number;
|
||||
/** 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;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ export interface ThreadMemberBase {
|
||||
|
||||
export interface ThreadMember extends ThreadMemberBase {
|
||||
/** The id of the thread */
|
||||
id: string;
|
||||
id?: string;
|
||||
/** The id of the user */
|
||||
userId: string;
|
||||
userId?: string;
|
||||
/** The time the current user last joined the thread */
|
||||
joinTimestamp: string;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ export enum DiscordJsonErrorCodes {
|
||||
MaximumNumberOfGuildChannelsReached = 30013,
|
||||
MaximumNumberOfAttachmentsInAMessageReached = 30015,
|
||||
MaximumNumberOfInvitesReached,
|
||||
MaximumNumberOfAnimatedEmojisReached = 30018,
|
||||
MaximumNumberOfServerMembersReached,
|
||||
MaximumNumberOfGuildDiscoverySubcategoriesHasBeenReached = 30030,
|
||||
GuildAlreadyHasTemplate = 30031,
|
||||
MaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035,
|
||||
@@ -106,6 +108,10 @@ export enum DiscordJsonErrorCodes {
|
||||
NoUsersWithDiscordTagExist = 80004,
|
||||
ReqctionWasBlocked = 90001,
|
||||
ApiResourceIsCurrentlyOverloadedTryAgainALittleLater = 130000,
|
||||
AThreadHasAlreadyBeenCreatedForThisMessage = 160004,
|
||||
ThreadIsLocked = 160005,
|
||||
MaximumNumberOfActiveThreadsReached = 160006,
|
||||
MaximumNumberOfActiveAnnouncementThreadsReached = 160007,
|
||||
}
|
||||
|
||||
export type JsonErrrorCodes = DiscordJsonErrorCodes;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Guild } from "../guilds/guild.ts";
|
||||
import { Application } from "../applications/application.ts";
|
||||
import { User } from "../users/user.ts";
|
||||
import { DiscordTargetTypes } from "./target_types.ts";
|
||||
import { InviteStageInstance } from "./invite_stage_instance.ts";
|
||||
|
||||
/** https://discord.com/developers/docs/resources/invite#invite-object */
|
||||
export interface Invite {
|
||||
@@ -26,4 +27,6 @@ export interface Invite {
|
||||
approximateMemberCount?: number;
|
||||
/** The expiration date of this invite, returned from the `GET /invites/<code>` endpoint when `with_expiration` is `true` */
|
||||
expiresAt?: string | null;
|
||||
/** Stage instance data if there is a public Stage instance in the Stage channel this invite is for */
|
||||
stageInstance?: InviteStageInstance;
|
||||
}
|
||||
|
||||
12
src/types/invites/invite_stage_instance.ts
Normal file
12
src/types/invites/invite_stage_instance.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { GuildMember } from "../members/guild_member.ts";
|
||||
|
||||
export interface InviteStageInstance {
|
||||
/** The members speaking in the Stage */
|
||||
members: Partial<GuildMember>[];
|
||||
/** The number of users in the Stage */
|
||||
participantCount: number;
|
||||
/** The number of users speaking in the Stage */
|
||||
speakerCount: number;
|
||||
/** The topic of the Stage instance (1-120 characters) */
|
||||
topic: string;
|
||||
}
|
||||
@@ -10,8 +10,13 @@ export interface CreateMessage {
|
||||
content?: string;
|
||||
/** true if this is a TTS message */
|
||||
tts?: boolean;
|
||||
/** Embedded `rich` content */
|
||||
// TODO: v12 remove
|
||||
/** Embedded `rich` content
|
||||
* @deprecated will be removed in Discordeno v12 use embeds
|
||||
*/
|
||||
embed?: Embed;
|
||||
/** Embedded `rich` content (up to 6000 characters) */
|
||||
embeds?: Embed[];
|
||||
/** Allowed mentions for the message */
|
||||
allowedMentions?: AllowedMentions;
|
||||
/** Include to make your message a reply */
|
||||
|
||||
@@ -8,8 +8,13 @@ import { MessageComponents } from "./components/message_components.ts";
|
||||
export interface EditMessage {
|
||||
/** The new message contents (up to 2000 characters) */
|
||||
content?: string | null;
|
||||
/** Embedded `rich` content */
|
||||
// TODO: v12 remove
|
||||
/** Embedded `rich` content
|
||||
* @deprecated will be removed in Discordeno v12 use embeds
|
||||
*/
|
||||
embed?: Embed | null;
|
||||
/** Embedded `rich` content (up to 6000 characters) */
|
||||
embeds?: Embed[] | null;
|
||||
/** Edit the flags of the message (only `SUPRESS_EMBEDS` can currently be set/unset) */
|
||||
flags?: 4 | null;
|
||||
/** The contents of the file being sent/edited */
|
||||
|
||||
@@ -78,7 +78,10 @@ export interface Message {
|
||||
messageReference?: Omit<MessageReference, "failIfNotExists">;
|
||||
/** Message flags combined as a bitfield */
|
||||
flags?: number;
|
||||
/** The stickers sent with the message (bots currently can only receive messages with stickers, not send) */
|
||||
/**
|
||||
* The stickers sent with the message (bots currently can only receive messages with stickers, not send)
|
||||
* @deprecated
|
||||
*/
|
||||
stickers?: MessageSticker[];
|
||||
/**
|
||||
* The message associated with the `message_reference`
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import { DiscordMessageStickerFormatTypes } from "./message_sticker_format_types.ts";
|
||||
import type { User } from "../users/user.ts";
|
||||
|
||||
/** https://discord.com/developers/docs/resources/channel#message-object-message-sticker-structure */
|
||||
export interface MessageSticker {
|
||||
/** id of the sticker */
|
||||
/** Id of the sticker */
|
||||
id: string;
|
||||
/** id of the pack the sticker is from */
|
||||
packId: string;
|
||||
/** Id of the pack the sticker is from */
|
||||
packId?: string;
|
||||
/** Name of the sticker */
|
||||
name: string;
|
||||
/** Description of the sticker */
|
||||
description: string;
|
||||
/** A comma-separated list of tags for the sticker */
|
||||
tags?: string;
|
||||
/** For guild stickers, a unicode emoji representing the sticker's expression. For Nitro stickers, a comma-separated list of related expressions */
|
||||
tags: string;
|
||||
/**
|
||||
* Sticker asset hash
|
||||
* Note: The URL for fetching sticker assets is currently private.
|
||||
* @deprecated the value of the asset field will an empty string.
|
||||
*/
|
||||
asset: string;
|
||||
/** Type of sticker format */
|
||||
formatType: DiscordMessageStickerFormatTypes;
|
||||
/** Whether or not the sticker is available */
|
||||
available?: boolean;
|
||||
/** Id of the guild that owns this sticker */
|
||||
guildId?: string;
|
||||
/** The user that uploaded the sticker */
|
||||
user?: User;
|
||||
/** A sticker's sort order within a pack */
|
||||
sortValue?: number;
|
||||
}
|
||||
|
||||
@@ -149,3 +149,5 @@ export type CamelCasedPropertiesDeep<Value> = Value extends Function
|
||||
: {
|
||||
[K in keyof Value as CamelCase<K>]: CamelCasedPropertiesDeep<Value[K]>;
|
||||
};
|
||||
|
||||
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||
|
||||
7
src/util/calculate_shard_id.ts
Normal file
7
src/util/calculate_shard_id.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ws } from "../ws/ws.ts";
|
||||
|
||||
export function calculateShardId(guildId: bigint) {
|
||||
if (ws.maxShards === 1) return 0;
|
||||
|
||||
return Number((guildId >> 22n) % BigInt(ws.maxShards - 1));
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export const GATEWAY_VERSION = 9;
|
||||
|
||||
// TODO: update this version
|
||||
/** https://github.com/discordeno/discordeno/releases */
|
||||
export const DISCORDENO_VERSION = "11.0.3";
|
||||
export const DISCORDENO_VERSION = "11.2.0";
|
||||
|
||||
/** https://discord.com/developers/docs/reference#user-agent */
|
||||
export const USER_AGENT = `DiscordBot (https://github.com/discordeno/discordeno, v${DISCORDENO_VERSION})`;
|
||||
|
||||
Reference in New Issue
Block a user