mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 11:33:30 +01:00
feat(GuildMember): Add timeouts (#7104)
Co-authored-by: Rodry <38259440+ImRodry@users.noreply.github.com> Co-authored-by: Tiemen <ThaTiemsz@users.noreply.github.com> Co-authored-by: Antonio Román <kyradiscord@gmail.com>
This commit is contained in:
@@ -223,6 +223,19 @@ class GuildMemberManager extends CachedManager {
|
|||||||
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
|
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data for editing a guild member.
|
||||||
|
* @typedef {Object} GuildMemberEditData
|
||||||
|
* @property {?string} [nick] The nickname to set for the member
|
||||||
|
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role ids to apply
|
||||||
|
* @property {boolean} [mute] Whether or not the member should be muted
|
||||||
|
* @property {boolean} [deaf] Whether or not the member should be deafened
|
||||||
|
* @property {GuildVoiceChannelResolvable|null} [channel] Channel to move the member to
|
||||||
|
* (if they are connected to voice), or `null` if you want to disconnect them from voice
|
||||||
|
* @property {DateResolvable|null} [communicationDisabledUntil] The date or timestamp
|
||||||
|
* for the member's communication to be disabled until. Provide `null` to enable communication again.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits a member of the guild.
|
* Edits a member of the guild.
|
||||||
* <info>The user must be a member of the guild</info>
|
* <info>The user must be a member of the guild</info>
|
||||||
@@ -249,6 +262,10 @@ class GuildMemberManager extends CachedManager {
|
|||||||
_data.channel = undefined;
|
_data.channel = undefined;
|
||||||
}
|
}
|
||||||
_data.roles &&= _data.roles.map(role => (role instanceof Role ? role.id : role));
|
_data.roles &&= _data.roles.map(role => (role instanceof Role ? role.id : role));
|
||||||
|
|
||||||
|
_data.communication_disabled_until =
|
||||||
|
_data.communicationDisabledUntil && new Date(_data.communicationDisabledUntil).toISOString();
|
||||||
|
|
||||||
let endpoint = this.client.api.guilds(this.guild.id);
|
let endpoint = this.client.api.guilds(this.guild.id);
|
||||||
if (id === this.client.user.id) {
|
if (id === this.client.user.id) {
|
||||||
const keys = Object.keys(_data);
|
const keys = Object.keys(_data);
|
||||||
|
|||||||
@@ -68,8 +68,16 @@ class BaseGuildVoiceChannel extends GuildChannel {
|
|||||||
*/
|
*/
|
||||||
get joinable() {
|
get joinable() {
|
||||||
if (!this.viewable) return false;
|
if (!this.viewable) return false;
|
||||||
if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) return false;
|
const permissions = this.permissionsFor(this.client.user);
|
||||||
return true;
|
if (!permissions) return false;
|
||||||
|
|
||||||
|
// This flag allows joining even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
|
||||||
|
permissions.has(Permissions.FLAGS.CONNECT, false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -510,6 +510,11 @@ class GuildChannel extends Channel {
|
|||||||
if (this.client.user.id === this.guild.ownerId) return true;
|
if (this.client.user.id === this.guild.ownerId) return true;
|
||||||
const permissions = this.permissionsFor(this.client.user);
|
const permissions = this.permissionsFor(this.client.user);
|
||||||
if (!permissions) return false;
|
if (!permissions) return false;
|
||||||
|
|
||||||
|
// This flag allows managing even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
if (this.guild.me.communicationDisabledUntilTimestamp > Date.now()) return false;
|
||||||
|
|
||||||
const bitfield = VoiceBasedChannelTypes.includes(this.type)
|
const bitfield = VoiceBasedChannelTypes.includes(this.type)
|
||||||
? Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.CONNECT
|
? Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.CONNECT
|
||||||
: Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.MANAGE_CHANNELS;
|
: Permissions.FLAGS.VIEW_CHANNEL | Permissions.FLAGS.MANAGE_CHANNELS;
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ class GuildMember extends Base {
|
|||||||
*/
|
*/
|
||||||
this.pending = false;
|
this.pending = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp this member's timeout will be removed
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
this.communicationDisabledUntilTimestamp = null;
|
||||||
|
|
||||||
this._roles = [];
|
this._roles = [];
|
||||||
if (data) this._patch(data);
|
if (data) this._patch(data);
|
||||||
}
|
}
|
||||||
@@ -83,6 +89,11 @@ class GuildMember extends Base {
|
|||||||
}
|
}
|
||||||
if ('roles' in data) this._roles = data.roles;
|
if ('roles' in data) this._roles = data.roles;
|
||||||
this.pending = data.pending ?? false;
|
this.pending = data.pending ?? false;
|
||||||
|
|
||||||
|
if ('communication_disabled_until' in data) {
|
||||||
|
this.communicationDisabledUntilTimestamp =
|
||||||
|
data.communication_disabled_until && Date.parse(data.communication_disabled_until);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_clone() {
|
_clone() {
|
||||||
@@ -177,6 +188,15 @@ class GuildMember extends Base {
|
|||||||
return this.joinedTimestamp ? new Date(this.joinedTimestamp) : null;
|
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
|
* The last time this member started boosting the guild
|
||||||
* @type {?Date}
|
* @type {?Date}
|
||||||
@@ -273,6 +293,15 @@ class GuildMember extends Base {
|
|||||||
return this.manageable && this.guild.me.permissions.has(Permissions.FLAGS.BAN_MEMBERS);
|
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.manageable && (this.guild.me?.permissions.has(Permissions.FLAGS.MODERATE_MEMBERS) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel,
|
* Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel,
|
||||||
* taking into account roles and permission overwrites.
|
* taking into account roles and permission overwrites.
|
||||||
@@ -285,17 +314,6 @@ class GuildMember extends Base {
|
|||||||
return channel.permissionsFor(this);
|
return channel.permissionsFor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The data for editing a guild member.
|
|
||||||
* @typedef {Object} GuildMemberEditData
|
|
||||||
* @property {?string} [nick] The nickname to set for the member
|
|
||||||
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role ids to apply
|
|
||||||
* @property {boolean} [mute] Whether or not the member should be muted
|
|
||||||
* @property {boolean} [deaf] Whether or not the member should be deafened
|
|
||||||
* @property {GuildVoiceChannelResolvable|null} [channel] Channel to move the member to
|
|
||||||
* (if they are connected to voice), or `null` if you want to disconnect them from voice
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits this member.
|
* Edits this member.
|
||||||
* @param {GuildMemberEditData} data The data to edit the member with
|
* @param {GuildMemberEditData} data The data to edit the member with
|
||||||
@@ -356,6 +374,38 @@ class GuildMember extends Base {
|
|||||||
return this.guild.members.ban(this, 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.
|
* Fetches this GuildMember.
|
||||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||||
@@ -382,6 +432,7 @@ class GuildMember extends Base {
|
|||||||
this.nickname === member.nickname &&
|
this.nickname === member.nickname &&
|
||||||
this.avatar === member.avatar &&
|
this.avatar === member.avatar &&
|
||||||
this.pending === member.pending &&
|
this.pending === member.pending &&
|
||||||
|
this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp &&
|
||||||
(this._roles === member._roles ||
|
(this._roles === member._roles ||
|
||||||
(this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i])))
|
(this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i])))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -610,9 +610,16 @@ class Message extends Base {
|
|||||||
if (!this.channel?.viewable) {
|
if (!this.channel?.viewable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permissions = this.channel?.permissionsFor(this.client.user);
|
||||||
|
if (!permissions) return false;
|
||||||
|
// This flag allows deleting even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
|
||||||
return Boolean(
|
return Boolean(
|
||||||
this.author.id === this.client.user.id ||
|
this.author.id === this.client.user.id ||
|
||||||
this.channel?.permissionsFor(this.client.user)?.has(Permissions.FLAGS.MANAGE_MESSAGES, false),
|
(permissions.has(Permissions.FLAGS.MANAGE_MESSAGES, false) &&
|
||||||
|
this.guild.me.communicationDisabledUntilTimestamp < Date.now()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -439,7 +439,15 @@ class ThreadChannel extends Channel {
|
|||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get manageable() {
|
get manageable() {
|
||||||
return this.permissionsFor(this.client.user)?.has(Permissions.FLAGS.MANAGE_THREADS, false);
|
const permissions = this.permissionsFor(this.client.user);
|
||||||
|
if (!permissions) return false;
|
||||||
|
// This flag allows managing even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.guild.me.communicationDisabledUntilTimestamp < Date.now() &&
|
||||||
|
permissions.has(Permissions.FLAGS.MANAGE_THREADS, false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -460,11 +468,16 @@ class ThreadChannel extends Channel {
|
|||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get sendable() {
|
get sendable() {
|
||||||
|
const permissions = this.permissionsFor(this.client.user);
|
||||||
|
if (!permissions) return false;
|
||||||
|
// This flag allows sending even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(!(this.archived && this.locked && !this.manageable) &&
|
!(this.archived && this.locked && !this.manageable) &&
|
||||||
(this.type !== 'GUILD_PRIVATE_THREAD' || this.joined || this.manageable) &&
|
(this.type !== 'GUILD_PRIVATE_THREAD' || this.joined || this.manageable) &&
|
||||||
this.permissionsFor(this.client.user)?.has(Permissions.FLAGS.SEND_MESSAGES_IN_THREADS, false)) ??
|
permissions.has(Permissions.FLAGS.SEND_MESSAGES_IN_THREADS, false) &&
|
||||||
false
|
this.guild.me.communicationDisabledUntilTimestamp < Date.now()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,14 @@ class VoiceChannel extends BaseGuildVoiceChannel {
|
|||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get speakable() {
|
get speakable() {
|
||||||
return this.permissionsFor(this.client.user).has(Permissions.FLAGS.SPEAK, false);
|
const permissions = this.permissionsFor(this.client.user);
|
||||||
|
if (!permissions) return false;
|
||||||
|
// This flag allows speaking even if timed out
|
||||||
|
if (permissions.has(Permissions.FLAGS.ADMINISTRATOR, false)) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.guild.me.communicationDisabledUntilTimestamp < Date.now() && permissions.has(Permissions.FLAGS.SPEAK, false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class Permissions extends BitField {
|
|||||||
* * `USE_EXTERNAL_STICKERS` (use stickers from different guilds)
|
* * `USE_EXTERNAL_STICKERS` (use stickers from different guilds)
|
||||||
* * `SEND_MESSAGES_IN_THREADS`
|
* * `SEND_MESSAGES_IN_THREADS`
|
||||||
* * `START_EMBEDDED_ACTIVITIES`
|
* * `START_EMBEDDED_ACTIVITIES`
|
||||||
|
* * `MODERATE_MEMBERS`
|
||||||
* @type {Object<string, bigint>}
|
* @type {Object<string, bigint>}
|
||||||
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
|
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
|
||||||
*/
|
*/
|
||||||
@@ -144,6 +145,7 @@ Permissions.FLAGS = {
|
|||||||
USE_EXTERNAL_STICKERS: 1n << 37n,
|
USE_EXTERNAL_STICKERS: 1n << 37n,
|
||||||
SEND_MESSAGES_IN_THREADS: 1n << 38n,
|
SEND_MESSAGES_IN_THREADS: 1n << 38n,
|
||||||
START_EMBEDDED_ACTIVITIES: 1n << 39n,
|
START_EMBEDDED_ACTIVITIES: 1n << 39n,
|
||||||
|
MODERATE_MEMBERS: 1n << 40n,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
9
typings/index.d.ts
vendored
9
typings/index.d.ts
vendored
@@ -1096,10 +1096,13 @@ export class GuildMember extends PartialTextBasedChannel(Base) {
|
|||||||
public guild: Guild;
|
public guild: Guild;
|
||||||
public readonly id: Snowflake;
|
public readonly id: Snowflake;
|
||||||
public pending: boolean;
|
public pending: boolean;
|
||||||
|
public readonly communicationDisabledUntil: Date | null;
|
||||||
|
public communicationDisabledUntilTimestamp: number | null;
|
||||||
public readonly joinedAt: Date | null;
|
public readonly joinedAt: Date | null;
|
||||||
public joinedTimestamp: number | null;
|
public joinedTimestamp: number | null;
|
||||||
public readonly kickable: boolean;
|
public readonly kickable: boolean;
|
||||||
public readonly manageable: boolean;
|
public readonly manageable: boolean;
|
||||||
|
public readonly moderatable: boolean;
|
||||||
public nickname: string | null;
|
public nickname: string | null;
|
||||||
public readonly partial: false;
|
public readonly partial: false;
|
||||||
public readonly permissions: Readonly<Permissions>;
|
public readonly permissions: Readonly<Permissions>;
|
||||||
@@ -1111,6 +1114,8 @@ export class GuildMember extends PartialTextBasedChannel(Base) {
|
|||||||
public readonly voice: VoiceState;
|
public readonly voice: VoiceState;
|
||||||
public avatarURL(options?: ImageURLOptions): string | null;
|
public avatarURL(options?: ImageURLOptions): string | null;
|
||||||
public ban(options?: BanOptions): Promise<GuildMember>;
|
public ban(options?: BanOptions): Promise<GuildMember>;
|
||||||
|
public disableCommunicationUntil(timeout: DateResolvable | null, reason?: string): Promise<GuildMember>;
|
||||||
|
public timeout(timeout: number | null, reason?: string): Promise<GuildMember>;
|
||||||
public fetch(force?: boolean): Promise<GuildMember>;
|
public fetch(force?: boolean): Promise<GuildMember>;
|
||||||
public createDM(force?: boolean): Promise<DMChannel>;
|
public createDM(force?: boolean): Promise<DMChannel>;
|
||||||
public deleteDM(): Promise<DMChannel>;
|
public deleteDM(): Promise<DMChannel>;
|
||||||
@@ -4590,6 +4595,7 @@ export interface GuildMemberEditData {
|
|||||||
mute?: boolean;
|
mute?: boolean;
|
||||||
deaf?: boolean;
|
deaf?: boolean;
|
||||||
channel?: GuildVoiceChannelResolvable | null;
|
channel?: GuildVoiceChannelResolvable | null;
|
||||||
|
communicationDisabledUntil?: DateResolvable | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GuildMemberResolvable = GuildMember | UserResolvable;
|
export type GuildMemberResolvable = GuildMember | UserResolvable;
|
||||||
@@ -5080,7 +5086,8 @@ export type PermissionString =
|
|||||||
| 'CREATE_PRIVATE_THREADS'
|
| 'CREATE_PRIVATE_THREADS'
|
||||||
| 'USE_EXTERNAL_STICKERS'
|
| 'USE_EXTERNAL_STICKERS'
|
||||||
| 'SEND_MESSAGES_IN_THREADS'
|
| 'SEND_MESSAGES_IN_THREADS'
|
||||||
| 'START_EMBEDDED_ACTIVITIES';
|
| 'START_EMBEDDED_ACTIVITIES'
|
||||||
|
| 'MODERATE_MEMBERS';
|
||||||
|
|
||||||
export type RecursiveArray<T> = ReadonlyArray<T | RecursiveArray<T>>;
|
export type RecursiveArray<T> = ReadonlyArray<T | RecursiveArray<T>>;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user