Skip to content

Commit

Permalink
feat: add support for role icons (#6633)
Browse files Browse the repository at this point in the history
  • Loading branch information
iShibi committed Oct 3, 2021
1 parent a8c21cd commit 7129965
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 3 deletions.
23 changes: 22 additions & 1 deletion src/managers/RoleManager.js
Expand Up @@ -4,6 +4,7 @@ const { Collection } = require('@discordjs/collection');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const Role = require('../structures/Role');
const DataResolver = require('../util/DataResolver');
const Permissions = require('../util/Permissions');
const { resolveColor, setPosition } = require('../util/Util');

Expand Down Expand Up @@ -104,6 +105,10 @@ class RoleManager extends CachedManager {
* @property {PermissionResolvable} [permissions] The permissions for the new role
* @property {number} [position] The position of the new role
* @property {boolean} [mentionable] Whether or not the new role should be mentionable
* @property {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} [icon] The icon for the role
* <warn>The `EmojiResolvable` should belong to the same guild as the role.
* If not, pass the emoji's URL directly</warn>
* @property {?string} [unicodeEmoji] The unicode emoji for the role
* @property {string} [reason] The reason for creating this role
*/

Expand All @@ -128,9 +133,14 @@ class RoleManager extends CachedManager {
* .catch(console.error);
*/
async create(options = {}) {
let { name, color, hoist, permissions, position, mentionable, reason } = options;
let { name, color, hoist, permissions, position, mentionable, reason, icon, unicodeEmoji } = options;
color &&= resolveColor(color);
if (typeof permissions !== 'undefined') permissions = new Permissions(permissions);
if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.url;
icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon);
if (typeof icon !== 'string') icon = undefined;
}

const data = await this.client.api.guilds(this.guild.id).roles.post({
data: {
Expand All @@ -139,6 +149,8 @@ class RoleManager extends CachedManager {
hoist,
permissions,
mentionable,
icon,
unicode_emoji: unicodeEmoji,
},
reason,
});
Expand Down Expand Up @@ -182,12 +194,21 @@ class RoleManager extends CachedManager {
});
}

let icon = data.icon;
if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.url;
icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon);
if (typeof icon !== 'string') icon = undefined;
}

const _data = {
name: data.name,
color: typeof data.color === 'undefined' ? undefined : resolveColor(data.color),
hoist: data.hoist,
permissions: typeof data.permissions === 'undefined' ? undefined : new Permissions(data.permissions),
mentionable: data.mentionable,
icon,
unicode_emoji: data.unicodeEmoji,
};

const d = await this.client.api.guilds(this.guild.id).roles(role.id).patch({ data: _data, reason });
Expand Down
1 change: 1 addition & 0 deletions src/structures/Guild.js
Expand Up @@ -186,6 +186,7 @@ class Guild extends AnonymousGuild {
* * THREE_DAY_THREAD_ARCHIVE
* * SEVEN_DAY_THREAD_ARCHIVE
* * PRIVATE_THREADS
* * ROLE_ICONS
* @typedef {string} Features
* @see {@link https://discord.com/developers/docs/resources/guild#guild-object-guild-features}
*/
Expand Down
57 changes: 56 additions & 1 deletion src/structures/Role.js
Expand Up @@ -91,6 +91,18 @@ class Role extends Base {
*/
this.deleted = false;

/**
* The icon hash of the role
* @type {?string}
*/
this.icon = data.icon;

/**
* The unicode emoji for the role
* @type {?string}
*/
this.unicodeEmoji = data.unicode_emoji;

/**
* The tags this role has
* @type {?Object}
Expand Down Expand Up @@ -191,6 +203,10 @@ class Role extends Base {
* @property {number} [position] The position of the role
* @property {PermissionResolvable} [permissions] The permissions of the role
* @property {boolean} [mentionable] Whether or not the role should be mentionable
* @property {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} [icon] The icon for the role
* <warn>The `EmojiResolvable` should belong to the same guild as the role.
* If not, pass the emoji's URL directly</warn>
* @property {?string} [unicodeEmoji] The unicode emoji for the role
*/

/**
Expand Down Expand Up @@ -300,6 +316,33 @@ class Role extends Base {
return this.edit({ mentionable }, reason);
}

/**
* Sets a new icon for the role.
* @param {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} icon The icon for the role
* <warn>The `EmojiResolvable` should belong to the same guild as the role.
* If not, pass the emoji's URL directly</warn>
* @param {string} [reason] Reason for changing the role's icon
* @returns {Promise<Role>}
*/
setIcon(icon, reason) {
return this.edit({ icon }, reason);
}

/**
* Sets a new unicode emoji for the role.
* @param {?string} unicodeEmoji The new unicode emoji for the role
* @param {string} [reason] Reason for changing the role's unicode emoji
* @returns {Promise<Role>}
* @example
* // Set a new unicode emoji for the role
* role.setUnicodeEmoji('🤖')
* .then(updated => console.log(`Set unicode emoji for the role to ${updated.unicodeEmoji}`))
* .catch(console.error);
*/
setUnicodeEmoji(unicodeEmoji, reason) {
return this.edit({ unicodeEmoji }, reason);
}

/**
* Options used to set position of a role.
* @typedef {Object} SetRolePositionOptions
Expand Down Expand Up @@ -350,6 +393,16 @@ class Role extends Base {
return this;
}

/**
* A link to the role's icon
* @param {StaticImageURLOptions} [options={}] Options for the image URL
* @returns {?string}
*/
iconURL({ format, size } = {}) {
if (!this.icon) return null;
return this.client.rest.cdn.RoleIcon(this.id, this.icon, format, size);
}

/**
* Whether this role equals another role. It compares all properties, so for most operations
* it is advisable to just compare `role.id === role2.id` as it is much faster and is often
Expand All @@ -366,7 +419,9 @@ class Role extends Base {
this.hoist === role.hoist &&
this.position === role.position &&
this.permissions.bitfield === role.permissions.bitfield &&
this.managed === role.managed
this.managed === role.managed &&
this.icon === role.icon &&
this.unicodeEmoji === role.unicodeEmoji
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/util/Constants.js
Expand Up @@ -73,6 +73,8 @@ exports.Endpoints = {
TeamIcon: (teamId, hash, options) => makeImageUrl(`${root}/team-icons/${teamId}/${hash}`, options),
Sticker: (stickerId, stickerFormat) =>
`${root}/stickers/${stickerId}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`,
RoleIcon: (roleId, hash, format = 'webp', size) =>
makeImageUrl(`${root}/role-icons/${roleId}/${hash}`, { size, format }),
};
},
invite: (root, code) => `${root}/${code}`,
Expand Down
11 changes: 10 additions & 1 deletion typings/index.d.ts
Expand Up @@ -1647,16 +1647,21 @@ export class Role extends Base {
public rawPosition: number;
public tags: RoleTagData | null;
public comparePositionTo(role: RoleResolvable): number;
public icon: string | null;
public unicodeEmoji: string | null;
public delete(reason?: string): Promise<Role>;
public edit(data: RoleData, reason?: string): Promise<Role>;
public equals(role: Role): boolean;
public iconURL(options?: StaticImageURLOptions): string | null;
public permissionsIn(channel: GuildChannel | Snowflake): Readonly<Permissions>;
public setColor(color: ColorResolvable, reason?: string): Promise<Role>;
public setHoist(hoist?: boolean, reason?: string): Promise<Role>;
public setMentionable(mentionable?: boolean, reason?: string): Promise<Role>;
public setName(name: string, reason?: string): Promise<Role>;
public setPermissions(permissions: PermissionResolvable, reason?: string): Promise<Role>;
public setIcon(icon: BufferResolvable | Base64Resolvable | EmojiResolvable | null, reason?: string): Promise<Role>;
public setPosition(position: number, options?: SetRolePositionOptions): Promise<Role>;
public setUnicodeEmoji(unicodeEmoji: string | null, reason?: string): Promise<Role>;
public toJSON(): unknown;
public toString(): RoleMention;

Expand Down Expand Up @@ -2362,6 +2367,7 @@ export const Constants: {
{ format, size }: { format: AllowedImageFormat; size: AllowedImageSize },
) => string;
Sticker: (stickerId: Snowflake, stickerFormat: StickerFormatType) => string;
RoleIcon: (roleId: Snowflake, hash: string, format: AllowedImageFormat, size: AllowedImageSize) => string;
};
};
WSCodes: {
Expand Down Expand Up @@ -4069,7 +4075,8 @@ export type GuildFeatures =
| 'MORE_STICKERS'
| 'THREE_DAY_THREAD_ARCHIVE'
| 'SEVEN_DAY_THREAD_ARCHIVE'
| 'PRIVATE_THREADS';
| 'PRIVATE_THREADS'
| 'ROLE_ICONS';

export interface GuildMemberEditData {
nick?: string | null;
Expand Down Expand Up @@ -4740,6 +4747,8 @@ export interface RoleData {
position?: number;
permissions?: PermissionResolvable;
mentionable?: boolean;
icon?: BufferResolvable | Base64Resolvable | EmojiResolvable | null;
unicodeEmoji?: string | null;
}

export type RoleMention = '@everyone' | `<@&${Snowflake}>`;
Expand Down

0 comments on commit 7129965

Please sign in to comment.