Skip to content

Commit

Permalink
feat: add methods to managers (discordjs#7300)
Browse files Browse the repository at this point in the history
Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
Co-authored-by: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
  • Loading branch information
4 people committed Mar 6, 2022
1 parent 13dd82d commit b8d3684
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 179 deletions.
198 changes: 196 additions & 2 deletions src/managers/GuildChannelManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const CachedManager = require('./CachedManager');
const ThreadManager = require('./ThreadManager');
const { Error } = require('../errors');
const { Error, TypeError } = require('../errors');
const GuildChannel = require('../structures/GuildChannel');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const ThreadChannel = require('../structures/ThreadChannel');
const { ChannelTypes, ThreadChannelTypes } = require('../util/Constants');
const Webhook = require('../structures/Webhook');
const { ThreadChannelTypes, ChannelTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util');

let cacheWarningEmitted = false;
let storeChannelDeprecationEmitted = false;
Expand Down Expand Up @@ -169,6 +172,147 @@ class GuildChannelManager extends CachedManager {
return this.client.actions.ChannelCreate.handle(data).channel;
}

/**
* Creates a webhook for the channel.
* @param {GuildChannelResolvable} channel The channel to create the webhook for
* @param {string} name The name of the webhook
* @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* guild.channels.createWebhook('222197033908436994', 'Snek', {
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
async createWebhook(channel, name, { avatar, reason } = {}) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
avatar = await DataResolver.resolveImage(avatar);
}
const data = await this.client.api.channels[id].webhooks.post({
data: {
name,
avatar,
},
reason,
});
return new Webhook(this.client, data);
}

/**
* The data for a guild channel.
* @typedef {Object} ChannelData
* @property {string} [name] The name of the channel
* @property {ChannelType} [type] The type of the channel (only conversion between text and news is supported)
* @property {number} [position] The position of the channel
* @property {string} [topic] The topic of the text channel
* @property {boolean} [nsfw] Whether the channel is NSFW
* @property {number} [bitrate] The bitrate of the voice channel
* @property {number} [userLimit] The user limit of the voice channel
* @property {?CategoryChannelResolvable} [parent] The parent of the channel
* @property {boolean} [lockPermissions]
* Lock the permissions of the channel to what the parent's permissions are
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites for the channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the channel in seconds
* @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration]
* The default auto archive duration for all new threads in this channel
* @property {?string} [rtcRegion] The RTC region of the channel
*/

/**
* Edits the channel.
* @param {GuildChannelResolvable} channel The channel to edit
* @param {ChannelData} data The new data for the channel
* @param {string} [reason] Reason for editing this channel
* @returns {Promise<GuildChannel>}
* @example
* // Edit a channel
* guild.channels.edit('222197033908436994', { name: 'new-channel' })
* .then(console.log)
* .catch(console.error);
*/
async edit(channel, data, reason) {
channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');

const parent = this.client.channels.resolveId(data.parent);

if (typeof data.position !== 'undefined') await this.setPosition(channel, data.position, { reason });

let permission_overwrites = data.permissionOverwrites?.map(o => PermissionOverwrites.resolve(o, this.guild));

if (data.lockPermissions) {
if (parent) {
const newParent = this.guild.channels.resolve(parent);
if (newParent?.type === 'GUILD_CATEGORY') {
permission_overwrites = newParent.permissionOverwrites.cache.map(o =>
PermissionOverwrites.resolve(o, this.guild),
);
}
} else if (channel.parent) {
permission_overwrites = this.parent.permissionOverwrites.cache.map(o =>
PermissionOverwrites.resolve(o, this.guild),
);
}
}

const newData = await this.client.api.channels(channel.id).patch({
data: {
name: (data.name ?? channel.name).trim(),
type: data.type,
topic: data.topic,
nsfw: data.nsfw,
bitrate: data.bitrate ?? channel.bitrate,
user_limit: data.userLimit ?? channel.userLimit,
rtc_region: data.rtcRegion ?? channel.rtcRegion,
parent_id: parent,
lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser,
default_auto_archive_duration: data.defaultAutoArchiveDuration,
permission_overwrites,
},
reason,
});

return this.client.actions.ChannelUpdate.handle(newData).updated;
}

/**
* Sets a new position for the guild channel.
* @param {GuildChannelResolvable} channel The channel to set the position for
* @param {number} position The new position for the guild channel
* @param {SetChannelPositionOptions} [options] Options for setting position
* @returns {Promise<GuildChannel>}
* @example
* // Set a new channel position
* guild.channels.setPosition('222078374472843266', 2)
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
async setPosition(channel, position, { relative, reason } = {}) {
channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const updatedChannels = await Util.setPosition(
channel,
position,
relative,
this.guild._sortedChannels(this),
this.client.api.guilds(this.guild.id).channels,
reason,
);

this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id,
channels: updatedChannels,
});
return channel;
}

/**
* Obtains one or more guild channels from Discord, or the channel cache if they're already available.
* @param {Snowflake} [id] The channel's id
Expand Down Expand Up @@ -204,6 +348,39 @@ class GuildChannelManager extends CachedManager {
return channels;
}

/**
* Fetches all webhooks for the channel.
* @param {GuildChannelResolvable} channel The channel to fetch webhooks for
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* guild.channels.fetchWebhooks('769862166131245066')
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
async fetchWebhooks(channel) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const data = await this.client.api.channels[id].webhooks.get();
return data.reduce((hooks, hook) => hooks.set(hook.id, new Webhook(this.client, hook)), new Collection());
}

/**
* Data that can be resolved to give a Category Channel object. This can be:
* * A CategoryChannel object
* * A Snowflake
* @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable
*/

/**
* The data needed for updating a channel's position.
* @typedef {Object} ChannelPosition
* @property {GuildChannel|Snowflake} channel Channel to update
* @property {number} [position] New position for the channel
* @property {CategoryChannelResolvable} [parent] Parent channel for this channel
* @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites
*/

/**
* Batch-updates the guild's channels' positions.
* <info>Only one channel's parent can be changed at a time</info>
Expand Down Expand Up @@ -243,6 +420,23 @@ class GuildChannelManager extends CachedManager {
const raw = await this.client.api.guilds(this.guild.id).threads.active.get();
return ThreadManager._mapThreads(raw, this.client, { guild: this.guild, cache });
}

/**
* Deletes the channel.
* @param {GuildChannelResolvable} channel The channel to delete
* @param {string} [reason] Reason for deleting this channel
* @returns {Promise<void>}
* @example
* // Delete the channel
* guild.channels.delete('858850993013260338', 'making room for new channels')
* .then(console.log)
* .catch(console.error);
*/
async delete(channel, reason) {
const id = this.resolveId(channel);
if (!id) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
await this.client.api.channels(this.id).delete({ reason });
}
}

module.exports = GuildChannelManager;
68 changes: 67 additions & 1 deletion src/managers/GuildEmojiManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

const { Collection } = require('@discordjs/collection');
const BaseGuildEmojiManager = require('./BaseGuildEmojiManager');
const { TypeError } = require('../errors');
const { Error, TypeError } = require('../errors');
const DataResolver = require('../util/DataResolver');
const Permissions = require('../util/Permissions');

/**
* Manages API methods for GuildEmojis and stores their cache.
Expand Down Expand Up @@ -100,6 +101,71 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
return emojis;
}

/**
* Deletes an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
* @param {string} [reason] Reason for deleting the emoji
* @returns {Promise<void>}
*/
async delete(emoji, reason) {
const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
await this.client.api.guilds(this.guild.id).emojis(id).delete({ reason });
}

/**
* Edits an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
* @param {GuildEmojiEditData} data The new data for the emoji
* @param {string} [reason] Reason for editing this emoji
* @returns {Promise<GuildEmoji>}
*/
async edit(emoji, data, reason) {
const id = this.resolveId(emoji);
if (!id) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
const roles = data.roles?.map(r => this.guild.roles.resolveId(r));
const newData = await this.client.api
.guilds(this.guild.id)
.emojis(id)
.patch({
data: {
name: data.name,
roles,
},
reason,
});
const existing = this.cache.get(id);
if (existing) {
const clone = existing._clone();
clone._patch(newData);
return clone;
}
return this._add(newData);
}

/**
* Fetches the author for this emoji
* @param {EmojiResolvable} emoji The emoji to fetch the author of
* @returns {Promise<User>}
*/
async fetchAuthor(emoji) {
emoji = this.resolve(emoji);
if (!emoji) throw new TypeError('INVALID_TYPE', 'emoji', 'EmojiResolvable', true);
if (emoji.managed) {
throw new Error('EMOJI_MANAGED');
}

const { me } = this.guild;
if (!me) throw new Error('GUILD_UNCACHED_ME');
if (!me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) {
throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild);
}

const data = await this.client.api.guilds(this.guild.id).emojis(emoji.id).get();
emoji._patch(data);
return emoji.author;
}
}

module.exports = GuildEmojiManager;
13 changes: 13 additions & 0 deletions src/managers/GuildStickerManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ class GuildStickerManager extends CachedManager {
const data = await this.client.api.guilds(this.guild.id).stickers.get();
return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)]));
}

/**
* Fetches the user who uploaded this sticker, if this is a guild sticker.
* @param {StickerResolvable} sticker The sticker to fetch the user for
* @returns {Promise<?User>}
*/
async fetchUser(sticker) {
sticker = this.resolve(sticker);
if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable');
const data = await this.client.api.guilds(this.guildId).stickers(sticker.id).get();
sticker._patch(data);
return sticker.user;
}
}

module.exports = GuildStickerManager;

0 comments on commit b8d3684

Please sign in to comment.