Skip to content

Commit

Permalink
feat: add methods to managers
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 Jan 28, 2022
1 parent fbb1d03 commit 10c2eea
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 158 deletions.
181 changes: 180 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, Routes } = 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 @@ -169,6 +172,148 @@ 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.rest.post(Routes.channelWebhooks(id), {
body: {
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 === ChannelType.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.rest.patch(Routes.channel(channel.id), {
body: {
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(channel),
this.client,
Routes.guildChannels(this.guild.id),
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 +349,23 @@ 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.rest.get(Routes.channelWebhooks(id));
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
Expand Down Expand Up @@ -259,6 +421,23 @@ class GuildChannelManager extends CachedManager {
const raw = await this.client.rest.get(Routes.guildActiveThreads(this.guild.id));
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.rest.delete(Routes.channel(id), { reason });
}
}

module.exports = GuildChannelManager;
28 changes: 26 additions & 2 deletions packages/discord.js/src/managers/GuildEmojiManager.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v9');
const { Routes, PermissionFlagsBits } = require('discord-api-types/v9');
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 @@ -140,6 +141,29 @@ 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');
}

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

const data = await this.client.rest.get(Routes.guildEmoji(this.guild.id, emoji.id));
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 @@ -165,6 +165,19 @@ class GuildStickerManager extends CachedManager {
const data = await this.client.rest.get(Routes.guildStickers(this.guild.id));
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.rest.get(Routes.guildSticker(this.guildId, sticker.id));
sticker._patch(data);
return sticker.user;
}
}

module.exports = GuildStickerManager;
39 changes: 36 additions & 3 deletions packages/discord.js/src/managers/RoleManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { Role } = require('../structures/Role');
const DataResolver = require('../util/DataResolver');
const PermissionsBitField = require('../util/PermissionsBitField');
const { resolveColor } = require('../util/Util');
const Util = require('../util/Util');

let cacheWarningEmitted = false;

Expand Down Expand Up @@ -160,7 +161,7 @@ class RoleManager extends CachedManager {
guild_id: this.guild.id,
role: data,
});
if (position) return role.setPosition(position, reason);
if (position) return this.setPosition(role, position, { reason });
return role;
}

Expand All @@ -181,7 +182,7 @@ class RoleManager extends CachedManager {
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');

if (typeof data.position === 'number') {
await role.setPosition(data.position, { reason });
await this.setPosition(role, data.position, { reason });
}

let icon = data.icon;
Expand Down Expand Up @@ -225,7 +226,39 @@ 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
* guild.roles.setPosition('222197033908436994', 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,
Routes.guildRoles(this.guild.id),
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

0 comments on commit 10c2eea

Please sign in to comment.