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 support for role icons #6633

Merged
merged 17 commits into from Oct 3, 2021
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
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);
iShibi marked this conversation as resolved.
Show resolved Hide resolved
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);
iShibi marked this conversation as resolved.
Show resolved Hide resolved
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) =>
iShibi marked this conversation as resolved.
Show resolved Hide resolved
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