Improved Guild#createChannel, added RoleResolvable and fixed a bit of Emoji stuff (#1754)

* Made creating channels with overwrites nicer
and added ClientDataResolver#resolveRole

* Renamed ChannelPermissionOverwrites to ChannelCreationOverwrites

* Added RoleResolvables everywhere possible

* Fixed Emoji#setName resetting restricted roles and Emoji#equals

Which will lead to emojis not to update when roles are being added removed.
This commit is contained in:
SpaceEEC
2017-08-10 01:22:57 +02:00
committed by Crawl
parent 48b69c6e2f
commit 87cdad332c
5 changed files with 132 additions and 53 deletions

View File

@@ -8,6 +8,7 @@ const Message = require('../structures/Message');
const Guild = require('../structures/Guild');
const Channel = require('../structures/Channel');
const GuildMember = require('../structures/GuildMember');
const Role = require('../structures/Role');
const Emoji = require('../structures/Emoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { Error, TypeError } = require('../errors');
@@ -101,6 +102,27 @@ class ClientDataResolver {
return guild.members.get(user.id) || null;
}
/**
* Data that can be resolved to a Role object. This can be:
* * A Role
* * A Snowflake
* @typedef {Role|Snowflake} RoleResolvable
*/
/**
* Resolves a RoleResolvable to a Role object.
* @param {GuildResolvable} guild The guild that this role is part of
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?Role}
*/
resolveRole(guild, role) {
if (role instanceof Role) return role;
guild = this.resolveGuild(guild);
if (!guild) return null;
if (typeof role === 'string') return guild.roles.get(role);
return null;
}
/**
* Data that can be resolved to give a Channel object. This can be:
* * A Channel object

View File

@@ -106,7 +106,7 @@ class Emoji {
* Data for editing an emoji.
* @typedef {Object} EmojiEditData
* @property {string} [name] The name of the emoji
* @property {Collection<Snowflake, Role>|Array<Snowflake|Role>} [roles] Roles to restrict emoji to
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] Roles to restrict emoji to
*/
/**
@@ -124,7 +124,7 @@ class Emoji {
return this.client.api.guilds(this.guild.id).emojis(this.id)
.patch({ data: {
name: data.name,
roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : [],
roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : undefined,
}, reason })
.then(() => this);
}
@@ -150,13 +150,18 @@ class Emoji {
/**
* Add multiple roles to the list of roles that can use this emoji.
* @param {Role[]} roles Roles to add
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to add
* @returns {Promise<Emoji>}
*/
addRestrictedRoles(roles) {
const newRoles = new Collection(this.roles);
for (const role of roles) {
if (this.guild.roles.has(role.id)) newRoles.set(role.id, role);
for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
}
newRoles.set(role.id, role);
}
return this.edit({ roles: newRoles });
}
@@ -172,12 +177,17 @@ class Emoji {
/**
* Remove multiple roles from the list of roles that can use this emoji.
* @param {Role[]} roles Roles to remove
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to remove
* @returns {Promise<Emoji>}
*/
removeRestrictedRoles(roles) {
const newRoles = new Collection(this.roles);
for (const role of roles) {
for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
}
if (newRoles.has(role.id)) newRoles.delete(role.id);
}
return this.edit({ roles: newRoles });
@@ -206,12 +216,14 @@ class Emoji {
other.id === this.id &&
other.name === this.name &&
other.managed === this.managed &&
other.requiresColons === this.requiresColons
other.requiresColons === this.requiresColons &&
other._roles === this._roles
);
} else {
return (
other.id === this.id &&
other.name === this.name
other.name === this.name &&
other._roles === this._roles
);
}
}

View File

@@ -508,7 +508,7 @@ class Guild {
* @param {string} options.accessToken An OAuth2 access token for the user with the `guilds.join` scope granted to the
* bot's application
* @param {string} [options.nick] Nickname to give the member (requires `MANAGE_NICKNAMES`)
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} [options.roles] Roles to add to the member
* @param {Collection<Snowflake, Role>|RoleResolvable[]} [options.roles] Roles to add to the member
* (requires `MANAGE_ROLES`)
* @param {boolean} [options.mute] Whether the member should be muted (requires `MUTE_MEMBERS`)
* @param {boolean} [options.deaf] Whether the member should be deafened (requires `DEAFEN_MEMBERS`)
@@ -518,9 +518,14 @@ class Guild {
if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
options.access_token = options.accessToken;
if (options.roles) {
const roles = options.roles;
if (roles instanceof Collection || (roles instanceof Array && roles[0] instanceof Role)) {
options.roles = roles.map(role => role.id);
const roles = [];
for (let role of options.roles instanceof Collection ? options.roles.values() : options.roles) {
role = this.client.resolver.resolveRole(this, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'Array or Collection of Roles or Snowflakes', true));
}
roles.push(role.id);
}
}
return this.client.api.guilds(this.id).members(user.id).put({ data: options })
@@ -898,12 +903,21 @@ class Guild {
if (!this.client.user.bot) this.client.syncGuilds([this]);
}
/**
* Can be used to overwrite permissions when creating a channel.
* @typedef {Object} ChannelCreationOverwrites
* @property {PermissionResolveable[]|number} [allow] The permissions to allow
* @property {PermissionResolveable[]|number} [deny] The permissions to deny
* @property {RoleResolveable|UserResolvable} id ID of the group or member this overwrite is for
*/
/**
* Creates a new channel in the guild.
* @param {string} name The name of the new channel
* @param {string} type The type of the new channel, either `text` or `voice`
* @param {Object} options Options
* @param {Array<PermissionOverwrites|Object>} [options.overwrites] Permission overwrites to apply to the new channel
* @param {Object} [options={}] Options
* @param {Array<PermissionOverwrites|ChannelCreationOverwrites>} [options.overwrites] Permission overwrites
* to apply to the new channel
* @param {string} [options.reason] Reason for creating this channel
* @returns {Promise<TextChannel|VoiceChannel>}
* @example
@@ -914,13 +928,30 @@ class Guild {
*/
createChannel(name, type, { overwrites, reason } = {}) {
if (overwrites instanceof Collection || overwrites instanceof Array) {
overwrites = overwrites.map(overwrite => ({
allow: overwrite.allow || overwrite._allowed,
deny: overwrite.deny || overwrite._denied,
type: overwrite.type,
id: overwrite.id,
}));
overwrites = overwrites.map(overwrite => {
let allow = overwrite.allow || overwrite._allowed;
let deny = overwrite.deny || overwrite._denied;
if (allow instanceof Array) allow = Permissions.resolve(allow);
if (deny instanceof Array) deny = Permissions.resolve(deny);
const role = this.client.resolver.resolveRole(this, overwrite.id);
if (role) {
overwrite.id = role.id;
overwrite.type = 'role';
} else {
overwrite.id = this.client.resolver.resolveUserID(overwrite.id);
overwrite.type = 'member';
}
return {
allow,
deny,
type: overwrite.type,
id: overwrite.id,
};
});
}
return this.client.api.guilds(this.id).channels.post({
data: {
name, type, permission_overwrites: overwrites,
@@ -1005,7 +1036,7 @@ class Guild {
* @param {BufferResolvable|Base64Resolvable} attachment The image for the emoji
* @param {string} name The name for the emoji
* @param {Object} [options] Options
* @param {Collection<Snowflake, Role>|Role[]} [options.roles] Roles to limit the emoji to
* @param {Collection<Snowflake, Role>|RoleResolvable[]} [options.roles] Roles to limit the emoji to
* @param {string} [options.reason] Reason for creating the emoji
* @returns {Promise<Emoji>} The created emoji
* @example
@@ -1022,16 +1053,27 @@ class Guild {
createEmoji(attachment, name, { roles, reason } = {}) {
if (typeof attachment === 'string' && attachment.startsWith('data:')) {
const data = { image: attachment, name };
if (roles) data.roles = roles.map(r => r.id ? r.id : r);
if (roles) {
data.roles = [];
for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'options.roles',
'Array or Collection of Roles or Snowflakes', true));
}
data.roles.push(role.id);
}
}
return this.client.api.guilds(this.id).emojis.post({ data, reason })
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
} else {
return this.client.resolver.resolveBuffer(attachment)
.then(data => {
const dataURI = this.client.resolver.resolveBase64(data);
return this.createEmoji(dataURI, name, roles);
});
}
return this.client.resolver.resolveBuffer(attachment)
.then(data => {
const dataURI = this.client.resolver.resolveBase64(data);
return this.createEmoji(dataURI, name, { roles, reason });
});
}
/**

View File

@@ -324,7 +324,7 @@ class GuildMember {
* The data for editing a guild member.
* @typedef {Object} GuildMemberEditData
* @property {string} [nick] The nickname to set for the member
* @property {Collection<Snowflake, Role>|Role[]|Snowflake[]} [roles] The roles or role IDs to apply
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role IDs to apply
* @property {boolean} [mute] Whether or not the member should be muted
* @property {boolean} [deaf] Whether or not the member should be deafened
* @property {ChannelResolvable} [channel] Channel to move member to (if they are connected to voice)
@@ -384,7 +384,7 @@ class GuildMember {
/**
* Sets the roles applied to the member.
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to apply
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role IDs to apply
* @param {string} [reason] Reason for applying the roles
* @returns {Promise<GuildMember>}
*/
@@ -394,12 +394,12 @@ class GuildMember {
/**
* Adds a single role to the member.
* @param {Role|Snowflake} role The role or ID of the role to add
* @param {RoleResolvable} role The role or ID of the role to add
* @param {string} [reason] Reason for adding the role
* @returns {Promise<GuildMember>}
*/
addRole(role, reason) {
if (!(role instanceof Role)) role = this.guild.roles.get(role);
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
if (this._roles.includes(role.id)) return Promise.resolve(this);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
@@ -409,30 +409,33 @@ class GuildMember {
/**
* Adds multiple roles to the member.
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to add
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role IDs to add
* @param {string} [reason] Reason for adding the roles
* @returns {Promise<GuildMember>}
*/
addRoles(roles, reason) {
let allRoles;
if (roles instanceof Collection) {
allRoles = this._roles.slice();
for (const role of roles.values()) allRoles.push(role.id ? role.id : role);
} else {
allRoles = this._roles.concat(roles.map(r => r.id ? r.id : r));
let allRoles = this._roles.slice();
for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
}
allRoles.push(role.id);
}
return this.edit({ roles: allRoles }, reason);
}
/**
* Removes a single role from the member.
* @param {Role|Snowflake} role The role or ID of the role to remove
* @param {RoleResolvable} role The role or ID of the role to remove
* @param {string} [reason] Reason for removing the role
* @returns {Promise<GuildMember>}
*/
removeRole(role, reason) {
if (!(role instanceof Role)) role = this.guild.roles.get(role);
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
if (!this._roles.includes(role.id)) return Promise.resolve(this);
return this.client.api.guilds(this.guild.id).members(this.user.id).roles(role.id)
.delete({ reason })
.then(() => this);
@@ -440,22 +443,20 @@ class GuildMember {
/**
* Removes multiple roles from the member.
* @param {Collection<Snowflake, Role>|Role[]|Snowflake[]} roles The roles or role IDs to remove
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role IDs to remove
* @param {string} [reason] Reason for removing the roles
* @returns {Promise<GuildMember>}
*/
removeRoles(roles, reason) {
const allRoles = this._roles.slice();
if (roles instanceof Collection) {
for (const role of roles.values()) {
const index = allRoles.indexOf(role.id);
if (index >= 0) allRoles.splice(index, 1);
}
} else {
for (const role of roles) {
const index = allRoles.indexOf(role instanceof Role ? role.id : role);
if (index >= 0) allRoles.splice(index, 1);
for (let role of roles instanceof Collection ? roles.values() : roles) {
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) {
return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
'Array or Collection of Roles or Snowflakes', true));
}
const index = allRoles.indexOf(role.id);
if (index >= 0) allRoles.splice(index, 1);
}
return this.edit({ roles: allRoles }, reason);
}

View File

@@ -169,11 +169,13 @@ class Role {
/**
* Compares this role's position to another role's.
* @param {Role} role Role to compare to this one
* @param {RoleResolvable} role Role to compare to this one
* @returns {number} Negative number if the this role's position is lower (other role's is higher),
* positive number if the this one is higher (other's is lower), 0 if equal
*/
comparePositionTo(role) {
role = this.client.resolver.resolveRole(this.guild, role);
if (!role) return Promise.reject(new TypeError('INVALID_TYPE', 'role', 'Role nor a Snowflake'));
return this.constructor.comparePositions(this, role);
}