Add channel cloning

Changes:
    - Added function cloneChannel
    - Modified createChannel to support cloneChannel
   - Added clone(reason) method on channel structure
    - Added channel cloning tests
This commit is contained in:
Exists
2021-04-14 04:01:35 -04:00
parent f31216d0a4
commit 1a1ef34a96
5 changed files with 116 additions and 24 deletions
+28
View File
@@ -0,0 +1,28 @@
import { cacheHandlers } from "../../cache.ts";
import { createChannel } from "./create_channel.ts";
import { CreateGuildChannel } from "../../types/guilds/create_guild_channel.ts";
import { DiscordenoChannel } from "../../structures/channel.ts";
/** Create a copy of a channel */
export async function cloneChannel(channelId: string, reason?: string) {
const channelToClone: DiscordenoChannel | undefined = await cacheHandlers.get(
"channels",
channelId
);
//Return undefined if channel is not cached (unsure about error handling)
if (!channelToClone) return;
//If "name" is null or undefined as specified by types
channelToClone.name ??= "new-channel";
//Merge channel data with reason for createChannel options
const creationData = {
reason,
...channelToClone,
};
//Create the channel (also handles permissions)
return createChannel(
channelToClone.guildId!,
creationData as CreateGuildChannel
);
}
+18 -8
View File
@@ -17,14 +17,22 @@ import { calculateBits } from "../../util/permissions.ts";
/** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ /** Create a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */
export async function createChannel( export async function createChannel(
guildId: string, guildId: string,
options?: CreateGuildChannel, options?: CreateGuildChannel
) { ) {
const requiredPerms: Set<PermissionStrings> = new Set(["MANAGE_CHANNELS"]); const requiredPerms: Set<PermissionStrings> = new Set(["MANAGE_CHANNELS"]);
//Integration with permissions for channel cloning
let useDefaultOverwrites = false;
options?.permissionOverwrites?.forEach((overwrite) => { options?.permissionOverwrites?.forEach((overwrite) => {
if (overwrite.id && parseInt(overwrite.allow)) {
useDefaultOverwrites = true;
return;
}
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running forEach loop in create_channel file.`, `Running forEach loop in create_channel file.`
); );
overwrite.allow.forEach(requiredPerms.add, requiredPerms); overwrite.allow.forEach(requiredPerms.add, requiredPerms);
overwrite.deny.forEach(requiredPerms.add, requiredPerms); overwrite.deny.forEach(requiredPerms.add, requiredPerms);
@@ -40,14 +48,16 @@ export async function createChannel(
endpoints.GUILD_CHANNELS(guildId), endpoints.GUILD_CHANNELS(guildId),
{ {
...camelKeysToSnakeCase<DiscordCreateGuildChannel>(options ?? {}), ...camelKeysToSnakeCase<DiscordCreateGuildChannel>(options ?? {}),
permission_overwrites: options?.permissionOverwrites?.map((perm) => ({ permission_overwrites: useDefaultOverwrites
...perm, ? options?.permissionOverwrites
: options?.permissionOverwrites?.map((perm) => ({
...perm,
allow: calculateBits(perm.allow), allow: calculateBits(perm.allow),
deny: calculateBits(perm.deny), deny: calculateBits(perm.deny),
})), })),
type: options?.type || DiscordChannelTypes.GUILD_TEXT, type: options?.type || DiscordChannelTypes.GUILD_TEXT,
}, }
)) as DiscordChannel; )) as DiscordChannel;
const discordenoChannel = await structures.createDiscordenoChannel(result); const discordenoChannel = await structures.createDiscordenoChannel(result);
+19 -14
View File
@@ -5,6 +5,8 @@ import { deleteChannel } from "../helpers/channels/delete_channel.ts";
import { deleteChannelOverwrite } from "../helpers/channels/delete_channel_overwrite.ts"; import { deleteChannelOverwrite } from "../helpers/channels/delete_channel_overwrite.ts";
import { editChannel } from "../helpers/channels/edit_channel.ts"; import { editChannel } from "../helpers/channels/edit_channel.ts";
import { editChannelOverwrite } from "../helpers/channels/edit_channel_overwrite.ts"; import { editChannelOverwrite } from "../helpers/channels/edit_channel_overwrite.ts";
import { createChannel } from "../helpers/channels/create_channel.ts";
import { cloneChannel } from "../helpers/channels/clone_channel.ts";
import { sendMessage } from "../helpers/messages/send_message.ts"; import { sendMessage } from "../helpers/messages/send_message.ts";
import { disconnectMember } from "../helpers/mod.ts"; import { disconnectMember } from "../helpers/mod.ts";
import { Channel, DiscordChannel } from "../types/channels/channel.ts"; import { Channel, DiscordChannel } from "../types/channels/channel.ts";
@@ -30,8 +32,8 @@ const baseChannel: Partial<DiscordenoChannel> = {
return `<#${this.id!}>`; return `<#${this.id!}>`;
}, },
get voiceStates() { get voiceStates() {
return this.guild?.voiceStates.filter((voiceState) => return this.guild?.voiceStates.filter(
voiceState.channelId === this.id (voiceState) => voiceState.channelId === this.id
); );
}, },
get connectedMembers() { get connectedMembers() {
@@ -39,7 +41,7 @@ const baseChannel: Partial<DiscordenoChannel> = {
if (!voiceStates) return undefined; if (!voiceStates) return undefined;
return new Collection( return new Collection(
voiceStates.map((vs, key) => [key, cache.members.get(key)]), voiceStates.map((vs, key) => [key, cache.members.get(key)])
); );
}, },
send(content) { send(content) {
@@ -62,19 +64,23 @@ const baseChannel: Partial<DiscordenoChannel> = {
this.guildId!, this.guildId!,
this.id!, this.id!,
overwrites, overwrites,
permissions, permissions
); );
}, },
edit(options, reason) { edit(options, reason) {
return editChannel(this.id!, options, reason); return editChannel(this.id!, options, reason);
}, },
clone(reason) {
return cloneChannel(this.id!, reason);
},
}; };
/** Create a structure object */ /** Create a structure object */
// deno-lint-ignore require-await // deno-lint-ignore require-await
export async function createDiscordenoChannel( export async function createDiscordenoChannel(
data: DiscordChannel, data: DiscordChannel,
guildId?: string, guildId?: string
) { ) {
const { const {
guildId: rawGuildId = "", guildId: rawGuildId = "",
@@ -86,7 +92,7 @@ export async function createDiscordenoChannel(
Object.keys(rest).forEach((key) => { Object.keys(rest).forEach((key) => {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running forEach loop in createDiscordenoChannel function.`, `Running forEach loop in createDiscordenoChannel function.`
); );
// @ts-ignore index signature // @ts-ignore index signature
props[key] = createNewProp(rest[key]); props[key] = createNewProp(rest[key]);
@@ -96,7 +102,7 @@ export async function createDiscordenoChannel(
...props, ...props,
guildId: createNewProp(guildId || rawGuildId), guildId: createNewProp(guildId || rawGuildId),
lastPinTimestamp: createNewProp( lastPinTimestamp: createNewProp(
lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined, lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined
), ),
}); });
@@ -147,20 +153,19 @@ export interface DiscordenoChannel
/** Edit a channel Overwrite */ /** Edit a channel Overwrite */
editOverwrite( editOverwrite(
overwriteID: string, overwriteID: string,
options: Omit<Overwrite, "id">, options: Omit<Overwrite, "id">
): ReturnType<typeof editChannelOverwrite>; ): ReturnType<typeof editChannelOverwrite>;
/** Delete a channel Overwrite */ /** Delete a channel Overwrite */
deleteOverwrite( deleteOverwrite(
overwriteID: string, overwriteID: string
): ReturnType<typeof deleteChannelOverwrite>; ): ReturnType<typeof deleteChannelOverwrite>;
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */ /** Checks if a channel overwrite for a user id or a role id has permission in this channel */
hasPermission( hasPermission(
overwrites: DiscordOverwrite[], overwrites: DiscordOverwrite[],
permissions: PermissionStrings[], permissions: PermissionStrings[]
): ReturnType<typeof channelOverwriteHasPermission>; ): ReturnType<typeof channelOverwriteHasPermission>;
/** Edit the channel */ /** Edit the channel */
edit( edit(options: ModifyChannel, reason?: string): ReturnType<typeof editChannel>;
options: ModifyChannel, /** Create a new channel with the same properties */
reason?: string, clone(reason?: string): ReturnType<typeof createChannel>;
): ReturnType<typeof editChannel>;
} }
+48
View File
@@ -0,0 +1,48 @@
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
import { assertEquals, assertExists } from "../deps.ts";
import { cache } from "../../src/cache.ts";
import { cloneChannel } from "../../src/helpers/channels/clone_channel.ts";
import { delayUntil } from "../util/delay_until.ts";
Deno.test({
name: "[channel] clone a channel",
async fn() {
const cloned = await cloneChannel(tempData.channelId, "testing");
//Get channel that was cloned
const originalChannel = cache.channels.get(tempData.channelId);
//Assertation
assertExists(cloned);
assertEquals(cloned.type, originalChannel.type);
// Delay the execution to allow CHANNEL_CREATE event to be processed
await delayUntil(10000, () => cache.channels.has(cloned.id));
if (!cache.channels.has(cloned.id)) {
throw new Error(`The channel seemed to be cloned but was not cached.`);
}
if (originalChannel.topic && cloned.topic !== originalChannel.topic) {
throw new Error(
"The clone was supposed to have a topic but it does not appear to be the same topic."
);
}
if (originalChannel.bitrate && cloned.bitrate !== originalChannel.bitrate) {
throw new Error(
"The clone was supposed to have a bitrate but it does not appear to be the same bitrate."
);
}
if (
originalChannel.permissionOverwrites &&
cloned.permissionOverwrites?.length !==
originalChannel.permissionOverwrites.length
) {
throw new Error(
"The clone was supposed to have a permissionOverwrites but it does not appear to be the same permissionOverwrites."
);
}
},
});
+1
View File
@@ -16,6 +16,7 @@ import "./guilds/create_guild.ts";
import "./channels/category_children.ts"; import "./channels/category_children.ts";
import "./channels/channel_overwrite_has_permission.ts"; import "./channels/channel_overwrite_has_permission.ts";
import "./channels/create_channel.ts"; import "./channels/create_channel.ts";
import "./channels/clone_channel.ts";
import "./channels/delete_channel.ts"; import "./channels/delete_channel.ts";
import "./channels/delete_channel_overwrite.ts"; import "./channels/delete_channel_overwrite.ts";
import "./channels/edit_channel.ts"; import "./channels/edit_channel.ts";