discord.js-selfbot-v13/src/structures/GuildMember.js
March 7th de5fe927a3 fix(User): Profile data (API) change
- Add GuildMember bio, banner, ...
2022-08-09 10:42:30 +07:00

580 lines
16 KiB
JavaScript

'use strict';
const process = require('node:process');
const Base = require('./Base');
const VoiceState = require('./VoiceState');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const { Error } = require('../errors');
const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager');
const Permissions = require('../util/Permissions');
/**
* @type {WeakSet<GuildMember>}
* @private
* @internal
*/
const deletedGuildMembers = new WeakSet();
let deprecationEmittedForDeleted = false;
/**
* Represents a member of a guild on Discord.
* @implements {TextBasedChannel}
* @extends {Base}
*/
class GuildMember extends Base {
constructor(client, data, guild) {
super(client);
/**
* The guild that this member is part of
* @type {Guild}
*/
this.guild = guild;
/**
* The timestamp the member joined the guild at
* @type {?number}
*/
this.joinedTimestamp = null;
/**
* The last timestamp this member started boosting the guild
* @type {?number}
*/
this.premiumSinceTimestamp = null;
/**
* The nickname of this member, if they have one
* @type {?string}
*/
this.nickname = null;
/**
* Whether this member has yet to pass the guild's membership gate
* @type {boolean}
*/
this.pending = false;
/**
* The timestamp this member's timeout will be removed
* @type {?number}
*/
this.communicationDisabledUntilTimestamp = null;
this._roles = [];
if (data) this._patch(data);
}
_patch(data) {
if ('user' in data) {
/**
* The user that this guild member instance represents
* @type {?User}
*/
this.user = this.client.users._add(data.user, true);
}
if ('nick' in data) this.nickname = data.nick;
if ('avatar' in data) {
/**
* The guild member's avatar hash
* @type {?string}
*/
this.avatar = data.avatar;
} else if (typeof this.avatar !== 'string') {
this.avatar = null;
}
if ('joined_at' in data) this.joinedTimestamp = new Date(data.joined_at).getTime();
if ('premium_since' in data) {
this.premiumSinceTimestamp = data.premium_since ? new Date(data.premium_since).getTime() : null;
}
if ('roles' in data) this._roles = data.roles;
this.pending = data.pending ?? false;
if ('communication_disabled_until' in data) {
this.communicationDisabledUntilTimestamp =
data.communication_disabled_until && Date.parse(data.communication_disabled_until);
}
}
_ProfilePatch(data) {
if ('accent_color' in data) {
/**
* The member's accent color
* <info>The user must be force fetched for this property to be present or be updated</info>
* @type {?number}
*/
this.accentColor = data.accent_color;
}
if ('banner' in data) {
/**
* The member's banner hash
* <info>The user must be force fetched for this property to be present or be updated</info>
* @type {?string}
*/
this.banner = data.banner;
}
if ('bio' in data) {
/**
* The member's biography (About me)
* <info>The user must be force fetched for this property to be present or be updated</info>
* @type {?string}
*/
this.bio = data.bio;
}
}
_clone() {
const clone = super._clone();
clone._roles = this._roles.slice();
return clone;
}
/**
* Whether or not the structure has been deleted
* @type {boolean}
* @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
*/
get deleted() {
if (!deprecationEmittedForDeleted) {
deprecationEmittedForDeleted = true;
process.emitWarning(
'GuildMember#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
'DeprecationWarning',
);
}
return deletedGuildMembers.has(this);
}
set deleted(value) {
if (!deprecationEmittedForDeleted) {
deprecationEmittedForDeleted = true;
process.emitWarning(
'GuildMember#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
'DeprecationWarning',
);
}
if (value) deletedGuildMembers.add(this);
else deletedGuildMembers.delete(this);
}
/**
* Whether this GuildMember is a partial
* @type {boolean}
* @readonly
*/
get partial() {
return this.joinedTimestamp === null;
}
/**
* A manager for the roles belonging to this member
* @type {GuildMemberRoleManager}
* @readonly
*/
get roles() {
return new GuildMemberRoleManager(this);
}
/**
* The voice state of this member
* @type {VoiceState}
* @readonly
*/
get voice() {
return this.guild.voiceStates.cache.get(this.id) ?? new VoiceState(this.guild, { user_id: this.id });
}
/**
* A link to the member's guild avatar.
* @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
avatarURL({ format, size, dynamic } = {}) {
if (!this.avatar) return null;
return this.client.rest.cdn.GuildMemberAvatar(this.guild.id, this.id, this.avatar, format, size, dynamic);
}
/**
* A link to the user's banner.
* <info>This method will throw an error if called before the user is force fetched Profile.
* See {@link GuildMember#banner} for more info</info>
* @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {?string}
*/
bannerURL({ format, size, dynamic } = {}) {
if (typeof this.banner === 'undefined') {
throw new Error('USER_BANNER_NOT_FETCHED');
}
if (!this.banner) return null;
return this.client.rest.cdn.GuildMemberBanner(this.guild.id, this.id, this.banner, format, size, dynamic);
}
/**
* A link to the member's guild avatar if they have one.
* Otherwise, a link to their {@link User#displayAvatarURL} will be returned.
* @param {ImageURLOptions} [options={}] Options for the Image URL
* @returns {string}
*/
displayAvatarURL(options) {
return this.avatarURL(options) ?? this.user.displayAvatarURL(options);
}
/**
* The time this member joined the guild
* @type {?Date}
* @readonly
*/
get joinedAt() {
return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null;
}
/**
* The time this member's timeout will be removed
* @type {?Date}
* @readonly
*/
get communicationDisabledUntil() {
return this.communicationDisabledUntilTimestamp && new Date(this.communicationDisabledUntilTimestamp);
}
/**
* The last time this member started boosting the guild
* @type {?Date}
* @readonly
*/
get premiumSince() {
return this.premiumSinceTimestamp ? new Date(this.premiumSinceTimestamp) : null;
}
/**
* The presence of this guild member
* @type {?Presence}
* @readonly
*/
get presence() {
return this.guild.presences.resolve(this.id);
}
/**
* The displayed color of this member in base 10
* @type {number}
* @readonly
*/
get displayColor() {
return this.roles.color?.color ?? 0;
}
/**
* The displayed color of this member in hexadecimal
* @type {string}
* @readonly
*/
get displayHexColor() {
return this.roles.color?.hexColor ?? '#000000';
}
/**
* The member's id
* @type {Snowflake}
* @readonly
*/
get id() {
return this.user.id;
}
/**
* The nickname of this member, or their username if they don't have one
* @type {?string}
* @readonly
*/
get displayName() {
return this.nickname ?? this.user.username;
}
/**
* The overall set of permissions for this member, taking only roles and owner status into account
* @type {Readonly<Permissions>}
* @readonly
*/
get permissions() {
if (this.user.id === this.guild.ownerId) return new Permissions(Permissions.ALL).freeze();
return new Permissions(this.roles.cache.map(role => role.permissions)).freeze();
}
/**
* Whether the client user is above this user in the hierarchy, according to role position and guild ownership.
* This is a prerequisite for many moderative actions.
* @type {boolean}
* @readonly
*/
get manageable() {
if (this.user.id === this.guild.ownerId) return false;
if (this.user.id === this.client.user.id) return false;
if (this.client.user.id === this.guild.ownerId) return true;
if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME');
return this.guild.me.roles.highest.comparePositionTo(this.roles.highest) > 0;
}
/**
* Whether this member is kickable by the client user
* @type {boolean}
* @readonly
*/
get kickable() {
return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.KICK_MEMBERS);
}
/**
* Whether this member is bannable by the client user
* @type {boolean}
* @readonly
*/
get bannable() {
return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS);
}
/**
* Whether this member is moderatable by the client user
* @type {boolean}
* @readonly
*/
get moderatable() {
return (
!this.permissions.has(Permissions.FLAGS.ADMINISTRATOR) &&
this.manageable &&
(this.guild.me?.permissions.has(Permissions.FLAGS.MODERATE_MEMBERS) ?? false)
);
}
/**
* Whether this member is currently timed out
* @returns {boolean}
*/
isCommunicationDisabled() {
return this.communicationDisabledUntilTimestamp > Date.now();
}
/**
* Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel,
* taking into account roles and permission overwrites.
* @param {GuildChannelResolvable} channel The guild channel to use as context
* @returns {Readonly<Permissions>}
*/
permissionsIn(channel) {
channel = this.guild.channels.resolve(channel);
if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
return channel.permissionsFor(this);
}
/**
* Edits this member.
* @param {GuildMemberEditData} data The data to edit the member with
* @param {string} [reason] Reason for editing this user
* @returns {Promise<GuildMember>}
*/
edit(data, reason) {
return this.guild.members.edit(this, data, reason);
}
/**
* Sets the nickname for this member.
* @param {?string} nick The nickname for the guild member, or `null` if you want to reset their nickname
* @param {string} [reason] Reason for setting the nickname
* @returns {Promise<GuildMember>}
*/
setNickname(nick, reason) {
return this.edit({ nick }, reason);
}
/**
* Sets the guild avatar of the logged in client.
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
* @returns {Promise<GuildMember>}
*/
setAvatar(avatar) {
if (this.user.id !== this.client.user.id) {
throw new Error('ONLY_ME');
}
if (this.client.user.nitroType !== 'NITRO_BOOST') {
throw new Error('NITRO_BOOST_REQUIRED', 'avatar');
}
return this.edit({ avatar });
}
/**
* Sets the guild banner of the logged in client.
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
* @returns {Promise<GuildMember>}
*/
setBanner(banner) {
if (this.user.id !== this.client.user.id) {
throw new Error('ONLY_ME');
}
if (this.client.user.nitroType !== 'NITRO_BOOST') {
throw new Error('NITRO_BOOST_REQUIRED', 'banner');
}
return this.edit({ banner });
}
/**
* Set Guild About me
* @param {string | null} bio Bio to set
* @returns {Promise<GuildMember>}
*/
setAboutMe(bio = null) {
if (this.user.id !== this.client.user.id) {
throw new Error('ONLY_ME');
}
if (this.client.user.nitroType !== 'NITRO_BOOST') {
throw new Error('NITRO_BOOST_REQUIRED', 'bio');
}
return this.edit({ bio });
}
/**
* Creates a DM channel between the client and this member.
* @param {boolean} [force=false] Whether to skip the cache check and request the API
* @returns {Promise<DMChannel>}
*/
createDM(force = false) {
return this.user.createDM(force);
}
/**
* Deletes any DMs with this member.
* @returns {Promise<DMChannel>}
*/
deleteDM() {
return this.user.deleteDM();
}
/**
* Kicks this member from the guild.
* @param {string} [reason] Reason for kicking user
* @returns {Promise<GuildMember>}
*/
kick(reason) {
return this.guild.members.kick(this, reason);
}
/**
* Bans this guild member.
* @param {BanOptions} [options] Options for the ban
* @returns {Promise<GuildMember>}
* @example
* // ban a guild member
* guildMember.ban({ days: 7, reason: 'They deserved it' })
* .then(console.log)
* .catch(console.error);
*/
ban(options) {
return this.guild.members.ban(this, options);
}
/**
* Times this guild member out.
* @param {DateResolvable|null} communicationDisabledUntil The date or timestamp
* for the member's communication to be disabled until. Provide `null` to remove the timeout.
* @param {string} [reason] The reason for this timeout.
* @returns {Promise<GuildMember>}
* @example
* // Time a guild member out for 5 minutes
* guildMember.disableCommunicationUntil(Date.now() + (5 * 60 * 1000), 'They deserved it')
* .then(console.log)
* .catch(console.error);
*/
disableCommunicationUntil(communicationDisabledUntil, reason) {
return this.edit({ communicationDisabledUntil }, reason);
}
/**
* Times this guild member out.
* @param {number|null} timeout The time in milliseconds
* for the member's communication to be disabled until. Provide `null` to remove the timeout.
* @param {string} [reason] The reason for this timeout.
* @returns {Promise<GuildMember>}
* @example
* // Time a guild member out for 5 minutes
* guildMember.timeout(5 * 60 * 1000, 'They deserved it')
* .then(console.log)
* .catch(console.error);
*/
timeout(timeout, reason) {
return this.disableCommunicationUntil(timeout && Date.now() + timeout, reason);
}
/**
* Fetches this GuildMember.
* @param {boolean} [force=true] Whether to skip the cache check and request the API
* @returns {Promise<GuildMember>}
*/
fetch(force = true) {
return this.guild.members.fetch({ user: this.id, cache: true, force });
}
/**
* Whether this guild member equals another guild member. It compares all properties, so for most
* comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster
* and is often what most users need.
* @param {GuildMember} member The member to compare with
* @returns {boolean}
*/
equals(member) {
return (
member instanceof this.constructor &&
this.id === member.id &&
this.partial === member.partial &&
this.guild.id === member.guild.id &&
this.joinedTimestamp === member.joinedTimestamp &&
this.nickname === member.nickname &&
this.avatar === member.avatar &&
this.accentColor === member.accentColor &&
this.bio === member.bio &&
this.pending === member.pending &&
this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp &&
(this._roles === member._roles ||
(this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i])))
);
}
/**
* When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object.
* @returns {string}
* @example
* // Logs: Hello from <@123456789012345678>!
* console.log(`Hello from ${member}!`);
*/
toString() {
return `<@${this.nickname ? '!' : ''}${this.user.id}>`;
}
toJSON() {
const json = super.toJSON({
guild: 'guildId',
user: 'userId',
displayName: true,
roles: true,
});
json.avatarURL = this.avatarURL();
json.displayAvatarURL = this.displayAvatarURL();
return json;
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
send() {}
}
TextBasedChannel.applyToClass(GuildMember);
exports.GuildMember = GuildMember;
exports.deletedGuildMembers = deletedGuildMembers;
/**
* @external APIGuildMember
* @see {@link https://discord.com/developers/docs/resources/guild#guild-member-object}
*/