Skip to content

Commit

Permalink
feat: add methods to managers
Browse files Browse the repository at this point in the history
  • Loading branch information
ImRodry committed Jan 18, 2022
1 parent 307389a commit 81e9b50
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 176 deletions.
184 changes: 183 additions & 1 deletion packages/discord.js/src/managers/GuildChannelManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ const { Collection } = require('@discordjs/collection');
const { ChannelType } = require('discord-api-types/v9');
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 Webhook = require('../structures/Webhook');
const { ThreadChannelTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util');

let cacheWarningEmitted = false;
let storeChannelDeprecationEmitted = false;
Expand Down Expand Up @@ -170,6 +173,149 @@ 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
* channel.edit({ 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');
data.parent &&= this.client.channels.resolveId(data.parent);

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

let permission_overwrites;

if (data.permissionOverwrites) {
permission_overwrites = data.permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
}

if (data.lockPermissions) {
if (data.parent) {
const newParent = this.guild.channels.resolve(data.parent);
if (newParent?.type === 'GuildCategory') {
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: ChannelType[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: data.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(channel),
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 @@ -205,6 +351,25 @@ 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();
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
}

/**
* Data that can be resolved to give a Category Channel object. This can be:
* * A CategoryChannel object
Expand Down Expand Up @@ -260,6 +425,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
* channel.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(id).delete({ reason });
}
}

module.exports = GuildChannelManager;
24 changes: 23 additions & 1 deletion packages/discord.js/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 @@ -142,6 +143,27 @@ class GuildEmojiManager extends BaseGuildEmojiManager {
}
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');
} else {
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
if (!this.guild.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 packages/discord.js/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.guild.id).stickers(sticker.id).get();
sticker._patch(data);
return sticker.user;
}
}

module.exports = GuildStickerManager;
33 changes: 32 additions & 1 deletion packages/discord.js/src/managers/RoleManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { Role } = require('../structures/Role');
const DataResolver = require('../util/DataResolver');
const Permissions = require('../util/Permissions');
const { resolveColor } = require('../util/Util');
const Util = require('../util/Util');

let cacheWarningEmitted = false;

Expand Down Expand Up @@ -224,7 +225,37 @@ class RoleManager extends CachedManager {
this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: id });
}

/*
/**
* Sets the new position of the role.
* @param {RoleResolvable} role The role to change the position of
* @param {number} position The new position for the role
* @param {SetRolePositionOptions} [options] Options for setting the position
* @returns {Promise<Role>}
* @example
* // Set the position of the role
* role.setPosition(1)
* .then(updated => console.log(`Role position: ${updated.position}`))
* .catch(console.error);
*/
async setPosition(role, position, { relative, reason } = {}) {
role = this.resolve(role);
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
const updatedRoles = await Util.setPosition(
role,
position,
relative,
this.guild._sortedRoles(),
this.client.api.guilds(this.guild.id).roles,
reason,
);
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: updatedRoles,
});
return role;
}

/**
* The data needed for updating a guild role's position
* @typedef {Object} GuildRolePosition
* @property {RoleResolvable} role The role's id
Expand Down
24 changes: 4 additions & 20 deletions packages/discord.js/src/structures/BaseGuildTextChannel.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const GuildChannel = require('./GuildChannel');
const Webhook = require('./Webhook');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const MessageManager = require('../managers/MessageManager');
const ThreadManager = require('../managers/ThreadManager');
const DataResolver = require('../util/DataResolver');

/**
* Represents a text-based guild channel on Discord.
Expand Down Expand Up @@ -121,11 +118,8 @@ class BaseGuildTextChannel extends GuildChannel {
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
async fetchWebhooks() {
const data = await this.client.api.channels[this.id].webhooks.get();
const hooks = new Collection();
for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook));
return hooks;
fetchWebhooks() {
return this.guild.channels.fetchWebhooks(this.id);
}

/**
Expand All @@ -149,18 +143,8 @@ class BaseGuildTextChannel extends GuildChannel {
* .then(console.log)
* .catch(console.error)
*/
async createWebhook(name, { avatar, reason } = {}) {
if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
avatar = await DataResolver.resolveImage(avatar);
}
const data = await this.client.api.channels[this.id].webhooks.post({
data: {
name,
avatar,
},
reason,
});
return new Webhook(this.client, data);
createWebhook(name, options = {}) {
return this.guild.channels.createWebhook(this.id, name, options);
}

/**
Expand Down

0 comments on commit 81e9b50

Please sign in to comment.