diff --git a/src/managers/RoleManager.js b/src/managers/RoleManager.js
index b0c5b971c188..21e49bc66324 100644
--- a/src/managers/RoleManager.js
+++ b/src/managers/RoleManager.js
@@ -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');
@@ -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
+ * The `EmojiResolvable` should belong to the same guild as the role.
+ * If not, pass the emoji's URL directly
+ * @property {?string} [unicodeEmoji] The unicode emoji for the role
* @property {string} [reason] The reason for creating this role
*/
@@ -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: {
@@ -139,6 +149,8 @@ class RoleManager extends CachedManager {
hoist,
permissions,
mentionable,
+ icon,
+ unicode_emoji: unicodeEmoji,
},
reason,
});
@@ -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 });
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index 3e565c91c70a..5f4abb7fef51 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -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}
*/
diff --git a/src/structures/Role.js b/src/structures/Role.js
index feea3a2a0ee7..9fb5a09e3210 100644
--- a/src/structures/Role.js
+++ b/src/structures/Role.js
@@ -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}
@@ -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
+ * The `EmojiResolvable` should belong to the same guild as the role.
+ * If not, pass the emoji's URL directly
+ * @property {?string} [unicodeEmoji] The unicode emoji for the role
*/
/**
@@ -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
+ * The `EmojiResolvable` should belong to the same guild as the role.
+ * If not, pass the emoji's URL directly
+ * @param {string} [reason] Reason for changing the role's icon
+ * @returns {Promise}
+ */
+ 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}
+ * @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
@@ -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
@@ -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
);
}
diff --git a/src/util/Constants.js b/src/util/Constants.js
index a71d64fa1aec..80155da577b4 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -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}`,
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 2fc8b099d193..d81713d9e9d6 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -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;
public edit(data: RoleData, reason?: string): Promise;
public equals(role: Role): boolean;
+ public iconURL(options?: StaticImageURLOptions): string | null;
public permissionsIn(channel: GuildChannel | Snowflake): Readonly;
public setColor(color: ColorResolvable, reason?: string): Promise;
public setHoist(hoist?: boolean, reason?: string): Promise;
public setMentionable(mentionable?: boolean, reason?: string): Promise;
public setName(name: string, reason?: string): Promise;
public setPermissions(permissions: PermissionResolvable, reason?: string): Promise;
+ public setIcon(icon: BufferResolvable | Base64Resolvable | EmojiResolvable | null, reason?: string): Promise;
public setPosition(position: number, options?: SetRolePositionOptions): Promise;
+ public setUnicodeEmoji(unicodeEmoji: string | null, reason?: string): Promise;
public toJSON(): unknown;
public toString(): RoleMention;
@@ -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: {
@@ -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;
@@ -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}>`;