Merge pull request #893 from discordeno/threads

add: threads
This commit is contained in:
ITOH
2021-05-04 16:48:09 +01:00
committed by GitHub
53 changed files with 748 additions and 150 deletions
+2
View File
@@ -23,8 +23,10 @@ jobs:
if: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'run-tests' }}
run: DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} deno test --unstable --coverage=coverage --allow-net tests/mod.ts
- name: Create coverage report
if: github.ref == 'refs/heads/main'
run: deno --unstable coverage ./coverage --lcov > coverage.lcov
- name: Collect and upload the coverage report
if: github.ref == 'refs/heads/main'
uses: codecov/codecov-action@v1.0.10
with:
file: ./coverage.lcov
+22 -10
View File
@@ -15,7 +15,7 @@ export async function handleChannelDelete(data: DiscordGatewayPayload) {
if (!cachedChannel) return;
if (
cachedChannel.type === DiscordChannelTypes.GUILD_VOICE && payload.guildId
cachedChannel.type === DiscordChannelTypes.GuildVoice && payload.guildId
) {
const guild = await cacheHandlers.get("guilds", cachedChannel.guildId);
@@ -34,15 +34,27 @@ export async function handleChannelDelete(data: DiscordGatewayPayload) {
}
}
if (
[
DiscordChannelTypes.GuildText,
DiscordChannelTypes.Dm,
DiscordChannelTypes.GroupDm,
DiscordChannelTypes.GuildNews,
].includes(payload.type)
) {
await cacheHandlers.delete("channels", snowflakeToBigint(payload.id));
cacheHandlers.forEach("messages", (message) => {
eventHandlers.debug?.(
"loop",
`Running forEach messages loop in CHANNEL_DELTE file.`,
);
if (message.channelId === snowflakeToBigint(payload.id)) {
cacheHandlers.delete("messages", message.id);
}
});
}
await cacheHandlers.delete("channels", snowflakeToBigint(payload.id));
cacheHandlers.forEach("messages", (message) => {
eventHandlers.debug?.(
"loop",
`Running forEach messages loop in CHANNEL_DELTE file.`,
);
if (message.channelId === snowflakeToBigint(payload.id)) {
cacheHandlers.delete("messages", message.id);
}
});
eventHandlers.channelDelete?.(cachedChannel);
}
+1 -2
View File
@@ -11,11 +11,10 @@ export async function handleChannelUpdate(data: DiscordGatewayPayload) {
"channels",
snowflakeToBigint(payload.id),
);
if (!cachedChannel) return;
const discordenoChannel = await structures.createDiscordenoChannel(payload);
await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel);
if (!cachedChannel) return;
eventHandlers.channelUpdate?.(discordenoChannel, cachedChannel);
}
+14
View File
@@ -0,0 +1,14 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { structures } from "../../structures/mod.ts";
import { Channel } from "../../types/channels/channel.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
export async function handleThreadCreate(data: DiscordGatewayPayload) {
const payload = data.d as Channel;
const discordenoChannel = await structures.createDiscordenoChannel(payload);
await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel);
eventHandlers.threadCreate?.(discordenoChannel);
}
+28
View File
@@ -0,0 +1,28 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Channel } from "../../types/channels/channel.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
export async function handleThreadDelete(data: DiscordGatewayPayload) {
const payload = data.d as Channel;
const cachedChannel = await cacheHandlers.get(
"channels",
snowflakeToBigint(payload.id),
);
if (!cachedChannel) return;
await cacheHandlers.delete("channels", snowflakeToBigint(payload.id));
cacheHandlers.forEach("messages", (message) => {
eventHandlers.debug?.(
"loop",
`Running forEach messages loop in CHANNEL_DELTE file.`,
);
if (message.channelId === snowflakeToBigint(payload.id)) {
cacheHandlers.delete("messages", message.id);
}
});
eventHandlers.threadDelete?.(cachedChannel);
}
+39
View File
@@ -0,0 +1,39 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { DiscordenoChannel } from "../../structures/channel.ts";
import { structures } from "../../structures/mod.ts";
import { ThreadListSync } from "../../types/channels/threads/thread_list_sync.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { Collection } from "../../util/collection.ts";
export async function handleThreadListSync(data: DiscordGatewayPayload) {
const payload = data.d as ThreadListSync;
const discordenoChannels = await Promise.all(
payload.threads.map(async (thread) => {
const discordenoChannel = await structures.createDiscordenoChannel(
thread,
snowflakeToBigint(payload.guildId),
);
await cacheHandlers.set(
"channels",
discordenoChannel.id,
discordenoChannel,
);
return discordenoChannel;
}),
);
const threads = new Collection<bigint, DiscordenoChannel>(
discordenoChannels.map((t) => [t.id, t]),
);
eventHandlers.threadListSync?.(
threads,
payload.members,
snowflakeToBigint(payload.guildId),
);
}
@@ -0,0 +1,19 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { ThreadMembersUpdate } from "../../types/channels/threads/thread_members_update.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
export async function handleThreadMembersUpdate(data: DiscordGatewayPayload) {
const payload = data.d as ThreadMembersUpdate;
const thread = await cacheHandlers.get(
"channels",
snowflakeToBigint(payload.id),
);
if (!thread) return;
thread.memberCount = payload.memberCount;
await cacheHandlers.set("channels", thread.id, thread);
eventHandlers.threadMembersUpdate?.(payload);
}
@@ -0,0 +1,20 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { ThreadMember } from "../../types/channels/threads/thread_member.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
export async function handleThreadMemberUpdate(data: DiscordGatewayPayload) {
const payload = data.d as ThreadMember;
const thread = await cacheHandlers.get(
"channels",
snowflakeToBigint(payload.id),
);
if (!thread) return;
thread.member = payload;
await cacheHandlers.set("channels", thread.id, thread);
eventHandlers.threadMemberUpdate?.(payload);
}
+20
View File
@@ -0,0 +1,20 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { structures } from "../../structures/mod.ts";
import { Channel } from "../../types/channels/channel.ts";
import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
export async function handleThreadUpdate(data: DiscordGatewayPayload) {
const payload = data.d as Channel;
const oldChannel = await cacheHandlers.get(
"channels",
snowflakeToBigint(payload.id),
);
if (!oldChannel) return;
const discordenoChannel = await structures.createDiscordenoChannel(payload);
await cacheHandlers.set("channels", discordenoChannel.id, discordenoChannel);
eventHandlers.threadUpdate?.(discordenoChannel, oldChannel);
}
+18
View File
@@ -2,6 +2,12 @@ import { handleChannelCreate } from "./channels/CHANNEL_CREATE.ts";
import { handleChannelDelete } from "./channels/CHANNEL_DELETE.ts";
import { handleChannelPinsUpdate } from "./channels/CHANNEL_PINS_UPDATE.ts";
import { handleChannelUpdate } from "./channels/CHANNEL_UPDATE.ts";
import { handleThreadCreate } from "./channels/THREAD_CREATE.ts";
import { handleThreadDelete } from "./channels/THREAD_DELETE.ts";
import { handleThreadListSync } from "./channels/THREAD_LIST_SYNC.ts";
import { handleThreadMembersUpdate } from "./channels/THREAD_MEMBERS_UPDATE.ts";
import { handleThreadMemberUpdate } from "./channels/THREAD_MEMBER_UPDATE.ts";
import { handleThreadUpdate } from "./channels/THREAD_UPDATE.ts";
import { handleApplicationCommandCreate } from "./commands/APPLICATION_COMMAND_CREATE.ts";
import { handleApplicationCommandDelete } from "./commands/APPLICATION_COMMAND_DELETE.ts";
import { handleApplicationCommandUpdate } from "./commands/APPLICATION_COMMAND_UPDATE.ts";
@@ -77,6 +83,12 @@ export {
handleMessageUpdate,
handlePresenceUpdate,
handleReady,
handleThreadCreate,
handleThreadDelete,
handleThreadListSync,
handleThreadMembersUpdate,
handleThreadMemberUpdate,
handleThreadUpdate,
handleTypingStart,
handleUserUpdate,
handleVoiceServerUpdate,
@@ -92,6 +104,12 @@ export let handlers = {
CHANNEL_DELETE: handleChannelDelete,
CHANNEL_PINS_UPDATE: handleChannelPinsUpdate,
CHANNEL_UPDATE: handleChannelUpdate,
THREAD_CREATE: handleThreadCreate,
THREAD_UPDATE: handleThreadUpdate,
THREAD_DELETE: handleThreadDelete,
THREAD_LIST_SYNC: handleThreadListSync,
THREAD_MEMBER_UPDATE: handleThreadMemberUpdate,
THREAD_MEMBERS_UPDATE: handleThreadMembersUpdate,
// commands
APPLICATION_COMMAND_CREATE: handleApplicationCommandCreate,
APPLICATION_COMMAND_DELETE: handleApplicationCommandDelete,
+2 -2
View File
@@ -14,8 +14,8 @@ export async function cloneChannel(channelId: bigint, reason?: string) {
//Check for DM channel
if (
channelToClone.type === DiscordChannelTypes.DM ||
channelToClone.type === DiscordChannelTypes.GROUP_DM
channelToClone.type === DiscordChannelTypes.Dm ||
channelToClone.type === DiscordChannelTypes.GroupDm
) {
throw new Error(Errors.CHANNEL_NOT_IN_GUILD);
}
+7 -15
View File
@@ -1,4 +1,3 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { rest } from "../../rest/rest.ts";
import { structures } from "../../structures/mod.ts";
@@ -8,11 +7,10 @@ import type {
CreateGuildChannel,
DiscordCreateGuildChannel,
} from "../../types/guilds/create_guild_channel.ts";
import type { PermissionStrings } from "../../types/permissions/permission_strings.ts";
import { endpoints } from "../../util/constants.ts";
import {
calculateBits,
requireBotGuildPermissions,
requireOverwritePermissions,
} from "../../util/permissions.ts";
import { camelKeysToSnakeCase } from "../../util/utils.ts";
@@ -22,18 +20,12 @@ export async function createChannel(
options?: CreateGuildChannel,
reason?: string,
) {
const requiredPerms: Set<PermissionStrings> = new Set(["MANAGE_CHANNELS"]);
options?.permissionOverwrites?.forEach((overwrite) => {
eventHandlers.debug?.(
"loop",
`Running forEach loop in create_channel file.`,
if (options?.permissionOverwrites) {
await requireOverwritePermissions(
guildId,
options.permissionOverwrites,
);
overwrite.allow.forEach(requiredPerms.add, requiredPerms);
overwrite.deny.forEach(requiredPerms.add, requiredPerms);
});
await requireBotGuildPermissions(guildId, [...requiredPerms]);
}
// BITRATES ARE IN THOUSANDS SO IF USER PROVIDES 32 WE CONVERT TO 32000
if (options?.bitrate && options.bitrate < 1000) options.bitrate *= 1000;
@@ -48,7 +40,7 @@ export async function createChannel(
allow: calculateBits(perm.allow),
deny: calculateBits(perm.deny),
})),
type: options?.type || DiscordChannelTypes.GUILD_TEXT,
type: options?.type || DiscordChannelTypes.GuildText,
reason,
},
);
+23 -10
View File
@@ -1,26 +1,39 @@
import { cacheHandlers } from "../../cache.ts";
import { rest } from "../../rest/rest.ts";
import { Errors } from "../../types/misc/errors.ts";
import { ChannelTypes } from "../../types/mod.ts";
import { endpoints } from "../../util/constants.ts";
import { requireBotGuildPermissions } from "../../util/permissions.ts";
/** Delete a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
export async function deleteChannel(
guildId: bigint,
channelId: bigint,
reason?: string,
): Promise<undefined> {
await requireBotGuildPermissions(guildId, ["MANAGE_CHANNELS"]);
) {
const channel = await cacheHandlers.get("channels", channelId);
const guild = await cacheHandlers.get("guilds", guildId);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
if (channel?.guildId) {
const guild = await cacheHandlers.get("guilds", channel.guildId);
if (!guild) throw new Error(Errors.GUILD_NOT_FOUND);
if (guild?.rulesChannelId === channelId) {
throw new Error(Errors.RULES_CHANNEL_CANNOT_BE_DELETED);
}
// TODO(threads): check if this requires guild perms or channel is enough
await requireBotGuildPermissions(
guild,
[
ChannelTypes.GuildNewsThread,
ChannelTypes.GuildPivateThread,
ChannelTypes.GuildPublicThread,
].includes(channel.type)
? ["MANAGE_THREADS"]
: ["MANAGE_CHANNELS"],
);
if (guild.rulesChannelId === channelId) {
throw new Error(Errors.RULES_CHANNEL_CANNOT_BE_DELETED);
}
if (guild?.publicUpdatesChannelId === channelId) {
throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED);
if (guild.publicUpdatesChannelId === channelId) {
throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED);
}
}
return await rest.runMethod<undefined>(
+60 -17
View File
@@ -1,22 +1,68 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { rest } from "../../rest/rest.ts";
import type { Channel } from "../../types/channels/channel.ts";
import { DiscordChannelTypes } from "../../types/channels/channel_types.ts";
import type { ModifyChannel } from "../../types/channels/modify_channel.ts";
import type { ModifyThread } from "../../types/channels/threads/modify_thread.ts";
import type { PermissionStrings } from "../../types/permissions/permission_strings.ts";
import { endpoints } from "../../util/constants.ts";
import {
calculateBits,
requireBotChannelPermissions,
requireOverwritePermissions,
} from "../../util/permissions.ts";
import { camelKeysToSnakeCase, hasOwnProperty } from "../../util/utils.ts";
//TODO: implement DM group channel edit
//TODO(threads): check thread perms
/** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */
export async function editChannel(
channelId: bigint,
options: ModifyChannel,
options: ModifyChannel | ModifyThread,
reason?: string,
) {
await requireBotChannelPermissions(channelId, ["MANAGE_CHANNELS"]);
const channel = await cacheHandlers.get("channels", channelId);
if (options.name || options.topic) {
if (channel) {
if (
[
DiscordChannelTypes.GuildNewsThread,
DiscordChannelTypes.GuildPivateThread,
DiscordChannelTypes.GuildPublicThread,
].includes(channel.type)
) {
const permissions = new Set<PermissionStrings>();
if (hasOwnProperty(options, "archive") && options.archive === false) {
permissions.add("SEND_MESSAGES");
}
// TODO(threads): change this to a better check
// hacky way of checking if more is being modified
if (Object.keys(options).length > 1) {
permissions.add("MANAGE_THREADS");
}
await requireBotChannelPermissions(channel.parentId ?? 0n, [
...permissions,
]);
}
if (
hasOwnProperty<ModifyChannel>(
options,
"permissionOverwrites",
) && Array.isArray(options.permissionOverwrites)
) {
await requireOverwritePermissions(
channel.guildId,
options.permissionOverwrites,
);
}
}
if (options.name || (options as ModifyChannel).topic) {
const request = editChannelNameTopicQueue.get(channelId);
if (!request) {
// If this hasnt been done before simply add 1 for it
@@ -42,21 +88,18 @@ export async function editChannel(
}
const payload = {
...options,
...camelKeysToSnakeCase<Record<string, unknown>>(options),
// deno-lint-ignore camelcase
rate_limit_per_user: options.rateLimitPerUser,
// deno-lint-ignore camelcase
parent_id: options.parentId,
// deno-lint-ignore camelcase
user_limit: options.userLimit,
// deno-lint-ignore camelcase
permission_overwrites: options.permissionOverwrites?.map((overwrite) => {
return {
...overwrite,
allow: calculateBits(overwrite.allow),
deny: calculateBits(overwrite.deny),
};
}),
permission_overwrites:
hasOwnProperty<ModifyChannel>(options, "permissionOverwrites")
? options.permissionOverwrites?.map((overwrite) => {
return {
...overwrite,
allow: calculateBits(overwrite.allow),
deny: calculateBits(overwrite.deny),
};
})
: undefined,
};
return await rest.runMethod<Channel>(
+6 -3
View File
@@ -16,9 +16,12 @@ export async function startTyping(channelId: bigint) {
if (channel) {
if (
![
DiscordChannelTypes.DM,
DiscordChannelTypes.GUILD_NEWS,
DiscordChannelTypes.GUILD_TEXT,
DiscordChannelTypes.Dm,
DiscordChannelTypes.GuildNews,
DiscordChannelTypes.GuildText,
DiscordChannelTypes.GuildNewsThread,
DiscordChannelTypes.GuildPivateThread,
DiscordChannelTypes.GuildPublicThread,
].includes(channel.type)
) {
throw new Error(Errors.CHANNEL_NOT_TEXT_BASED);
@@ -0,0 +1,35 @@
import { cacheHandlers } from "../../../cache.ts";
import { rest } from "../../../rest/rest.ts";
import { ChannelTypes, Errors } from "../../../types/mod.ts";
import { endpoints } from "../../../util/constants.ts";
//TODO(threads): this does not work rn
/** Adds the current user to a thread. Returns a 204 empty response on success. Also requires the thread is not archived. Fires a Thread Members Update Gateway event.Adds another user to a thread. Requires the ability to send messages in the thread. Also requires the thread is not archived. Returns a 204 empty response on success. Fires a Thread Members Update Gateway event.
* @param userId the user to add to the thread defaults to bot
*/
export async function addToThread(channelId: bigint, userId?: bigint) {
// TODO(threads): perm check
const channel = await cacheHandlers.get("channels", channelId);
if (channel) {
if (
![
ChannelTypes.GuildNewsThread,
ChannelTypes.GuildPivateThread,
ChannelTypes.GuildPublicThread,
].includes(channel.type)
) {
throw new Error(Errors.NOT_A_THREAD_CHANNEL);
}
}
console.log(
"put",
userId
? endpoints.THREAD_USER(channelId, userId)
: endpoints.THREAD_ME(channelId),
);
return await rest.runMethod(
"put",
userId
? endpoints.THREAD_USER(channelId, userId)
: endpoints.THREAD_ME(channelId),
);
}
@@ -0,0 +1,12 @@
import { rest } from "../../../rest/rest.ts";
import { endpoints } from "../../../util/constants.ts";
/** Returns all active threads in the channel, including public and private threads. Threads are ordered by their id, in descending order. Requires the READ_MESSAGE_HISTORY permission. */
export async function getActiveThreads(channelId: bigint) {
// TODO(threads): perm check
// TODO(threads): test if it works
return await rest.runMethod(
"get",
endpoints.THREAD_ACTIVE(channelId),
);
}
@@ -0,0 +1,24 @@
import { rest } from "../../../rest/rest.ts";
import { ListPublicArchivedThreads } from "../../../types/channels/threads/list_public_archived_threads.ts";
import { endpoints } from "../../../util/constants.ts";
import { camelKeysToSnakeCase } from "../../../util/utils.ts";
export async function getArchivedThreads(
channelId: bigint,
options?: ListPublicArchivedThreads & {
type?: "public" | "private" | "privateJoinedThreads";
},
) {
// TODO(threads): perm check
// TODO(threads): check if this works
return await rest.runMethod(
"get",
options?.type === "privateJoinedThreads"
? endpoints.THREAD_ARCHIVED_PRIVATE_JOINED(channelId)
: options?.type === "private"
? endpoints.THREAD_ARCHIVED_PRIVATE(channelId)
: endpoints.THREAD_ARCHIVED_PUBLIC(channelId),
camelKeysToSnakeCase(options ?? {}),
);
}
@@ -0,0 +1,26 @@
import { cacheHandlers } from "../../../cache.ts";
import { rest } from "../../../rest/rest.ts";
import { ChannelTypes } from "../../../types/channels/channel_types.ts";
import { Errors } from "../../../types/misc/errors.ts";
import { endpoints } from "../../../util/constants.ts";
// TODO(threads): it seems like the documented return type is wrong
/** Returns array of thread members objects that are members of the thread. */
export async function getThreadMembers(channelId: bigint) {
// TODO(threads): perm check
// TODO(threads): intents check
const channel = await cacheHandlers.get("channels", channelId);
if (channel) {
if (
![
ChannelTypes.GuildNewsThread,
ChannelTypes.GuildPivateThread,
ChannelTypes.GuildPublicThread,
].includes(channel.type)
) {
throw new Error(Errors.NOT_A_THREAD_CHANNEL);
}
}
return await rest.runMethod("get", endpoints.THREAD_MEMBERS(channelId));
}
@@ -0,0 +1,29 @@
import { cacheHandlers } from "../../../cache.ts";
import { rest } from "../../../rest/rest.ts";
import { ChannelTypes } from "../../../types/channels/channel_types.ts";
import { Errors } from "../../../types/misc/errors.ts";
import { endpoints } from "../../../util/constants.ts";
/** Removes another user from a thread. Requires the MANAGE_THREADS permission or that you are the creator of the thread. Also requires the thread is not archived. Returns a 204 empty response on success. Fires a Thread Members Update Gateway event. */
export async function removeFromThread(channelId: bigint, userId?: bigint) {
// TODO(threads): perm check
const channel = await cacheHandlers.get("channels", channelId);
if (channel) {
if (
![
ChannelTypes.GuildNewsThread,
ChannelTypes.GuildPivateThread,
ChannelTypes.GuildPublicThread,
].includes(channel.type)
) {
throw new Error(Errors.NOT_A_THREAD_CHANNEL);
}
}
return await rest.runMethod(
"delete",
userId
? endpoints.THREAD_USER(channelId, userId)
: endpoints.THREAD_ME(channelId),
);
}
@@ -0,0 +1,38 @@
import { cacheHandlers } from "../../../cache.ts";
import { rest } from "../../../rest/rest.ts";
import { ChannelTypes } from "../../../types/channels/channel_types.ts";
import { StartThread } from "../../../types/channels/threads/start_thread.ts";
import { Errors } from "../../../types/misc/errors.ts";
import { endpoints } from "../../../util/constants.ts";
import { camelKeysToSnakeCase } from "../../../util/utils.ts";
/**
* Creates a new public thread from an existing message. Returns a channel on success, and a 400 BAD REQUEST on invalid parameters. Fires a Thread Create Gateway event.
* @param messageId when provided the thread will be public
*/
export async function startThread(
channelId: bigint,
options: StartThread & { messageId?: bigint },
) {
const channel = await cacheHandlers.get("channels", channelId);
if (channel) {
// TODO(threads): perm check
if (
![ChannelTypes.GuildText, ChannelTypes.GuildNews].includes(channel.type)
) {
throw new Error(Errors.INVALID_THREAD_PARENT_CHANNEL_TYPE);
}
if (!options.messageId && channel.type === ChannelTypes.GuildNews) {
throw new Error(Errors.GUILD_NEWS_CHANNEL_ONLY_SUPPORT_PUBLIC_THREADS);
}
}
return rest.runMethod(
"post",
options?.messageId
? endpoints.THREAD_START_PUBLIC(channelId, options.messageId)
: endpoints.THREAD_START_PRIVATE(channelId),
camelKeysToSnakeCase(options),
);
}
+6 -3
View File
@@ -26,9 +26,12 @@ export async function sendMessage(
if (channel) {
if (
![
DiscordChannelTypes.DM,
DiscordChannelTypes.GUILD_NEWS,
DiscordChannelTypes.GUILD_TEXT,
DiscordChannelTypes.Dm,
DiscordChannelTypes.GuildNews,
DiscordChannelTypes.GuildText,
DiscordChannelTypes.GuildPublicThread,
DiscordChannelTypes.GuildPivateThread,
DiscordChannelTypes.GuildNewsThread,
].includes(channel.type)
) {
throw new Error(Errors.CHANNEL_NOT_TEXT_BASED);
+3 -3
View File
@@ -62,8 +62,8 @@ const baseChannel: Partial<DiscordenoChannel> = {
disconnect(memberId) {
return disconnectMember(this.guildId!, memberId);
},
delete() {
return deleteChannel(this.guildId!, this.id!);
delete(reason) {
return deleteChannel(this.id!, reason);
},
editOverwrite(id, options) {
return editChannelOverwrite(
@@ -204,7 +204,7 @@ export interface DiscordenoChannel extends
/** Disconnect a member from a voice channel. Requires MOVE_MEMBERS permission. */
disconnect(memberId: bigint): ReturnType<typeof disconnectMember>;
/** Delete the channel */
delete(): ReturnType<typeof deleteChannel>;
delete(reason?: string): ReturnType<typeof deleteChannel>;
/** Edit a channel Overwrite */
editOverwrite(
overwriteId: bigint,
+2 -1
View File
@@ -158,6 +158,7 @@ export async function createDiscordenoGuild(
memberCount = 0,
voiceStates = [],
channels = [],
threads = [],
presences = [],
joinedAt = "",
emojis,
@@ -181,7 +182,7 @@ export async function createDiscordenoGuild(
voiceStates.map((vs) => structures.createDiscordenoVoiceState(guildId, vs)),
);
await Promise.all(channels.map(async (channel) => {
await Promise.all([...channels, ...threads].map(async (channel) => {
const discordenoChannel = await structures.createDiscordenoChannel(
channel,
guildId,
+13 -2
View File
@@ -1,6 +1,8 @@
import { User } from "../users/user.ts";
import { DiscordChannelTypes } from "./channel_types.ts";
import { DiscordOverwrite } from "./overwrite.ts";
import { ThreadMember } from "./threads/thread_member.ts";
import { ThreadMetadata } from "./threads/thread_metadata.ts";
import { DiscordVideoQualityModes } from "./video_quality_modes.ts";
/** https://discord.com/developers/docs/resources/channel#channel-object */
@@ -33,11 +35,11 @@ export interface Channel {
recipients?: User[];
/** Icon hash */
icon?: string | null;
/** id of the DM creator */
/** Id of the creator of the group DM or thread */
ownerId?: string;
/** Application id of the group DM creator if it is bot-created */
applicationId?: string;
/** Id of the parent category for a channel (each parent category can contain up to 50 channels) */
/** For guild channels: Id of the parent category for a channel (each parent category can contain up to 50 channels), for threads: id of the text channel this thread was created */
parentId?: string | null;
/** When the last pinned message was pinned. This may be null in events such as GUILD_CREATE when a message is not pinned. */
lastPinTimestamp?: string | null;
@@ -45,4 +47,13 @@ export interface Channel {
rtcRegion?: string | null;
/** The camera video quality mode of the voice channel, 1 when not present */
videoQualityMode?: DiscordVideoQualityModes;
// TODO(threads): consider a ThreadChannel object
/** An approximate count of messages in a thread, stops counting at 50 */
messageCount?: number;
/** An approximate count of users in a thread, stops counting at 50 */
memberCount?: number;
/** Thread-specifig fields not needed by other channels */
threadMetadata?: ThreadMetadata;
/** Thread member object for the current user, if they have joined the thread, only included on certain API endpoints */
member?: ThreadMember;
}
+14 -8
View File
@@ -1,21 +1,27 @@
/** https://discord.com/developers/docs/resources/channel#channel-object-channel-types */
export enum DiscordChannelTypes {
/** A text channel within a server */
GUILD_TEXT,
GuildText,
/** A direct message between users */
DM,
Dm,
/** A voice channel within a server */
GUILD_VOICE,
GuildVoice,
/** A direct message between multiple users */
GROUP_DM,
GroupDm,
/** An organizational category that contains up to 50 channels */
GUILD_CATEGORY,
GuildCategory,
/** A channel that users can follow and crosspost into their own server */
GUILD_NEWS,
GuildNews,
/** A channel in which game developers can sell their game on Discord */
GUILD_STORE,
GuildStore,
/** A temporary sub-channel within a GUILD_NEWS channel */
GuildNewsThread = 10,
/** A temporary sub-channel within a GUILD_TEXT channel */
GuildPublicThread,
/** A temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission */
GuildPivateThread,
/** A voice channel for hosting events with an audience */
GUILD_STAGE_VOICE = 13,
GuildStageVoice = 13,
}
export type ChannelTypes = DiscordChannelTypes;
@@ -0,0 +1,12 @@
import { Channel } from "../channel.ts";
import { ThreadMember } from "./thread_member.ts";
// TODO: add docs link
export interface ListActiveThreads {
/** The active threads */
threads: Channel[];
/** A thread member object for each returned thread the current user has joined */
members: ThreadMember[];
/** Whether there are potentially additional threads that could be returned on subsequent call */
hasMore: boolean;
}
@@ -0,0 +1,8 @@
// TODO: add docs link
export interface ListPublicArchivedThreads {
// TODO: convert unix to ISO9601 timestamp
/** Returns threads before this timestamp. UNIX or ISO8601 timestamp */
before?: number | string;
/** Optional maximum number of threads to return */
limit?: number;
}
@@ -0,0 +1,13 @@
// TODO: add docs link
export interface ModifyThread {
/** 2-100 character thread name */
name?: string;
/** Whether the thread is archived */
archived?: boolean;
/** Duration in minutes to automatically archive the thread after recent activity */
autoArchiveDuration?: 60 | 1440 | 4320 | 10080;
/** When a thread is locked, only users with `MANAGE_THREADS` can unarchive it */
locked?: boolean;
/** Amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission `MANAGE_MESSAGES`, `MANAGE_THREAD` or `MANAGE_CHANNEL` are unaffected */
rateLimitPerUser?: number;
}
@@ -0,0 +1,7 @@
// TODO: add docs link
export interface StartThread {
/** 2-100 character thread name */
name: string;
/** Duration in minutes to automatically archive the thread after recent activity */
autoArchiveDuration: 60 | 1440 | 4320 | 10080;
}
@@ -0,0 +1,15 @@
import { Channel } from "../channel.ts";
import { ThreadMember } from "./thread_member.ts";
// TODO: add docs link
export interface ThreadListSync {
/** The id of the guild */
guildId: string;
/** The parent channel ids whose threads are being synced. If omitted, then threads were synced for the entire guild. This array may contain channelIds that have no active threads as well, so you know to clear that data */
channelIds?: string[];
// TODO: check if need to omit
/** All active threads in the given channels that the current user can access */
threads: Channel[];
/** All thread member objects from the synced threads for the current user, indicating which threads the current user has been added to */
members: ThreadMember[];
}
@@ -0,0 +1,10 @@
export interface ThreadMember {
/** The id of the thread */
id: string;
/** The id of the user */
userId: string;
/** The time the current user last joined the thread */
joinTimestamp: string;
/** Any user-thread settings, currently only used for notifications */
flags: number;
}
@@ -0,0 +1,15 @@
import { ThreadMember } from "./thread_member.ts";
// TODO: add docs link
export interface ThreadMembersUpdate {
/** The id of the thread */
id: string;
/** The id of the guild */
guildId: string;
/** The approximate number of members in the thread, capped at 50 */
memberCount: number;
/** The users who were added to the thread */
addedMembers?: ThreadMember[];
/** The id of the users who were removed from the thread */
removedMemberIds?: string[];
}
@@ -0,0 +1,13 @@
export interface ThreadMetadata {
/** Whether the thread is archived */
archived: boolean;
/** Id of the user that last archived or unarchived the thread */
archiverId?: string;
/** Duration in minutes to automatically archive the thread after recent activity */
autoArchiveDuration: 60 | 1440 | 4320 | 10080;
// TODO(threads): channel struct should convert this to a unixx
/** Timestamp when the thread's archive status was last changed, used for calculating recent activity */
archiveTimestamp: string;
/** When a thread is locked, only users with `MANAGE_THREADS` can unarchive it */
locked?: boolean;
}
+5
View File
@@ -80,6 +80,11 @@ export enum DiscordJsonErrorCodes {
InvalidApiVersionProvided = 50041,
CannotDeleteAChannelRequiredForCommunityGuilds = 50074,
InvalidStickerSent = 50081,
TriedToPerformAnOperationOnAnArchivedThreadSuchAsEditingAMessageOrAddingAUserToTheThread =
50083,
InvalidThreadNotificationSettings,
BeforeValueIsEarlierThanTheThreadCreationDate,
TwoFactorIsRequiredForThisOperation = 60003,
ReqctionWasBlocked = 90001,
ApiResourceIsCurrentlyOverloadedTryAgainALittleLater = 130000,
}
+21
View File
@@ -4,6 +4,8 @@ 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 { ThreadMember } from "../channels/threads/thread_member.ts";
import { ThreadMembersUpdate } from "../channels/threads/thread_members_update.ts";
import { IntegrationCreateUpdate } from "../integration/integration_create_update.ts";
import { ApplicationCommandCreateUpdateDelete } from "../interactions/application_command_create_update_delete.ts";
import {
@@ -211,6 +213,25 @@ export interface EventHandlers {
shardId: number,
unavailableGuildIds: Set<bigint>,
) => unknown;
/** Sent when a thread is created */
threadCreate?: (channel: DiscordenoChannel) => unknown;
/** Sent when a thread is updated */
threadUpdate?: (
cahnnel: DiscordenoChannel,
oldChannel: DiscordenoChannel,
) => unknown;
/** Sent when the bot gains access to threads */
threadListSync?: (
channels: Collection<bigint, DiscordenoChannel>,
members: ThreadMember[],
guildId: bigint,
) => unknown;
/** Sent when the current users thread member is updated */
threadMemberUpdate?: (threadMember: ThreadMember) => unknown;
/** Sent when anyone is added to or removed from a thread */
threadMembersUpdate?: (update: ThreadMembersUpdate) => unknown;
/** Sent when a thread is deleted */
threadDelete?: (channel: DiscordenoChannel) => unknown;
/** Sent when a user starts typing in a channel. */
typingStart?: (data: TypingStart) => unknown;
/** Sent when a user joins a voice channel */
+3
View File
@@ -79,6 +79,9 @@ export interface Guild {
members?: GuildMember[];
/** Channels in the guild */
channels?: Channel[];
// TODO: check if need to omit
/** All active threads in the guild that the current user has permission to view */
threads?: Channel[];
/** Presences of the members in the guild, will only include non-offline members if the size is greater than large threshold */
presences?: Partial<PresenceUpdate>[];
/** The maximum number of presences for the guild (the default value, currently 25000, is in effect when null is returned) */
+4
View File
@@ -1,4 +1,6 @@
import { Channel } from "../channels/channel.ts";
import { ChannelMention } from "../channels/channel_mention.ts";
import { ThreadMember } from "../channels/threads/thread_member.ts";
import { Embed } from "../embeds/embed.ts";
import { GuildMember } from "../guilds/guild_member.ts";
import { MessageInteraction } from "../interactions/message_interaction.ts";
@@ -83,6 +85,8 @@ export interface Message {
referencedMessage?: Message;
/** Sent if the message is a response to an Interaction */
interaction?: MessageInteraction;
/** The thread that was started from this message, includes thread member object */
thread?: Omit<Channel, "member"> & { member: ThreadMember };
/** The components related to this message */
components: MessageComponents;
}
+9 -7
View File
@@ -1,19 +1,21 @@
/** https://discord.com/developers/docs/resources/channel#message-object-message-flags */
export enum DiscordMessageFlags {
/** This message has been published to subscribed channels (via Channel Following) */
CROSSPOSTED = 1 << 0,
Crossposted = 1 << 0,
/** This message originated from a message in another channel (via Channel Following) */
IS_CROSSPOST = 1 << 1,
IsCrosspost = 1 << 1,
/** Do not include any embeds when serializing this message */
SUPPRESS_EMBEDS = 1 << 2,
SuppressEmbeeds = 1 << 2,
/** The source message for this crosspost has been deleted (via Channel Following) */
SOURCE_MESSAGE_DELETED = 1 << 3,
SourceMessageDeleted = 1 << 3,
/** This message came from the urgent message system */
URGENT = 1 << 4,
Urgent = 1 << 4,
/** This message has an associated thread, with the same id as the message */
HasThread = 1 << 5,
/** This message is only visible to the user who invoked the Interaction */
EMPHERAL = 1 << 6,
Empheral = 1 << 6,
/** This message is an Interaction Response and the bot is "thinking" */
LOADING = 1 << 7,
Loading = 1 << 7,
}
export type MessageFlags = DiscordMessageFlags;
+21 -20
View File
@@ -1,25 +1,26 @@
/** https://discord.com/developers/docs/resources/channel#message-object-message-types */
export enum DiscordMessageTypes {
DEFAULT,
RECIPIENT_ADD,
RECIPIENT_REMOVE,
CALL,
CHANNEL_NAME_CHANGE,
CHANNEL_ICON_CHANGE,
CHANNEL_PINNED_MESSAGE,
GUILD_MEMBER_JOIN,
USER_PREMIUM_GUILD_SUBSCRIPTION,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2,
USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3,
CHANNEL_FOLLOW_ADD,
GUILD_DISCOVERY_DISQUALIFIED = 14,
GUILD_DISCOVERY_REQUALIFIED,
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING,
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING,
REPLY = 19,
APPLICATION_COMMAND,
GUILD_INVITE_REMINDER = 22,
Default,
RecipientAdd,
RecipientRemove,
Call,
ChannelNameChange,
ChannelIconChange,
ChannelPinnedMessage,
GuildMemberJoin,
UserPremiumGuildSubscription,
UserPremiumGuildSubscriptionTier1,
UserPremiumGuildSubscriptionTier2,
UserPremiumGuildSubscriptionTier3,
ChannelFollowAdd,
GuildDiscoveryDisqualified = 14,
GuildDiscoveryRequalified,
GuildDiscoveryGracePeriodInitialWarning,
GuildDiscoveryGracePeriodFinalWarning,
ThreadCreated,
Reply = 19,
ApplicationCommand,
GuildInviteReminder = 22,
}
export type MessageTypes = DiscordMessageTypes;
+5
View File
@@ -18,6 +18,11 @@ export enum Errors {
MEMBER_SEARCH_LIMIT_TOO_LOW = "MEMBER_SEARCH_LIMIT_TOO_LOW",
PRUNE_MAX_DAYS = "PRUNE_MAX_DAYS",
ROLE_NOT_FOUND = "ROLE_NOT_FOUND",
// Thread errors
INVALID_THREAD_PARENT_CHANNEL_TYPE = "INVALID_THREAD_PARENT_CHANNEL_TYPE",
GUILD_NEWS_CHANNEL_ONLY_SUPPORT_PUBLIC_THREADS =
"GUILD_NEWS_CHANNEL_ONLY_SUPPORT_PUBLIC_THREADS",
NOT_A_THREAD_CHANNEL = "NOT_A_THREAD_CHANNEL",
// Message Delete Errors
DELETE_MESSAGES_MIN = "DELETE_MESSAGES_MIN",
PRUNE_MIN_DAYS = "PRUNE_MIN_DAYS",
@@ -65,7 +65,13 @@ export enum DiscordBitwisePermissionFlags {
/** Allows members to use slash commands in text channels */
USE_SLASH_COMMANDS = 0x80000000,
/** Allows for requesting to speak in stage channels. */
REQUEST_TO_SPEAK = 0x100000000,
REQUEST_TO_SPEAK = 0x100000001,
/** Allows for deleting and archiving threads, and viewing all private threads */
MANAGE_THREADS = 0x0400000000,
/** Allows for creating and participating in threads */
USE_PUBLIC_THREADS = 0x0800000000,
/** Allows for creating and participating in private threads */
USE_PRIVATE_THREADS = 0x1000000000,
}
export type BitwisePermissions = DiscordBitwisePermissionFlags;
+26 -3
View File
@@ -2,13 +2,14 @@
export const BASE_URL = "https://discord.com/api";
/** https://discord.com/developers/docs/reference#api-versioning-api-versions */
export const API_VERSION = 8;
export const API_VERSION = 9;
/** https://discord.com/developers/docs/topics/gateway#gateways-gateway-versions */
export const GATEWAY_VERSION = 8;
export const GATEWAY_VERSION = 9;
// TODO: update this version
/** https://github.com/discordeno/discordeno/releases */
export const DISCORDENO_VERSION = 11;
export const DISCORDENO_VERSION = "11.0.0";
/** https://discord.com/developers/docs/reference#user-agent */
export const USER_AGENT =
@@ -77,6 +78,28 @@ export const endpoints = {
// Bots SHALL NOT use this endpoint but they can
CHANNEL_TYPING: (channelId: bigint) => `${CHANNEL_BASE(channelId)}/typing`,
// Thread Endpoints
THREAD_START_PUBLIC: (channelId: bigint, messageId: bigint) =>
`${endpoints.CHANNEL_MESSAGE(channelId, messageId)}/threads`,
THREAD_START_PRIVATE: (channelId: bigint) =>
`${CHANNEL_BASE(channelId)}/threads`,
THREAD_ACTIVE: (channelId: bigint) =>
`${CHANNEL_BASE(channelId)}/threads/active`,
THREAD_MEMBERS: (channelId: bigint) =>
`${CHANNEL_BASE(channelId)}/thread-members`,
THREAD_ME: (channelId: bigint) =>
`${endpoints.THREAD_MEMBERS(channelId)}/@me`,
THREAD_USER: (channelId: bigint, userId: bigint) =>
`${endpoints.THREAD_MEMBERS(channelId)}/${userId}`,
THREAD_ARCHIVED_BASE: (channelId: bigint) =>
`${CHANNEL_BASE(channelId)}/threads/archived`,
THREAD_ARCHIVED_PUBLIC: (channelId: bigint) =>
`${endpoints.THREAD_ARCHIVED_BASE(channelId)}/public`,
THREAD_ARCHIVED_PRIVATE: (channelId: bigint) =>
`${endpoints.THREAD_ARCHIVED_BASE(channelId)}/private`,
THREAD_ARCHIVED_PRIVATE_JOINED: (channelId: bigint) =>
`${CHANNEL_BASE(channelId)}/users/@me/threads/archived/private`,
// Guild Endpoints
GUILDS: `${baseEndpoints.BASE_URL}/guilds`,
GUILD_AUDIT_LOGS: (guildId: bigint) => `${GUILDS_BASE(guildId)}/audit-logs`,
+27 -11
View File
@@ -4,6 +4,7 @@ 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 { Overwrite } from "../types/channels/overwrite.ts";
import { Errors } from "../types/misc/errors.ts";
import { DiscordBitwisePermissionFlags } from "../types/permissions/bitwise_permission_flags.ts";
import type { PermissionStrings } from "../types/permissions/permission_strings.ts";
@@ -28,11 +29,6 @@ async function getCached(
? // @ts-ignore TS is wrong here
await cacheHandlers.get(table, key)
: key;
if (!cached || typeof cached === "bigint") {
throw new Error(
Errors[`${table.slice(0, -1).toUpperCase()}_NOT_FOUND` as Errors],
);
}
return typeof cached === "bigint" ? undefined : cached;
}
@@ -50,7 +46,7 @@ export async function calculateBasePermissions(
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 DiscordenoGuild).roles.get(id)?.permissions)
.map((id) => guild.roles.get(id)?.permissions)
// Removes any edge case undefined
.filter((perm) => perm)
.reduce((bits, perms) => {
@@ -76,7 +72,7 @@ export async function calculateChannelOverwrites(
const member = await getCached("members", memberOrId);
if (!member) return "8";
if (!channel || !member) return "8";
// Get all the role permissions this member already has
let permissions = BigInt(
@@ -84,8 +80,8 @@ export async function calculateChannelOverwrites(
);
// First calculate @everyone overwrites since these have the lowest priority
const overwriteEveryone = channel?.permissionOverwrites?.find(
(overwrite) => overwrite.id === (channel as DiscordenoChannel).guildId,
const overwriteEveryone = channel.permissionOverwrites?.find(
(overwrite) => overwrite.id === channel.guildId,
);
if (overwriteEveryone) {
// First remove denied permissions since denied < allowed
@@ -93,7 +89,7 @@ export async function calculateChannelOverwrites(
permissions |= BigInt(overwriteEveryone.allow);
}
const overwrites = channel?.permissionOverwrites;
const overwrites = channel.permissionOverwrites;
// In order to calculate the role permissions correctly we need to temporarily save the allowed and denied permissions
let allow = 0n;
@@ -112,7 +108,7 @@ export async function calculateChannelOverwrites(
// Third calculate member specific overwrites since these have the highest priority
const overwriteMember = overwrites?.find(
(overwrite) => overwrite.id === (member as DiscordenoMember).id,
(overwrite) => overwrite.id === member.id,
);
if (overwriteMember) {
permissions &= ~BigInt(overwriteMember.deny);
@@ -290,6 +286,26 @@ export function calculateBits(permissions: PermissionStrings[]) {
.toString();
}
/** Internal function to check if the bot has the permissions to set these overwrites */
export async function requireOverwritePermissions(
guildOrId: bigint | DiscordenoGuild,
overwrites: Overwrite[],
) {
let requiredPerms: Set<PermissionStrings> = new Set(["MANAGE_CHANNELS"]);
overwrites?.forEach((overwrite) => {
overwrite.allow.forEach(requiredPerms.add, requiredPerms);
overwrite.deny.forEach(requiredPerms.add, requiredPerms);
});
// MANAGE_ROLES permission can only be set by administrators
if (requiredPerms.has("MANAGE_ROLES")) {
requiredPerms = new Set<PermissionStrings>(["ADMINISTRATOR"]);
}
await requireGuildPermissions(guildOrId, botId, [...requiredPerms]);
}
/** Gets the highest role from the member in this guild */
export async function highestRole(
guildOrId: bigint | DiscordenoGuild,
+12
View File
@@ -204,3 +204,15 @@ export function validateSlashCommands(
}
}
}
// Typescript is not so good as we developers so we need this little utility function to help it out
// Taken from https://fettblog.eu/typescript-hasownproperty/
/** TS save way to check if a property exists in an object */
// deno-lint-ignore ban-types
export function hasOwnProperty<T extends {}, Y extends PropertyKey = string>(
obj: T,
prop: Y,
): obj is T & Record<Y, unknown> {
// deno-lint-ignore no-prototype-builtins
return obj.hasOwnProperty(prop);
}
+4 -4
View File
@@ -1,17 +1,17 @@
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
import { assertExists } from "../deps.ts";
import { cache } from "../../src/cache.ts";
import { categoryChildren, createChannel } from "../../src/helpers/mod.ts";
import { DiscordChannelTypes } from "../../src/types/channels/channel_types.ts";
import { delayUntil } from "../util/delay_until.ts";
import { bigintToSnowflake } from "../../src/util/bigint.ts";
import { assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
Deno.test({
name: "[channel] category channel ids",
async fn() {
const category = await createChannel(tempData.guildId, {
name: "Discordeno-test",
type: DiscordChannelTypes.GUILD_CATEGORY,
type: DiscordChannelTypes.GuildCategory,
});
// Assertions
@@ -15,7 +15,7 @@ async function ifItFailsBlameWolf(options: CreateGuildChannel) {
// Assertions
assertExists(channel);
assertEquals(channel.type, options.type || DiscordChannelTypes.GUILD_TEXT);
assertEquals(channel.type, options.type || DiscordChannelTypes.GuildText);
// Delay the execution by 5 seconds to allow CHANNEL_CREATE event to be processed
await delayUntil(10000, () => cache.channels.has(channel.id));
+4 -4
View File
@@ -61,7 +61,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-clone-test",
type: DiscordChannelTypes.GUILD_CATEGORY,
type: DiscordChannelTypes.GuildCategory,
},
);
},
@@ -74,7 +74,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-clone-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
},
);
},
@@ -87,7 +87,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "discordeno-clone-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
bitrate: 32000,
},
);
@@ -101,7 +101,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-clone-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
userLimit: 32,
},
);
+13 -13
View File
@@ -1,20 +1,20 @@
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
import { assertEquals, assertExists } from "../deps.ts";
import { cache } from "../../src/cache.ts";
import { DiscordChannelTypes } from "../../src/types/channels/channel_types.ts";
import { CreateGuildChannel } from "../../src/types/guilds/create_guild_channel.ts";
import { createChannel } from "../../src/helpers/channels/create_channel.ts";
import { delayUntil } from "../util/delay_until.ts";
import { DiscordOverwriteTypes } from "../../src/types/channels/overwrite_types.ts";
import { botId } from "../../src/bot.ts";
import { cache } from "../../src/cache.ts";
import { createChannel } from "../../src/helpers/channels/create_channel.ts";
import { DiscordChannelTypes } from "../../src/types/channels/channel_types.ts";
import { DiscordOverwriteTypes } from "../../src/types/channels/overwrite_types.ts";
import { CreateGuildChannel } from "../../src/types/guilds/create_guild_channel.ts";
import { bigintToSnowflake } from "../../src/util/bigint.ts";
import { assertEquals, assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
async function ifItFailsBlameWolf(options: CreateGuildChannel, save = false) {
const channel = await createChannel(tempData.guildId, options);
// Assertions
assertExists(channel);
assertEquals(channel.type, options.type || DiscordChannelTypes.GUILD_TEXT);
assertEquals(channel.type, options.type || DiscordChannelTypes.GuildText);
if (save) tempData.channelId = channel.id;
@@ -61,7 +61,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-test",
type: DiscordChannelTypes.GUILD_CATEGORY,
type: DiscordChannelTypes.GuildCategory,
},
true,
);
@@ -91,7 +91,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
},
true,
);
@@ -105,7 +105,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "discordeno-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
bitrate: 32000,
},
true,
@@ -120,7 +120,7 @@ Deno.test({
await ifItFailsBlameWolf(
{
name: "Discordeno-test",
type: DiscordChannelTypes.GUILD_VOICE,
type: DiscordChannelTypes.GuildVoice,
userLimit: 32,
},
true,
+3 -3
View File
@@ -1,8 +1,8 @@
import { cache } from "../../src/cache.ts";
import { createChannel } from "../../src/helpers/channels/create_channel.ts";
import { deleteChannel } from "../../src/helpers/channels/delete_channel.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
Deno.test({
name: "[channel] delete a channel without a reason.",
@@ -21,7 +21,7 @@ Deno.test({
}
// Delete the channel now without a reason
await deleteChannel(tempData.guildId, channel.id);
await deleteChannel(channel.id);
// wait 5 seconds to give it time for CHANNEL_DELETE event
await delayUntil(3000, () => !cache.channels.has(channel.id));
// Make sure it is gone from cache
@@ -51,7 +51,7 @@ Deno.test({
}
// Delete the channel now without a reason
await deleteChannel(tempData.guildId, channel.id, "with a reason");
await deleteChannel(channel.id, "with a reason");
// wait 5 seconds to give it time for CHANNEL_DELETE event
await delayUntil(10000, () => !cache.channels.has(channel.id));
// Make sure it is gone from cache
+1 -1
View File
@@ -15,7 +15,7 @@ async function ifItFailsBlameWolf(options: CreateGuildChannel, _save = false) {
// Assertions
assertExists(channel);
assertEquals(channel.type, options.type || DiscordChannelTypes.GUILD_TEXT);
assertEquals(channel.type, options.type || DiscordChannelTypes.GuildText);
// Delay the execution by 5 seconds to allow CHANNEL_CREATE event to be processed
await delayUntil(10000, () => cache.channels.has(channel.id));
+1 -1
View File
@@ -16,7 +16,7 @@ async function ifItFailsBlameWolf(options: CreateGuildChannel) {
// Assertions
assertExists(channel);
assertEquals(channel.type, options.type || DiscordChannelTypes.GUILD_TEXT);
assertEquals(channel.type, options.type || DiscordChannelTypes.GuildText);
// Delay the execution by 5 seconds to allow CHANNEL_CREATE event to be processed
await delayUntil(10000, () => cache.channels.has(channel.id));
+5 -5
View File
@@ -1,20 +1,20 @@
import { botId } from "../../src/bot.ts";
import { cache } from "../../src/cache.ts";
import { createChannel } from "../../src/helpers/channels/create_channel.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
import { delayUntil } from "../util/delay_until.ts";
import { assertEquals, assertExists } from "../deps.ts";
import { isChannelSynced } from "../../src/helpers/channels/is_channel_synced.ts";
import { DiscordChannelTypes } from "../../src/types/channels/channel_types.ts";
import { botId } from "../../src/bot.ts";
import { DiscordOverwriteTypes } from "../../src/types/channels/overwrite_types.ts";
import { bigintToSnowflake } from "../../src/util/bigint.ts";
import { assertEquals, assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
Deno.test({
name: "[channel] is channel synced.",
async fn() {
const category = await createChannel(tempData.guildId, {
name: "synced-category",
type: DiscordChannelTypes.GUILD_CATEGORY,
type: DiscordChannelTypes.GuildCategory,
permissionOverwrites: [
{
id: bigintToSnowflake(botId),