Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/managers/RoleManager.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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