Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add methods to managers #7300

Merged
merged 1 commit into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
27 changes: 25 additions & 2 deletions packages/discord.js/src/managers/GuildEmojiManager.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'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');

/**
Expand Down Expand Up @@ -140,6 +140,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