mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-04 01:40:08 +00:00
types: fix new types issues (#829)
* feat: add tests for deleting channel overwrites * fix: typings
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { getGatewayBot } from "./helpers/misc/get_gateway_bot.ts";
|
||||
import { rest } from "./rest/rest.ts";
|
||||
import { EventHandlers } from "./types/discordeno/eventHandlers.ts";
|
||||
import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts";
|
||||
import { DiscordGetGatewayBot } from "./types/gateway/get_gateway_bot.ts";
|
||||
import { GetGatewayBot } from "./types/gateway/get_gateway_bot.ts";
|
||||
import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts";
|
||||
import { ws } from "./ws/ws.ts";
|
||||
|
||||
@@ -12,7 +13,7 @@ export let applicationId = "";
|
||||
|
||||
export let eventHandlers: EventHandlers = {};
|
||||
|
||||
export let botGatewayData: DiscordGetGatewayBot;
|
||||
export let botGatewayData: GetGatewayBot;
|
||||
export let proxyWSURL = `wss://gateway.discord.gg`;
|
||||
|
||||
export const identifyPayload = {
|
||||
|
||||
@@ -15,5 +15,5 @@ export async function handleMessageReactionRemoveAll(
|
||||
await cacheHandlers.set("messages", payload.message_id, message);
|
||||
}
|
||||
|
||||
eventHandlers.reactionRemoveAll?.(data.d);
|
||||
eventHandlers.reactionRemoveAll?.(payload);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordUser } from "../../types/users/user.ts";
|
||||
import { DiscordUser, User } from "../../types/users/user.ts";
|
||||
import { camelKeysToSnakeCase } from "../../util/utils.ts";
|
||||
|
||||
export async function handleUserUpdate(data: DiscordGatewayPayload) {
|
||||
const userData = data.d as DiscordUser;
|
||||
const userData = camelKeysToSnakeCase(data.d as DiscordUser) as User;
|
||||
|
||||
const member = await cacheHandlers.get("members", userData.id);
|
||||
if (!member) return;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordVoiceServerUpdate, VoiceServerUpdate } from "../../types/voice/voice_server_update.ts";
|
||||
import { snakeKeysToCamelCase } from "../../util/utils.ts";
|
||||
|
||||
export async function handleVoiceServerUpdate(data: DiscordPayload) {
|
||||
const payload = data.d as DiscordVoiceServerUpdate;
|
||||
export async function handleVoiceServerUpdate(data: DiscordGatewayPayload) {
|
||||
const payload = snakeKeysToCamelCase(data.d as DiscordVoiceServerUpdate) as VoiceServerUpdate;
|
||||
|
||||
const guild = await cacheHandlers.get("guilds", payload.guild_id);
|
||||
const guild = await cacheHandlers.get("guilds", payload.guildId);
|
||||
if (!guild) return;
|
||||
|
||||
eventHandlers.voiceServerUpdate?.(payload.token, guild, payload.endpoint);
|
||||
eventHandlers.voiceServerUpdate?.(payload, guild);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { eventHandlers } from "../../bot.ts";
|
||||
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import { DiscordWebhooksUpdate } from "../../types/webhooks/webhooks_update.ts";
|
||||
import { DiscordWebhookUpdate } from "../../types/webhooks/webhooks_update.ts";
|
||||
|
||||
export function handleWebhooksUpdate(data: DiscordGatewayPayload) {
|
||||
const options = data.d as DiscordWebhooksUpdate;
|
||||
const options = data.d as DiscordWebhookUpdate;
|
||||
eventHandlers.webhooksUpdate?.(
|
||||
options.channel_id,
|
||||
options.guild_id,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGuildMember } from "../../types/guilds/guild_member.ts";
|
||||
import { Errors } from "../../types/misc/errors.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import {
|
||||
@@ -69,7 +70,7 @@ export async function editMember(
|
||||
"patch",
|
||||
endpoints.GUILD_MEMBER(guildId, memberId),
|
||||
options,
|
||||
) as MemberCreatePayload;
|
||||
) as DiscordGuildMember;
|
||||
const member = await structures.createDiscordenoMember(result, guildId);
|
||||
|
||||
return member;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordGuildMember } from "../../types/guilds/guild_member.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Returns a guild member object for the specified user.
|
||||
@@ -18,7 +19,7 @@ export async function getMember(
|
||||
const data = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.GUILD_MEMBER(guildId, id),
|
||||
)) as MemberCreatePayload;
|
||||
)) as DiscordGuildMember;
|
||||
|
||||
const discordenoMember = await structures.createDiscordenoMember(
|
||||
data,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { Member } from "../../structures/mod.ts";
|
||||
import { DiscordenoMember } from "../../structures/member.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
|
||||
/** Returns guild member objects for the specified user by their nickname/username.
|
||||
@@ -19,5 +19,5 @@ export async function getMembersByQuery(
|
||||
query: name,
|
||||
limit,
|
||||
});
|
||||
}) as Promise<Collection<string, Member>>;
|
||||
}) as Promise<Collection<string, DiscordenoMember>>;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
/** Edit the message. */
|
||||
export async function editMessage(
|
||||
message: Message,
|
||||
content: string | MessageContent,
|
||||
content: string | MessageContent
|
||||
) {
|
||||
if (message.author.id !== botId) {
|
||||
throw "You can only edit a message that was sent by the bot.";
|
||||
@@ -30,8 +30,8 @@ export async function editMessage(
|
||||
const result = await rest.runMethod(
|
||||
"patch",
|
||||
endpoints.CHANNEL_MESSAGE(message.channelId, message.id),
|
||||
content,
|
||||
content
|
||||
);
|
||||
|
||||
return structures.createDiscordenoMessage(result as MessageCreateOptions);
|
||||
return structures.createDiscordenoMessage(result as DiscordMessage);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cacheHandlers } from "../../cache.ts";
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
|
||||
@@ -15,8 +16,8 @@ export async function getMessage(channelId: string, id: string) {
|
||||
|
||||
const result = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.CHANNEL_MESSAGE(channelId, id),
|
||||
)) as MessageCreateOptions;
|
||||
endpoints.CHANNEL_MESSAGE(channelId, id)
|
||||
)) as DiscordMessage;
|
||||
|
||||
return structures.createDiscordenoMessage(result);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotChannelPermissions } from "../../util/permissions.ts";
|
||||
|
||||
@@ -10,7 +11,7 @@ export async function getMessages(
|
||||
| GetMessagesAfter
|
||||
| GetMessagesBefore
|
||||
| GetMessagesAround
|
||||
| GetMessages,
|
||||
| GetMessages
|
||||
) {
|
||||
await requireBotChannelPermissions(channelId, [
|
||||
"VIEW_CHANNEL",
|
||||
@@ -22,10 +23,10 @@ export async function getMessages(
|
||||
const result = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.CHANNEL_MESSAGES(channelId),
|
||||
options,
|
||||
)) as MessageCreateOptions[];
|
||||
options
|
||||
)) as DiscordMessage[];
|
||||
|
||||
return Promise.all(
|
||||
result.map((res) => structures.createDiscordenoMessage(res)),
|
||||
result.map((res) => structures.createDiscordenoMessage(res))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Crosspost a message in a News Channel to following channels. */
|
||||
export async function publishMessage(channelId: string, messageId: string) {
|
||||
const data = (await rest.runMethod(
|
||||
"post",
|
||||
endpoints.CHANNEL_MESSAGE_CROSSPOST(channelId, messageId),
|
||||
)) as MessageCreateOptions;
|
||||
endpoints.CHANNEL_MESSAGE_CROSSPOST(channelId, messageId)
|
||||
)) as DiscordMessage;
|
||||
|
||||
return structures.createDiscordenoMessage(data);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordChannelTypes } from "../../types/channels/channel_types.ts";
|
||||
import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts";
|
||||
import { CreateMessage } from "../../types/messages/create_message.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { Errors } from "../../types/misc/errors.ts";
|
||||
import { PermissionStrings } from "../../types/permissions/permission_strings.ts";
|
||||
@@ -13,7 +14,7 @@ import { camelKeysToSnakeCase } from "../../util/utils.ts";
|
||||
/** Send a message to the channel. Requires SEND_MESSAGES permission. */
|
||||
export async function sendMessage(
|
||||
channelId: string,
|
||||
content: string | DiscordenoCreateMessage,
|
||||
content: string | CreateMessage
|
||||
) {
|
||||
if (typeof content === "string") content = { content };
|
||||
|
||||
@@ -55,18 +56,18 @@ export async function sendMessage(
|
||||
if (content.allowedMentions.users?.length) {
|
||||
if (
|
||||
content.allowedMentions.parse?.includes(
|
||||
DiscordAllowedMentionsTypes.UserMentions,
|
||||
DiscordAllowedMentionsTypes.UserMentions
|
||||
)
|
||||
) {
|
||||
content.allowedMentions.parse = content.allowedMentions.parse.filter(
|
||||
(p) => p !== "users",
|
||||
(p) => p !== "users"
|
||||
);
|
||||
}
|
||||
|
||||
if (content.allowedMentions.users.length > 100) {
|
||||
content.allowedMentions.users = content.allowedMentions.users.slice(
|
||||
0,
|
||||
100,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,18 +75,18 @@ export async function sendMessage(
|
||||
if (content.allowedMentions.roles?.length) {
|
||||
if (
|
||||
content.allowedMentions.parse?.includes(
|
||||
DiscordAllowedMentionsTypes.RoleMentions,
|
||||
DiscordAllowedMentionsTypes.RoleMentions
|
||||
)
|
||||
) {
|
||||
content.allowedMentions.parse = content.allowedMentions.parse.filter(
|
||||
(p) => p !== "roles",
|
||||
(p) => p !== "roles"
|
||||
);
|
||||
}
|
||||
|
||||
if (content.allowedMentions.roles.length > 100) {
|
||||
content.allowedMentions.roles = content.allowedMentions.roles.slice(
|
||||
0,
|
||||
100,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -98,13 +99,14 @@ export async function sendMessage(
|
||||
...content,
|
||||
...(content.messageReference?.messageId
|
||||
? {
|
||||
messageReference: {
|
||||
...content.messageReference,
|
||||
failIfNotExists: content.messageReference.failIfNotExists === true,
|
||||
},
|
||||
}
|
||||
messageReference: {
|
||||
...content.messageReference,
|
||||
failIfNotExists:
|
||||
content.messageReference.failIfNotExists === true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
})
|
||||
)) as DiscordMessage;
|
||||
|
||||
return structures.createDiscordenoMessage(result);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { DiscordGetGatewayBot, GetGatewayBot } from "../../types/gateway/get_gateway_bot.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { camelKeysToSnakeCase } from "../../util/utils.ts";
|
||||
|
||||
/** Get the bots Gateway metadata that can help during the operation of large or sharded bots. */
|
||||
export async function getGatewayBot() {
|
||||
const result = await rest.runMethod("get", endpoints.GATEWAY_BOT);
|
||||
|
||||
return result as DiscordBotGatewayData;
|
||||
return camelKeysToSnakeCase(result as DiscordGetGatewayBot) as GetGatewayBot;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
@@ -27,7 +28,7 @@ export async function createGuildTemplate(
|
||||
"post",
|
||||
endpoints.GUILD_TEMPLATES(guildId),
|
||||
data,
|
||||
)) as GuildTemplate;
|
||||
)) as DiscordTemplate;
|
||||
|
||||
return structures.createTemplateStruct(template);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
@@ -16,7 +17,7 @@ export async function deleteGuildTemplate(
|
||||
const deletedTemplate = (await rest.runMethod(
|
||||
"delete",
|
||||
`${endpoints.GUILD_TEMPLATES(guildId)}/${templateCode}`,
|
||||
)) as GuildTemplate;
|
||||
)) as DiscordTemplate;
|
||||
|
||||
return structures.createTemplateStruct(deletedTemplate);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
|
||||
@@ -26,7 +27,7 @@ export async function editGuildTemplate(
|
||||
"patch",
|
||||
`${endpoints.GUILD_TEMPLATES(guildId)}/${templateCode}`,
|
||||
data,
|
||||
)) as GuildTemplate;
|
||||
)) as DiscordTemplate;
|
||||
|
||||
return structures.createTemplateStruct(template);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
|
||||
/**
|
||||
* Returns an array of templates.
|
||||
@@ -12,8 +13,8 @@ export async function getGuildTemplates(guildId: string) {
|
||||
|
||||
const templates = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.GUILD_TEMPLATES(guildId),
|
||||
)) as GuildTemplate[];
|
||||
endpoints.GUILD_TEMPLATES(guildId)
|
||||
)) as DiscordTemplate[];
|
||||
|
||||
return templates.map((template) => structures.createTemplateStruct(template));
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
|
||||
/** Returns the guild template if it exists */
|
||||
export async function getTemplate(templateCode: string) {
|
||||
const result = (await rest.runMethod(
|
||||
"get",
|
||||
endpoints.GUILD_TEMPLATE(templateCode),
|
||||
) as GuildTemplate);
|
||||
) as DiscordTemplate);
|
||||
const template = await structures.createTemplateStruct(result);
|
||||
|
||||
return template;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
import { requireBotGuildPermissions } from "../../util/permissions.ts";
|
||||
import { DiscordTemplate } from "../../types/templates/template.ts";
|
||||
|
||||
/**
|
||||
* Syncs the template to the guild's current state.
|
||||
@@ -13,7 +14,7 @@ export async function syncGuildTemplate(guildId: string, templateCode: string) {
|
||||
const template = (await rest.runMethod(
|
||||
"put",
|
||||
`${endpoints.GUILD_TEMPLATES(guildId)}/${templateCode}`,
|
||||
)) as GuildTemplate;
|
||||
)) as DiscordTemplate;
|
||||
|
||||
return structures.createTemplateStruct(template);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { Errors } from "../../types/misc/errors.ts";
|
||||
import { EditWebhookMessage } from "../../types/webhooks/edit_webhook_message.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
export async function editWebhookMessage(
|
||||
webhookId: string,
|
||||
webhookToken: string,
|
||||
messageId: string,
|
||||
options: EditWebhookMessageOptions,
|
||||
options: EditWebhookMessage
|
||||
) {
|
||||
if (options.content && options.content.length > 2000) {
|
||||
throw Error(Errors.MESSAGE_MAX_LENGTH);
|
||||
@@ -17,43 +20,43 @@ export async function editWebhookMessage(
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
|
||||
if (options.allowed_mentions) {
|
||||
if (options.allowed_mentions.users?.length) {
|
||||
if (options.allowed_mentions.parse.includes("users")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter(
|
||||
(p) => p !== "users",
|
||||
if (options.allowedMentions) {
|
||||
if (options.allowedMentions.users?.length) {
|
||||
if (options.allowedMentions.parse.includes("users")) {
|
||||
options.allowedMentions.parse = options.allowedMentions.parse.filter(
|
||||
(p) => p !== "users"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.users.length > 100) {
|
||||
options.allowed_mentions.users = options.allowed_mentions.users.slice(
|
||||
if (options.allowedMentions.users.length > 100) {
|
||||
options.allowedMentions.users = options.allowedMentions.users.slice(
|
||||
0,
|
||||
100,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles?.length) {
|
||||
if (options.allowed_mentions.parse.includes("roles")) {
|
||||
options.allowed_mentions.parse = options.allowed_mentions.parse.filter(
|
||||
(p) => p !== "roles",
|
||||
if (options.allowedMentions.roles?.length) {
|
||||
if (options.allowedMentions.parse.includes(DiscordAllowedMentionsTypes.RoleMentions)) {
|
||||
options.allowedMentions.parse = options.allowedMentions.parse.filter(
|
||||
(p) => p !== "roles"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.allowed_mentions.roles.length > 100) {
|
||||
options.allowed_mentions.roles = options.allowed_mentions.roles.slice(
|
||||
if (options.allowedMentions.roles.length > 100) {
|
||||
options.allowedMentions.roles = options.allowedMentions.roles.slice(
|
||||
0,
|
||||
100,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await rest.runMethod(
|
||||
const result = (await rest.runMethod(
|
||||
"patch",
|
||||
endpoints.WEBHOOK_MESSAGE(webhookId, webhookToken, messageId),
|
||||
{ ...options, allowed_mentions: options.allowed_mentions },
|
||||
) as MessageCreateOptions;
|
||||
{ ...options, allowedMentions: options.allowedMentions }
|
||||
)) as DiscordMessage;
|
||||
|
||||
const message = await structures.createDiscordenoMessage(result);
|
||||
return message;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { rest } from "../../rest/rest.ts";
|
||||
import { structures } from "../../structures/mod.ts";
|
||||
import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts";
|
||||
import { DiscordMessage } from "../../types/messages/message.ts";
|
||||
import { Errors } from "../../types/misc/errors.ts";
|
||||
import { ExecuteWebhook } from "../../types/webhooks/execute_webhook.ts";
|
||||
import { endpoints } from "../../util/constants.ts";
|
||||
|
||||
/** Execute a webhook with webhook Id and webhook token */
|
||||
export async function executeWebhook(
|
||||
webhookId: string,
|
||||
webhookToken: string,
|
||||
options: ExecuteWebhookOptions,
|
||||
options: ExecuteWebhook
|
||||
) {
|
||||
if (!options.content && !options.file && !options.embeds) {
|
||||
throw new Error(Errors.INVALID_WEBHOOK_OPTIONS);
|
||||
@@ -21,28 +24,42 @@ export async function executeWebhook(
|
||||
options.embeds.splice(10);
|
||||
}
|
||||
|
||||
if (options.mentions) {
|
||||
if (options.mentions.users?.length) {
|
||||
if (options.mentions.parse.includes("users")) {
|
||||
options.mentions.parse = options.mentions.parse.filter(
|
||||
(p) => p !== "users",
|
||||
if (options.allowedMentions) {
|
||||
if (options.allowedMentions.users?.length) {
|
||||
if (
|
||||
options.allowedMentions.parse.includes(
|
||||
DiscordAllowedMentionsTypes.UserMentions
|
||||
)
|
||||
) {
|
||||
options.allowedMentions.parse = options.allowedMentions.parse.filter(
|
||||
(p) => p !== "users"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mentions.users.length > 100) {
|
||||
options.mentions.users = options.mentions.users.slice(0, 100);
|
||||
if (options.allowedMentions.users.length > 100) {
|
||||
options.allowedMentions.users = options.allowedMentions.users.slice(
|
||||
0,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.mentions.roles?.length) {
|
||||
if (options.mentions.parse.includes("roles")) {
|
||||
options.mentions.parse = options.mentions.parse.filter(
|
||||
(p) => p !== "roles",
|
||||
if (options.allowedMentions.roles?.length) {
|
||||
if (
|
||||
options.allowedMentions.parse.includes(
|
||||
DiscordAllowedMentionsTypes.RoleMentions
|
||||
)
|
||||
) {
|
||||
options.allowedMentions.parse = options.allowedMentions.parse.filter(
|
||||
(p) => p !== "roles"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.mentions.roles.length > 100) {
|
||||
options.mentions.roles = options.mentions.roles.slice(0, 100);
|
||||
if (options.allowedMentions.roles.length > 100) {
|
||||
options.allowedMentions.roles = options.allowedMentions.roles.slice(
|
||||
0,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,11 +71,15 @@ export async function executeWebhook(
|
||||
}`,
|
||||
{
|
||||
...options,
|
||||
allowed_mentions: options.mentions,
|
||||
avatar_url: options.avatar_url,
|
||||
},
|
||||
allowed_mentions: options.allowedMentions,
|
||||
avatar_url: options.avatarUrl,
|
||||
}
|
||||
);
|
||||
if (!options.wait) return;
|
||||
|
||||
return structures.createDiscordenoMessage(result as MessageCreateOptions);
|
||||
return structures.createDiscordenoMessage(result as DiscordMessage);
|
||||
}
|
||||
|
||||
function DiscordAllowedMentionTypes(DiscordAllowedMentionTypes: any) {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ export const rest = {
|
||||
ratelimitedPaths: new Map(),
|
||||
eventHandlers: {
|
||||
// BY DEFAULT WE WILL LOG ALL ERRORS TO CONSOLE. USER CAN CHOOSE TO OVERRIDE
|
||||
error: console.error,
|
||||
error: function (...args: unknown[]) {},
|
||||
// PLACEHOLDERS TO ALLOW USERS TO CUSTOMIZE
|
||||
debug: function (_type, error, ...args) {},
|
||||
fetching() {},
|
||||
fetched() {},
|
||||
fetchSuccess() {},
|
||||
fetchFailed() {},
|
||||
globallyRateLimited() {},
|
||||
retriesMaxed() {},
|
||||
debug: function (type: string, error: string | Record<string, unknown>) {},
|
||||
fetching(payload: Record<string, unknown>) {},
|
||||
fetched(payload: Record<string, unknown>) {},
|
||||
fetchSuccess(payload: Record<string, unknown>) {},
|
||||
fetchFailed(payload: Record<string, unknown>, error: any) {},
|
||||
globallyRateLimited(url: string, resetsAt: number) {},
|
||||
retriesMaxed(payload: Record<string, unknown>) {},
|
||||
},
|
||||
/** Handler function for every request. Converts to json, verified authorization & requirements and begins processing the request */
|
||||
handlePayload,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { removeReaction } from "../helpers/messages/remove_reaction.ts";
|
||||
import { removeReactionEmoji } from "../helpers/messages/remove_reaction_emoji.ts";
|
||||
import { sendMessage } from "../helpers/messages/send_message.ts";
|
||||
import { GuildMember } from "../types/guilds/guild_member.ts";
|
||||
import { CreateMessage } from "../types/messages/create_message.ts";
|
||||
import { EditMessage } from "../types/messages/edit_message.ts";
|
||||
import { DiscordMessage, Message } from "../types/messages/message.ts";
|
||||
import { CHANNEL_MENTION_REGEX } from "../util/constants.ts";
|
||||
@@ -38,8 +39,9 @@ const baseMessage: Partial<DiscordenoMessage> = {
|
||||
return this.member?.guilds.get(this.guildId);
|
||||
},
|
||||
get link() {
|
||||
return `https://discord.com/channels/${this.guildId ||
|
||||
"@me"}/${this.channelId}/${this.id}`;
|
||||
return `https://discord.com/channels/${this.guildId || "@me"}/${
|
||||
this.channelId
|
||||
}/${this.id}`;
|
||||
},
|
||||
get mentionedRoles() {
|
||||
return this.mentionedRoleIds?.map((id) => this.guild?.roles.get(id)) || [];
|
||||
@@ -68,20 +70,25 @@ const baseMessage: Partial<DiscordenoMessage> = {
|
||||
return addReactions(this.channelId!, this.id!, reactions, ordered);
|
||||
},
|
||||
reply(content) {
|
||||
const contentWithMention = typeof content === "string"
|
||||
? {
|
||||
content,
|
||||
mentions: { repliedUser: true },
|
||||
messageReference: { messageId: this.id },
|
||||
failReplyIfNotExists: false,
|
||||
}
|
||||
: {
|
||||
...content,
|
||||
mentions: { ...(content.allowedMentions || {}), repliedUser: true },
|
||||
messageReference: { messageId: this.id },
|
||||
failReplyIfNotExists:
|
||||
content.messageReference?.failIfNotExists === true,
|
||||
};
|
||||
const contentWithMention =
|
||||
typeof content === "string"
|
||||
? {
|
||||
content,
|
||||
mentions: { repliedUser: true },
|
||||
messageReference: {
|
||||
messageId: this.id,
|
||||
failReplyIfNotExists: false,
|
||||
},
|
||||
}
|
||||
: {
|
||||
...content,
|
||||
mentions: { ...(content.allowedMentions || {}), repliedUser: true },
|
||||
messageReference: {
|
||||
messageId: this.id,
|
||||
failReplyIfNotExists:
|
||||
content.messageReference?.failIfNotExists === true,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.guildId) return sendMessage(this.channelId!, contentWithMention);
|
||||
return sendDirectMessage(this.author!.id, contentWithMention);
|
||||
@@ -124,7 +131,7 @@ export async function createDiscordenoMessage(data: DiscordMessage) {
|
||||
mentionChannels = [],
|
||||
mentions,
|
||||
mentionRoles,
|
||||
edited_timestamp: editedTimestamp,
|
||||
editedTimestamp,
|
||||
...rest
|
||||
} = snakeKeysToCamelCase(data) as Message;
|
||||
|
||||
@@ -132,15 +139,15 @@ export async function createDiscordenoMessage(data: DiscordMessage) {
|
||||
for (const key of Object.keys(rest)) {
|
||||
eventHandlers.debug?.(
|
||||
"loop",
|
||||
`Running for of loop in createDiscordenoMessage function.`,
|
||||
`Running for of loop in createDiscordenoMessage function.`
|
||||
);
|
||||
// @ts-ignore index signature
|
||||
props[key] = createNewProp(rest[key]);
|
||||
}
|
||||
|
||||
// Discord doesnt give guild id for getMessage() so this will fill it in
|
||||
const guildIdFinal = guildId ||
|
||||
(await cacheHandlers.get("channels", channelId))?.guildId || "";
|
||||
const guildIdFinal =
|
||||
guildId || (await cacheHandlers.get("channels", channelId))?.guildId || "";
|
||||
|
||||
const message: DiscordenoMessage = Object.create(baseMessage, {
|
||||
...props,
|
||||
@@ -149,20 +156,18 @@ export async function createDiscordenoMessage(data: DiscordMessage) {
|
||||
guildId: createNewProp(guildIdFinal),
|
||||
mentionedUserIds: createNewProp(mentions.map((m) => m.id)),
|
||||
mentionedRoleIds: createNewProp(mentionRoles),
|
||||
mentionedChannelIds: createNewProp(
|
||||
[
|
||||
// Keep any ids that discord sends
|
||||
...mentionChannels.map((m) => m.id),
|
||||
// Add any other ids that can be validated in a channel mention format
|
||||
...(rest.content.match(CHANNEL_MENTION_REGEX) || []).map((text) =>
|
||||
// converts the <#123> into 123
|
||||
text.substring(2, text.length - 1)
|
||||
),
|
||||
],
|
||||
),
|
||||
mentionedChannelIds: createNewProp([
|
||||
// Keep any ids that discord sends
|
||||
...mentionChannels.map((m) => m.id),
|
||||
// Add any other ids that can be validated in a channel mention format
|
||||
...(rest.content.match(CHANNEL_MENTION_REGEX) || []).map((text) =>
|
||||
// converts the <#123> into 123
|
||||
text.substring(2, text.length - 1)
|
||||
),
|
||||
]),
|
||||
timestamp: createNewProp(Date.parse(data.timestamp)),
|
||||
editedTimestamp: createNewProp(
|
||||
editedTimestamp ? Date.parse(editedTimestamp) : undefined,
|
||||
editedTimestamp ? Date.parse(editedTimestamp) : undefined
|
||||
),
|
||||
});
|
||||
|
||||
@@ -204,7 +209,7 @@ export interface DiscordenoMessage extends Message {
|
||||
/** Delete the message */
|
||||
delete(
|
||||
reason?: string,
|
||||
delayMilliseconds?: number,
|
||||
delayMilliseconds?: number
|
||||
): ReturnType<typeof deleteMessage>;
|
||||
/** Edit the message */
|
||||
edit(content: string | EditMessage): ReturnType<typeof editMessage>;
|
||||
@@ -215,27 +220,23 @@ export interface DiscordenoMessage extends Message {
|
||||
/** Add multiple reactions to the message without or without order. */
|
||||
addReactions(
|
||||
reactions: string[],
|
||||
ordered?: boolean,
|
||||
ordered?: boolean
|
||||
): ReturnType<typeof addReactions>;
|
||||
/** Send a inline reply to this message */
|
||||
reply(
|
||||
content: string | DiscordenoCreateMessage,
|
||||
): ReturnType<typeof sendMessage>;
|
||||
reply(content: string | CreateMessage): ReturnType<typeof sendMessage>;
|
||||
/** Send a message to this channel where this message is */
|
||||
send(
|
||||
content: string | DiscordenoCreateMessage,
|
||||
): ReturnType<typeof sendMessage>;
|
||||
send(content: string | CreateMessage): ReturnType<typeof sendMessage>;
|
||||
/** Send a message to this channel and then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
|
||||
alert(
|
||||
content: string | DiscordenoCreateMessage,
|
||||
content: string | CreateMessage,
|
||||
timeout?: number,
|
||||
reason?: string,
|
||||
reason?: string
|
||||
): Promise<void>;
|
||||
/** Send a inline reply to this message but then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */
|
||||
alertReply(
|
||||
content: string | DiscordenoCreateMessage,
|
||||
content: string | CreateMessage,
|
||||
timeout?: number,
|
||||
reason?: string,
|
||||
reason?: string
|
||||
): Promise<unknown>;
|
||||
/** Remove all reactions */
|
||||
removeAllReactions(): ReturnType<typeof removeAllReactions>;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { eventHandlers } from "../bot.ts";
|
||||
import { cache } from "../cache.ts";
|
||||
import { DiscordTemplate, Template } from "../types/templates/template.ts";
|
||||
import { createNewProp } from "../util/utils.ts";
|
||||
import { DiscordenoGuild } from "./guild.ts";
|
||||
|
||||
const baseTemplate: Partial<Template> = {
|
||||
const baseTemplate: Partial<DiscordTemplate> = {
|
||||
get sourceGuild() {
|
||||
// deno-lint-ignore getter-return
|
||||
if (!this.sourceGuildId) return;
|
||||
@@ -11,7 +13,7 @@ const baseTemplate: Partial<Template> = {
|
||||
};
|
||||
|
||||
export function createTemplateStruct(
|
||||
data: GuildTemplate,
|
||||
data: DiscordTemplate,
|
||||
) {
|
||||
const {
|
||||
usage_count: usageCount,
|
||||
@@ -43,5 +45,9 @@ export function createTemplateStruct(
|
||||
sourceGuildId: createNewProp(sourceGuildId),
|
||||
serializedSourceGuild: createNewProp(serializedSourceGuild),
|
||||
isDirty: createNewProp(isDirty),
|
||||
}) as Template;
|
||||
}) as DiscordenoTemplate;
|
||||
}
|
||||
|
||||
export interface DiscordenoTemplate extends Template {
|
||||
sourceGuild?: DiscordenoGuild;
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
import { DiscordenoChannel } from "../../structures/channel.ts";
|
||||
import { DiscordenoGuild } from "../../structures/guild.ts";
|
||||
import { DiscordenoMember } from "../../structures/member.ts";
|
||||
import { DiscordenoMessage } from "../../structures/message.ts";
|
||||
import { DiscordenoRole } from "../../structures/role.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { IntegrationCreateUpdate } from "../integration/integration_create_update.ts";
|
||||
import { ApplicationCommandCreateUpdateDelete } from "../interactions/application_command_create_update_delete.ts";
|
||||
import {
|
||||
Channel,
|
||||
DiscordGatewayPayload,
|
||||
DiscordMessageReactionRemoveAll,
|
||||
Emoji,
|
||||
GatewayPayload,
|
||||
IntegrationDelete,
|
||||
Interaction,
|
||||
InviteCreate,
|
||||
InviteDelete,
|
||||
MessageDelete,
|
||||
MessageReactionAdd,
|
||||
MessageReactionRemove,
|
||||
PresenceUpdate,
|
||||
@@ -21,21 +23,22 @@ import {
|
||||
User,
|
||||
VoiceState,
|
||||
} from "../mod.ts";
|
||||
import { VoiceServerUpdate } from "../voice/voice_server_update.ts";
|
||||
import { DebugArg } from "./debug_arg.ts";
|
||||
import { GuildUpdateChange } from "./guild_update_change.ts";
|
||||
|
||||
export interface EventHandlers {
|
||||
/** Sent when a new Slash Command is created, relevant to the current user. */
|
||||
applicationCommandCreate?: (
|
||||
data: ApplicationCommandCreateUpdateDelete,
|
||||
data: ApplicationCommandCreateUpdateDelete
|
||||
) => unknown;
|
||||
/** Sent when a Slash Command relevant to the current user is updated. */
|
||||
applicationCommandUpdate?: (
|
||||
data: ApplicationCommandCreateUpdateDelete,
|
||||
data: ApplicationCommandCreateUpdateDelete
|
||||
) => unknown;
|
||||
/** Sent when a Slash Command relevant to the current user is deleted. */
|
||||
applicationCommandDelete?: (
|
||||
data: ApplicationCommandCreateUpdateDelete,
|
||||
data: ApplicationCommandCreateUpdateDelete
|
||||
) => unknown;
|
||||
/** Sent when properties about the user change. */
|
||||
botUpdate?: (user: User) => unknown;
|
||||
@@ -45,94 +48,96 @@ export interface EventHandlers {
|
||||
channelUpdate?: (channel: Channel, oldChannel: Channel) => unknown;
|
||||
/** Sent when a channel relevant to the current user is deleted. */
|
||||
channelDelete?: (channel: Channel) => unknown;
|
||||
debug?: (args: DebugArg) => unknown;
|
||||
debug?: (args: string | DebugArg, data?: string) => unknown;
|
||||
/** Sent before every event. Discordeno awaits the execution of this event before main event gets sent. */
|
||||
dispatchRequirements?: (
|
||||
data: DiscordGatewayPayload,
|
||||
shardId: number,
|
||||
shardId: number
|
||||
) => unknown;
|
||||
/** Sent when a user is banned from a guild. */
|
||||
guildBanAdd?: (
|
||||
guild: DiscordenoGuild,
|
||||
user: User,
|
||||
member?: DiscordenoMember,
|
||||
member?: DiscordenoMember
|
||||
) => unknown;
|
||||
/** Sent when a user is unbanned from a guild. */
|
||||
guildBanRemove?: (
|
||||
guild: DiscordenoGuild,
|
||||
user: User,
|
||||
member?: DiscordenoMember,
|
||||
member?: DiscordenoMember
|
||||
) => unknown;
|
||||
/**
|
||||
* This event can be sent in three different scenarios:
|
||||
* 1. When a user is initially connecting, to lazily load and backfill information for all unavailable guilds sent in the `READY` event. Guilds that are unavailable due to an outage will send a `GUILD_DELETE` event.
|
||||
* 2. When a Guild becomes available again to the client.
|
||||
* 3. When the current user joins a new Guild.
|
||||
*
|
||||
*
|
||||
* This event does not get sent on startup
|
||||
*/
|
||||
guildCreate?: (guild: DiscordenoGuild) => unknown;
|
||||
/** This event does get sent on start when shards are loading the guilds */
|
||||
guildLoaded?: (guild: DiscordenoGuild) => unknown;
|
||||
/** When a guild goes unavailable this event will be ran. */
|
||||
guildAvailable?: (guild: DiscordenoGuild) => unknown;
|
||||
/** Sent when a guild is updated. */
|
||||
guildUpdate?: (
|
||||
guild: DiscordenoGuild,
|
||||
changes: GuildUpdateChange[],
|
||||
changes: GuildUpdateChange[]
|
||||
) => unknown;
|
||||
/** Sent when a guild becomes or was already unavailable due to an outage, or when the user leaves or is removed from a guild. If the `unavailable` field is not set, the user was removed from the guild. */
|
||||
guildDelete?: (guild: DiscordenoGuild) => unknown;
|
||||
/** Sent when a guild's emojis have been updated. */
|
||||
guildEmojisUpdate?: (
|
||||
guild: DiscordenoGuild,
|
||||
emojis: Emoji[],
|
||||
oldEmojis: Emoji[],
|
||||
emojis: Collection<string, Emoji>,
|
||||
oldEmojis: Collection<string, Emoji>
|
||||
) => unknown;
|
||||
/** Sent when a new user joins a guild. */
|
||||
guildMemberAdd?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
member: DiscordenoMember
|
||||
) => unknown;
|
||||
/** Sent when a user is removed from a guild (leave/kick/ban). */
|
||||
guildMemberRemove?: (
|
||||
guild: DiscordenoGuild,
|
||||
user: User,
|
||||
member?: DiscordenoMember,
|
||||
member?: DiscordenoMember
|
||||
) => unknown;
|
||||
/** Sent when a guild member is updated. This will also fire when the user object of a guild member changes. */
|
||||
guildMemberUpdate?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
oldMember?: DiscordenoMember,
|
||||
oldMember?: DiscordenoMember
|
||||
) => unknown;
|
||||
// TODO: remove this?
|
||||
//heartbeat?: () => unknown;
|
||||
/** Sent when a user in a guild uses a Slash Command. */
|
||||
interactionCreate?: (
|
||||
data: Omit<Interaction, "member"> & { member: DiscordenoMember },
|
||||
data: Omit<Interaction, "member"> & { member: DiscordenoMember }
|
||||
) => unknown;
|
||||
/** Sent when a message is created. */
|
||||
messageCreate?: (message: DiscordenoMessage) => unknown;
|
||||
/** Sent when a message is deleted. */
|
||||
messageDelete?: (
|
||||
partial: MessageDelete,
|
||||
message?: DiscordenoMessage,
|
||||
partial: { id: string; channel: DiscordenoChannel },
|
||||
message?: DiscordenoMessage
|
||||
) => unknown;
|
||||
/** Sent when a message is updated. */
|
||||
messageUpdate?: (
|
||||
message: DiscordenoMessage,
|
||||
oldMessage: DiscordenoMessage,
|
||||
oldMessage: DiscordenoMessage
|
||||
) => unknown;
|
||||
/** Sent when a user updates its nickname */
|
||||
nicknameUpdate?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
nickname: string,
|
||||
oldNickname?: string,
|
||||
oldNickname?: string
|
||||
) => unknown;
|
||||
/** A user's presence is their current state on a guild. This event is sent when a user's presence or info, such as name or avatar, is updated. */
|
||||
presenceUpdate?: (
|
||||
presence: PresenceUpdate,
|
||||
oldPresence?: PerformanceEntry,
|
||||
oldPresence?: PerformanceEntry
|
||||
) => unknown;
|
||||
/** Sent before every event execution. Discordeno will not await its execution. */
|
||||
raw?: (data: GatewayPayload) => unknown;
|
||||
@@ -144,25 +149,21 @@ export interface EventHandlers {
|
||||
reactionAdd?: (
|
||||
data: MessageReactionAdd,
|
||||
member?: DiscordenoMember,
|
||||
message?: DiscordenoMessage,
|
||||
message?: DiscordenoMessage
|
||||
) => unknown;
|
||||
/** Sent when a user removes a reaction from a message. */
|
||||
reactionRemove?: (
|
||||
data: MessageReactionRemove,
|
||||
message?: DiscordenoMessage,
|
||||
message?: DiscordenoMessage
|
||||
) => unknown;
|
||||
/** Sent when a user explicitly removes all reactions from a message. */
|
||||
reactionRemoveAll?: (
|
||||
messageId: string,
|
||||
channelId: string,
|
||||
guildId?: string,
|
||||
) => unknown;
|
||||
reactionRemoveAll?: (payload: DiscordMessageReactionRemoveAll) => unknown;
|
||||
/** Sent when a bot removes all instances of a given emoji from the reactions of a message. */
|
||||
reactionRemoveEmoji?: (
|
||||
emoji: Partial<Emoji>,
|
||||
messageId: string,
|
||||
channelId: string,
|
||||
guildId?: string,
|
||||
guildId?: string
|
||||
) => unknown;
|
||||
/** Sent when a guild role is created. */
|
||||
roleCreate?: (guild: DiscordenoGuild, role: DiscordenoRole) => unknown;
|
||||
@@ -172,17 +173,17 @@ export interface EventHandlers {
|
||||
roleUpdate?: (
|
||||
guild: DiscordenoGuild,
|
||||
role: DiscordenoRole,
|
||||
old: DiscordenoRole,
|
||||
old: DiscordenoRole
|
||||
) => unknown;
|
||||
roleGained?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
roleId: string,
|
||||
roleId: string
|
||||
) => unknown;
|
||||
roleLost?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
roleId: string,
|
||||
roleId: string
|
||||
) => unknown;
|
||||
shardReady?: (shardId: number) => unknown;
|
||||
/** Sent when a user starts typing in a channel. */
|
||||
@@ -195,19 +196,24 @@ export interface EventHandlers {
|
||||
voiceChannelSwitch?: (
|
||||
member: DiscordenoMember,
|
||||
channelId: string,
|
||||
oldChannelId: string,
|
||||
oldChannelId: string
|
||||
) => unknown;
|
||||
/** Sent when a voice server is updated with information for making the bot connect to a voice channel. */
|
||||
voiceServerUpdate?: (
|
||||
payload: VoiceServerUpdate,
|
||||
guild: DiscordenoGuild
|
||||
) => unknown;
|
||||
/** Sent when someone joins/leaves/moves voice channels. */
|
||||
voiceStateUpdate?: (
|
||||
member: DiscordenoMember,
|
||||
voiceState: VoiceState,
|
||||
voiceState: VoiceState
|
||||
) => unknown;
|
||||
/** Sent when a guild channel's webhook is created, updated, or deleted. */
|
||||
webhooksUpdate?: (channelId: string, guildId: string) => unknown;
|
||||
/** Sent when a member has passed the guild's Membership Screening requirements */
|
||||
membershipScreeningPassed?: (
|
||||
guild: DiscordenoGuild,
|
||||
member: DiscordenoMember,
|
||||
member: DiscordenoMember
|
||||
) => unknown;
|
||||
/** Sent when an integration is created on a server such as twitch, youtube etc.. */
|
||||
integrationCreate?: (data: IntegrationCreateUpdate) => unknown;
|
||||
|
||||
@@ -34,7 +34,7 @@ export interface Message {
|
||||
/** When this message was sent */
|
||||
timestamp: string;
|
||||
/** When this message was edited (or null if never) */
|
||||
edited_timestamp: string | null;
|
||||
editedTimestamp: string | null;
|
||||
/** Whether this was a TTS message */
|
||||
tts: boolean;
|
||||
/** Whether this message mentions everyone */
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import { botId } from "../bot.ts";
|
||||
import { cacheHandlers } from "../cache.ts";
|
||||
import { Channel, Guild, Member, Role } from "../structures/mod.ts";
|
||||
import { DiscordenoChannel } from "../structures/channel.ts";
|
||||
import { DiscordenoGuild } from "../structures/guild.ts";
|
||||
import { DiscordenoMember } from "../structures/member.ts";
|
||||
import { DiscordenoRole } from "../structures/role.ts";
|
||||
import { Errors } from "../types/misc/errors.ts";
|
||||
import { DiscordBitwisePermissionFlags } from "../types/permissions/bitwise_permission_flags.ts";
|
||||
import { PermissionStrings } from "../types/permissions/permission_strings.ts";
|
||||
|
||||
async function getCached(
|
||||
table: "guilds",
|
||||
key: string | Guild,
|
||||
): Promise<Guild | undefined>;
|
||||
key: string | DiscordenoGuild
|
||||
): Promise<DiscordenoGuild | undefined>;
|
||||
async function getCached(
|
||||
table: "channels",
|
||||
key: string | Channel,
|
||||
): Promise<Channel | undefined>;
|
||||
key: string | DiscordenoChannel
|
||||
): Promise<DiscordenoChannel | undefined>;
|
||||
async function getCached(
|
||||
table: "members",
|
||||
key: string | Member,
|
||||
): Promise<Member | undefined>;
|
||||
key: string | DiscordenoMember
|
||||
): Promise<DiscordenoMember | undefined>;
|
||||
async function getCached(
|
||||
table: "guilds" | "channels" | "members",
|
||||
key: string | Guild | Channel | Member,
|
||||
key: string | DiscordenoGuild | DiscordenoChannel | DiscordenoMember
|
||||
) {
|
||||
const cached = typeof key === "string"
|
||||
? // @ts-ignore TS is wrong here
|
||||
await cacheHandlers.get(table, key)
|
||||
: key;
|
||||
const cached =
|
||||
typeof key === "string"
|
||||
? // @ts-ignore TS is wrong here
|
||||
await cacheHandlers.get(table, key)
|
||||
: key;
|
||||
if (!cached || typeof cached === "string") {
|
||||
throw new Error(
|
||||
Errors[`${table.slice(0, -1).toUpperCase()}_NOT_FOUND` as Errors],
|
||||
Errors[`${table.slice(0, -1).toUpperCase()}_NOT_FOUND` as Errors]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,18 +40,18 @@ async function getCached(
|
||||
|
||||
/** Calculates the permissions this member has in the given guild */
|
||||
export async function calculateBasePermissions(
|
||||
guild: string | Guild,
|
||||
member: string | Member,
|
||||
guildOrId: string | DiscordenoGuild,
|
||||
memberOrId: string | DiscordenoMember
|
||||
) {
|
||||
guild = await getCached("guilds", guild);
|
||||
member = await getCached("members", member);
|
||||
const guild = await getCached("guilds", guildOrId);
|
||||
const member = await getCached("members", memberOrId);
|
||||
|
||||
if (!guild || !member) return "8";
|
||||
|
||||
let permissions = 0n;
|
||||
// Calculate the role permissions bits, @everyone role is not in memberRoleIds so we need to pass guildId manualy
|
||||
permissions |= [...(member.guilds.get(guild.id)?.roles || []), guild.id]
|
||||
.map((id) => (guild as Guild).roles.get(id)?.permissions)
|
||||
.map((id) => (guild as DiscordenoGuild).roles.get(id)?.permissions)
|
||||
// Removes any edge case undefined
|
||||
.filter((perm) => perm)
|
||||
.reduce((bits, perms) => {
|
||||
@@ -63,26 +67,26 @@ export async function calculateBasePermissions(
|
||||
|
||||
/** Calculates the permissions this member has for the given Channel */
|
||||
export async function calculateChannelOverwrites(
|
||||
channel: string | Channel,
|
||||
member: string | Member,
|
||||
channelOrId: string | DiscordenoChannel,
|
||||
memberOrId: string | DiscordenoMember
|
||||
) {
|
||||
channel = await getCached("channels", channel);
|
||||
const channel = await getCached("channels", channelOrId);
|
||||
|
||||
// This is a DM channel so return ADMINISTRATOR permission
|
||||
if (!channel?.guildId) return "8";
|
||||
|
||||
member = await getCached("members", member);
|
||||
const member = await getCached("members", memberOrId);
|
||||
|
||||
if (!member) return "8";
|
||||
|
||||
// Get all the role permissions this member already has
|
||||
let permissions = BigInt(
|
||||
await calculateBasePermissions(channel.guildId, member),
|
||||
await calculateBasePermissions(channel.guildId, member)
|
||||
);
|
||||
|
||||
// First calculate @everyone overwrites since these have the lowest priority
|
||||
const overwriteEveryone = channel?.permissionOverwrites.find(
|
||||
(overwrite) => overwrite.id === (channel as Channel).guildId,
|
||||
const overwriteEveryone = channel?.permissionOverwrites?.find(
|
||||
(overwrite) => overwrite.id === (channel as DiscordenoChannel).guildId
|
||||
);
|
||||
if (overwriteEveryone) {
|
||||
// First remove denied permissions since denied < allowed
|
||||
@@ -97,7 +101,7 @@ export async function calculateChannelOverwrites(
|
||||
let deny = 0n;
|
||||
const memberRoles = member.guilds.get(channel.guildId)?.roles || [];
|
||||
// Second calculate members role overwrites since these have middle priority
|
||||
for (const overwrite of overwrites) {
|
||||
for (const overwrite of overwrites || []) {
|
||||
if (!memberRoles.includes(overwrite.id)) continue;
|
||||
|
||||
deny |= BigInt(overwrite.deny);
|
||||
@@ -108,8 +112,8 @@ export async function calculateChannelOverwrites(
|
||||
permissions |= allow;
|
||||
|
||||
// Third calculate member specific overwrites since these have the highest priority
|
||||
const overwriteMember = overwrites.find(
|
||||
(overwrite) => overwrite.id === (member as Member).id,
|
||||
const overwriteMember = overwrites?.find(
|
||||
(overwrite) => overwrite.id === (member as DiscordenoMember).id
|
||||
);
|
||||
if (overwriteMember) {
|
||||
permissions &= ~BigInt(overwriteMember.deny);
|
||||
@@ -122,23 +126,22 @@ export async function calculateChannelOverwrites(
|
||||
/** Checks if the given permission bits are matching the given permissions. `ADMINISTRATOR` always returns `true` */
|
||||
export function validatePermissions(
|
||||
permissionBits: string,
|
||||
permissions: PermissionStrings[],
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
if (BigInt(permissionBits) & 8n) return true;
|
||||
|
||||
return permissions.every(
|
||||
(permission) =>
|
||||
// Check if permission is in permissionBits
|
||||
BigInt(permissionBits) &
|
||||
BigInt(DiscordBitwisePermissionFlags[permission]),
|
||||
BigInt(permissionBits) & BigInt(DiscordBitwisePermissionFlags[permission])
|
||||
);
|
||||
}
|
||||
|
||||
/** Checks if the given member has these permissions in the given guild */
|
||||
export async function hasGuildPermissions(
|
||||
guild: string | Guild,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
guild: string | DiscordenoGuild,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// First we need the role permission bits this member has
|
||||
const basePermissions = await calculateBasePermissions(guild, member);
|
||||
@@ -148,8 +151,8 @@ export async function hasGuildPermissions(
|
||||
|
||||
/** Checks if the bot has these permissions in the given guild */
|
||||
export function botHasGuildPermissions(
|
||||
guild: string | Guild,
|
||||
permissions: PermissionStrings[],
|
||||
guild: string | DiscordenoGuild,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// Since Bot is a normal member we can use the hasRolePermissions() function
|
||||
return hasGuildPermissions(guild, botId, permissions);
|
||||
@@ -157,9 +160,9 @@ export function botHasGuildPermissions(
|
||||
|
||||
/** Checks if the given member has these permissions for the given channel */
|
||||
export async function hasChannelPermissions(
|
||||
channel: string | Channel,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
channel: string | DiscordenoChannel,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// First we need the overwrite bits this member has
|
||||
const channelOverwrites = await calculateChannelOverwrites(channel, member);
|
||||
@@ -169,8 +172,8 @@ export async function hasChannelPermissions(
|
||||
|
||||
/** Checks if the bot has these permissions f0r the given channel */
|
||||
export function botHasChannelPermissions(
|
||||
channel: string | Channel,
|
||||
permissions: PermissionStrings[],
|
||||
channel: string | DiscordenoChannel,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// Since Bot is a normal member we can use the hasRolePermissions() function
|
||||
return hasChannelPermissions(channel, botId, permissions);
|
||||
@@ -179,7 +182,7 @@ export function botHasChannelPermissions(
|
||||
/** Returns the permissions that are not in the given permissionBits */
|
||||
export function missingPermissions(
|
||||
permissionBits: string,
|
||||
permissions: PermissionStrings[],
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
if (BigInt(permissionBits) & 8n) return [];
|
||||
|
||||
@@ -188,15 +191,15 @@ export function missingPermissions(
|
||||
!(
|
||||
BigInt(permissionBits) &
|
||||
BigInt(DiscordBitwisePermissionFlags[permission])
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Get the missing Guild permissions this member has */
|
||||
export async function getMissingGuildPermissions(
|
||||
guild: string | Guild,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
guild: string | DiscordenoGuild,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// First we need the role permissino bits this member has
|
||||
const permissionBits = await calculateBasePermissions(guild, member);
|
||||
@@ -206,9 +209,9 @@ export async function getMissingGuildPermissions(
|
||||
|
||||
/** Get the missing Channel permissions this member has */
|
||||
export async function getMissingChannelPermissions(
|
||||
channel: string | Channel,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
channel: string | DiscordenoChannel,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// First we need the role permissino bits this member has
|
||||
const permissionBits = await calculateChannelOverwrites(channel, member);
|
||||
@@ -218,9 +221,9 @@ export async function getMissingChannelPermissions(
|
||||
|
||||
/** Throws an error if this member has not all of the given permissions */
|
||||
export async function requireGuildPermissions(
|
||||
guild: string | Guild,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
guild: string | DiscordenoGuild,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
const missing = await getMissingGuildPermissions(guild, member, permissions);
|
||||
if (missing.length) {
|
||||
@@ -231,8 +234,8 @@ export async function requireGuildPermissions(
|
||||
|
||||
/** Throws an error if the bot does not have all permissions */
|
||||
export function requireBotGuildPermissions(
|
||||
guild: string | Guild,
|
||||
permissions: PermissionStrings[],
|
||||
guild: string | DiscordenoGuild,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// Since Bot is a normal member we can use the throwOnMissingGuildPermission() function
|
||||
return requireGuildPermissions(guild, botId, permissions);
|
||||
@@ -240,14 +243,14 @@ export function requireBotGuildPermissions(
|
||||
|
||||
/** Throws an error if this member has not all of the given permissions */
|
||||
export async function requireChannelPermissions(
|
||||
channel: string | Channel,
|
||||
member: string | Member,
|
||||
permissions: PermissionStrings[],
|
||||
channel: string | DiscordenoChannel,
|
||||
member: string | DiscordenoMember,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
const missing = await getMissingChannelPermissions(
|
||||
channel,
|
||||
member,
|
||||
permissions,
|
||||
permissions
|
||||
);
|
||||
if (missing.length) {
|
||||
// If the member is missing a permission throw an Error
|
||||
@@ -257,8 +260,8 @@ export async function requireChannelPermissions(
|
||||
|
||||
/** Throws an error if the bot has not all of the given channel permissions */
|
||||
export function requireBotChannelPermissions(
|
||||
channel: string | Channel,
|
||||
permissions: PermissionStrings[],
|
||||
channel: string | DiscordenoChannel,
|
||||
permissions: PermissionStrings[]
|
||||
) {
|
||||
// Since Bot is a normal member we can use the throwOnMissingChannelPermission() function
|
||||
return requireChannelPermissions(channel, botId, permissions);
|
||||
@@ -270,8 +273,10 @@ export function calculatePermissions(permissionBits: bigint) {
|
||||
// Since Object.keys() not only returns the permission names but also the bit values we need to return false if it is a Number
|
||||
if (Number(permission)) return false;
|
||||
// Check if permissionBits has this permission
|
||||
return permissionBits &
|
||||
BigInt(DiscordBitwisePermissionFlags[permission as PermissionStrings]);
|
||||
return (
|
||||
permissionBits &
|
||||
BigInt(DiscordBitwisePermissionFlags[permission as PermissionStrings])
|
||||
);
|
||||
}) as PermissionStrings[];
|
||||
}
|
||||
|
||||
@@ -287,20 +292,20 @@ export function calculateBits(permissions: PermissionStrings[]) {
|
||||
|
||||
/** Gets the highest role from the member in this guild */
|
||||
export async function highestRole(
|
||||
guild: string | Guild,
|
||||
member: string | Member,
|
||||
guildOrId: string | DiscordenoGuild,
|
||||
memberOrId: string | DiscordenoMember
|
||||
) {
|
||||
guild = await getCached("guilds", guild);
|
||||
const guild = await getCached("guilds", guildOrId);
|
||||
|
||||
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
|
||||
|
||||
// Get the roles from the member
|
||||
const memberRoles = (await getCached("members", member))?.guilds.get(guild.id)
|
||||
const memberRoles = (await getCached("members", memberOrId))?.guilds.get(guild.id)
|
||||
?.roles;
|
||||
// This member has no roles so the highest one is the @everyone role
|
||||
if (!memberRoles) return guild.roles.get(guild.id) as Role;
|
||||
if (!memberRoles) return guild.roles.get(guild.id)!;
|
||||
|
||||
let memberHighestRole: Role | undefined;
|
||||
let memberHighestRole: DiscordenoRole | undefined;
|
||||
|
||||
for (const roleId of memberRoles) {
|
||||
const role = guild.roles.get(roleId);
|
||||
@@ -324,11 +329,11 @@ export async function highestRole(
|
||||
|
||||
/** Checks if the first role is higher than the second role */
|
||||
export async function higherRolePosition(
|
||||
guild: string | Guild,
|
||||
guildOrId: string | DiscordenoGuild,
|
||||
roleId: string,
|
||||
otherRoleId: string,
|
||||
otherRoleId: string
|
||||
) {
|
||||
guild = await getCached("guilds", guild);
|
||||
const guild = await getCached("guilds", guildOrId);
|
||||
|
||||
if (!guild) return true;
|
||||
|
||||
@@ -346,11 +351,11 @@ export async function higherRolePosition(
|
||||
|
||||
/** Checks if the member has a higher position than the given role */
|
||||
export async function isHigherPosition(
|
||||
guild: string | Guild,
|
||||
guildOrId: string | DiscordenoGuild,
|
||||
memberId: string,
|
||||
compareRoleId: string,
|
||||
compareRoleId: string
|
||||
) {
|
||||
guild = await getCached("guilds", guild);
|
||||
const guild = await getCached("guilds", guildOrId);
|
||||
|
||||
if (!guild || guild.ownerId === memberId) return true;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { encode } from "../../deps.ts";
|
||||
import { eventHandlers } from "../bot.ts";
|
||||
import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts";
|
||||
import { StatusUpdate } from "../types/gateway/status_update.ts";
|
||||
import { DiscordApplicationCommandOptionTypes } from "../types/interactions/application_command_option_types.ts";
|
||||
import { Errors } from "../types/misc/errors.ts";
|
||||
import { DiscordImageFormat } from "../types/misc/image_format.ts";
|
||||
import { DiscordImageSize } from "../types/misc/image_size.ts";
|
||||
@@ -9,7 +11,7 @@ import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts";
|
||||
|
||||
// TODO: move this function to helpers
|
||||
export function editBotStatus(
|
||||
data: Pick<GatewayStatusUpdatePayload, "activities" | "status">,
|
||||
data: Omit<StatusUpdate, "afk" | "since">,
|
||||
) {
|
||||
ws.shards.forEach((shard) => {
|
||||
eventHandlers.debug?.(
|
||||
@@ -131,7 +133,7 @@ export function snakeKeysToCamelCase<T>(
|
||||
/** @private */
|
||||
function validateSlashOptionChoices(
|
||||
choices: SlashCommandOptionChoice[],
|
||||
optionType: SlashCommandOptionType,
|
||||
optionType: DiscordApplicationCommandOptionTypes,
|
||||
) {
|
||||
for (const choice of choices) {
|
||||
eventHandlers.debug?.(
|
||||
@@ -143,11 +145,11 @@ function validateSlashOptionChoices(
|
||||
}
|
||||
|
||||
if (
|
||||
(optionType === SlashCommandOptionType.STRING &&
|
||||
(optionType === DiscordApplicationCommandOptionTypes.STRING &&
|
||||
(typeof choice.value !== "string" ||
|
||||
choice.value.length < 1 ||
|
||||
choice.value.length > 100)) ||
|
||||
(optionType === SlashCommandOptionType.INTEGER &&
|
||||
(optionType === DiscordApplicationCommandOptionTypes.INTEGER &&
|
||||
typeof choice.value !== "number")
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
@@ -165,8 +167,8 @@ function validateSlashOptions(options: SlashCommandOption[]) {
|
||||
if (
|
||||
option.choices?.length &&
|
||||
(option.choices.length > 25 ||
|
||||
(option.type !== SlashCommandOptionType.STRING &&
|
||||
option.type !== SlashCommandOptionType.INTEGER))
|
||||
(option.type !== DiscordApplicationCommandOptionTypes.STRING &&
|
||||
option.type !== DiscordApplicationCommandOptionTypes.INTEGER))
|
||||
) {
|
||||
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
|
||||
}
|
||||
@@ -186,7 +188,6 @@ function validateSlashOptions(options: SlashCommandOption[]) {
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
export function validateSlashCommands(
|
||||
commands: UpsertSlashCommandOptions[],
|
||||
create = false,
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
import { getGatewayBot } from "../helpers/misc/get_gateway_bot.ts";
|
||||
import { camelKeysToSnakeCase } from "../util/utils.ts";
|
||||
import { ws } from "./ws.ts";
|
||||
|
||||
/** The handler to automatically reshard when necessary. */
|
||||
export async function resharder() {
|
||||
const data = await getGatewayBot();
|
||||
const percentage = ((data.shards - ws.maxShards) / ws.maxShards) * 100;
|
||||
ws.botGatewayData = camelKeysToSnakeCase(await getGatewayBot());
|
||||
|
||||
const percentage =
|
||||
((ws.botGatewayData.shards - ws.maxShards) / ws.maxShards) * 100;
|
||||
// Less than necessary% being used so do nothing
|
||||
if (percentage < ws.reshardPercentage) return;
|
||||
|
||||
// Don't have enough identify rate limits to reshard
|
||||
if (data.session_start_limit.remaining < data.shards) return;
|
||||
if (ws.botGatewayData.sessionStartLimit.remaining < ws.botGatewayData.shards)
|
||||
return;
|
||||
|
||||
// Begin resharding
|
||||
ws.maxShards = data.shards;
|
||||
|
||||
// TODO: ALL THE FOLLOWING CAN BE REPLACED BY THIS 1 LINE
|
||||
// ws.botGatewayData = snakeToCamel(await getGatewayBot())
|
||||
ws.botGatewayData.sessionStartLimit.total = data.session_start_limit.total;
|
||||
ws.botGatewayData.sessionStartLimit.resetAfter =
|
||||
data.session_start_limit.reset_after;
|
||||
ws.botGatewayData.sessionStartLimit.remaining =
|
||||
data.session_start_limit.remaining;
|
||||
ws.botGatewayData.sessionStartLimit.maxConcurrency =
|
||||
data.session_start_limit.max_concurrency;
|
||||
ws.botGatewayData.shards = data.shards;
|
||||
ws.botGatewayData.url = data.url;
|
||||
ws.maxShards = ws.botGatewayData.shards;
|
||||
|
||||
ws.spawnShards(ws.firstShardId);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DiscordGatewayIntents } from "../types/gateway/gateway_intents.ts";
|
||||
import { DiscordGetGatewayBot } from "../types/gateway/get_gateway_bot.ts";
|
||||
import { StartGatewayOptions } from "./start_gateway_options.ts";
|
||||
import { ws } from "./ws.ts";
|
||||
|
||||
@@ -33,7 +34,7 @@ export async function startGateway(options: StartGatewayOptions) {
|
||||
|
||||
const data = (await fetch(`https://discord.com/api/gateway/bot`, {
|
||||
headers: { Authorization: ws.identifyPayload.token },
|
||||
}).then((res) => res.json())) as DiscordBotGatewayData;
|
||||
}).then((res) => res.json())) as DiscordGetGatewayBot;
|
||||
|
||||
ws.maxShards = options.maxShards || data.shards;
|
||||
ws.lastShardId = options.lastShardId || data.shards - 1;
|
||||
|
||||
Reference in New Issue
Block a user