mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-16 11:28:15 +00:00
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:
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
+21
-16
@@ -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
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,13 +131,13 @@ export interface DiscordenoChannel
|
|||||||
mention: string;
|
mention: string;
|
||||||
/**
|
/**
|
||||||
* Gets the voice states for this channel
|
* Gets the voice states for this channel
|
||||||
*
|
*
|
||||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||||
*/
|
*/
|
||||||
voiceStates?: Collection<string, VoiceState>;
|
voiceStates?: Collection<string, VoiceState>;
|
||||||
/**
|
/**
|
||||||
* Gets the connected members for this channel undefined if member is not cached
|
* Gets the connected members for this channel undefined if member is not cached
|
||||||
*
|
*
|
||||||
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
* ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async.
|
||||||
*/
|
*/
|
||||||
connectedMembers?: Collection<string, DiscordenoMember | undefined>;
|
connectedMembers?: Collection<string, DiscordenoMember | 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>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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";
|
||||||
|
|||||||
Reference in New Issue
Block a user