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(GuildMember): Add timeouts #7104

Merged
merged 41 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a5e84b2
feat: add basic stuff
Jiralite Dec 12, 2021
e96b7e1
docs: move `GuildMemberEditData` to the manager
Jiralite Dec 12, 2021
f5ce417
feat: add PATCH
Jiralite Dec 12, 2021
4457f8e
feat: add method
Jiralite Dec 12, 2021
3010f14
fix: change property
Jiralite Dec 12, 2021
a270f23
fix: shenanigans
Jiralite Dec 12, 2021
fe51ddb
fix: handle `null`
Jiralite Dec 12, 2021
08526d4
feat: add permission flag
Jiralite Dec 12, 2021
4e22c57
types: add `timeout()`
Jiralite Dec 12, 2021
5efe3dd
fix: display correctly
Jiralite Dec 12, 2021
33eff8e
types: add in `communicationDisabledUntil` for edit
Jiralite Dec 12, 2021
26d203f
fix: sad can't do this
Jiralite Dec 12, 2021
54da5c3
fix: check in equals
Jiralite Dec 12, 2021
c8aa49e
feat: allow seconds
Jiralite Dec 12, 2021
16e4a27
fix(GuildMember): correctly assert to `null`
Jiralite Dec 12, 2021
605bf54
docs: also works with `Date`s
Jiralite Dec 12, 2021
b13faae
types: use `Date`
Jiralite Dec 12, 2021
f48b948
docs: just oxford things
Jiralite Dec 12, 2021
522f5e4
types: insert _
Jiralite Dec 12, 2021
81545da
refactor: shorten second conversion
Jiralite Dec 13, 2021
ceeed28
docs: support ISO 8601 too
Jiralite Dec 13, 2021
9f9be82
refactor: use &&
Jiralite Dec 13, 2021
7a513f1
docs: specify `DateResolvable`
Jiralite Dec 13, 2021
e5b81a0
refactor: use `Date.parse()`
Jiralite Dec 14, 2021
94812a4
refactor: remove`setTime()`
Jiralite Dec 14, 2021
399c8cd
chore: prefer milliseconds
Jiralite Dec 14, 2021
2b82387
refactor: only accept milliseconds in the helper
Jiralite Dec 14, 2021
cc0347c
types: forgot this
Jiralite Dec 14, 2021
21a5c80
docs: Correct timeout wording
Jiralite Dec 14, 2021
786ce38
docs(GuildMember): tweak wording
Jiralite Dec 14, 2021
e42b679
feat: add permission checks to helpers
Jiralite Dec 21, 2021
0c2c7e5
fix: account for lingering timestamps
Jiralite Dec 21, 2021
d4e7c0f
style: spacing
Jiralite Dec 21, 2021
1fbc8b9
fix: perm checks
Jiralite Dec 21, 2021
bab6adc
feat: implement dual helpers
Jiralite Dec 21, 2021
07f4d0f
fix: handle `null`
Jiralite Dec 21, 2021
b2c73f3
chore: remove redundant check
Jiralite Dec 21, 2021
eb57f31
refactor(GuildMember): simplify `timeout()` code
Jiralite Dec 22, 2021
0c64dde
feat(ThreadChannel): add permission checks
Jiralite Dec 22, 2021
7cf9130
refactor: && returns
Jiralite Dec 22, 2021
c62d85a
feat(GuildMember): add `moderatable` getter
Jiralite Dec 22, 2021
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
31 changes: 31 additions & 0 deletions src/managers/GuildMemberManager.js
Expand Up @@ -223,6 +223,19 @@ class GuildMemberManager extends CachedManager {
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 {Date|number|null} [communicationDisabledUntil] The date, timestamp or time in seconds
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
* for the member's communication to be disabled until. Provide `null` to remove the timeout.
*/

/**
* Edits a member of the guild.
* <info>The user must be a member of the guild</info>
Expand All @@ -249,6 +262,24 @@ class GuildMemberManager extends CachedManager {
_data.channel = undefined;
}
_data.roles &&= _data.roles.map(role => (role instanceof Role ? role.id : role));

if (typeof _data.communicationDisabledUntil !== 'undefined') {
if (_data.communicationDisabledUntil === null) {
_data.communication_disabled_until = null;
} else {
let date = new Date(_data.communicationDisabledUntil);
if (date.getUTCFullYear() <= 2015) {
// Assume seconds
const dateFromSeconds = new Date();
date = new Date(
dateFromSeconds.setUTCSeconds(dateFromSeconds.getUTCSeconds() + _data.communicationDisabledUntil),
);
}

_data.communication_disabled_until = date.toISOString();
}
}

let endpoint = this.client.api.guilds(this.guild.id);
if (id === this.client.user.id) {
const keys = Object.keys(_data);
Expand Down
48 changes: 37 additions & 11 deletions src/structures/GuildMember.js
Expand Up @@ -53,6 +53,12 @@ class GuildMember extends Base {
*/
this.pending = false;

/**
* The timestamp this member's timeout will be removed
* @type {?number}
*/
this.communicationDisabledUntilTimestamp = null;

this._roles = [];
if (data) this._patch(data);
}
Expand Down Expand Up @@ -82,6 +88,11 @@ class GuildMember extends Base {
}
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 === null ? null : new Date(data.communication_disabled_until).getTime();
}
}

_clone() {
Expand Down Expand Up @@ -159,6 +170,15 @@ class GuildMember extends Base {
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) : null;
}

/**
* The last time this member started boosting the guild
* @type {?Date}
Expand Down Expand Up @@ -267,17 +287,6 @@ class GuildMember extends Base {
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.
* @param {GuildMemberEditData} data The data to edit the member with
Expand Down Expand Up @@ -337,6 +346,22 @@ class GuildMember extends Base {
return this.guild.members.ban(this, options);
}

/**
* Timeouts this guild member.
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
* @param {Date|number|null} timeout The date, timestamp or time in seconds
* 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
* // Timeout a guild member for 5 minutes
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
* guildMember.timeout(300, 'They deserved it')
* .then(console.log)
* .catch(console.error);
*/
timeout(timeout, reason) {
kyranet marked this conversation as resolved.
Show resolved Hide resolved
return this.edit({ communicationDisabledUntil: timeout }, reason);
}

/**
* Fetches this GuildMember.
* @param {boolean} [force=true] Whether to skip the cache check and request the API
Expand All @@ -363,6 +388,7 @@ class GuildMember extends Base {
this.nickname === member.nickname &&
this.avatar === member.avatar &&
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])))
);
Expand Down
2 changes: 2 additions & 0 deletions src/util/Permissions.js
Expand Up @@ -98,6 +98,7 @@ class Permissions extends BitField {
* * `USE_EXTERNAL_STICKERS` (use stickers from different guilds)
* * `SEND_MESSAGES_IN_THREADS`
* * `START_EMBEDDED_ACTIVITIES`
* * `MODERATE_MEMBERS`
* @type {Object<string, bigint>}
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
*/
Expand Down Expand Up @@ -144,6 +145,7 @@ Permissions.FLAGS = {
USE_EXTERNAL_STICKERS: 1n << 37n,
SEND_MESSAGES_IN_THREADS: 1n << 38n,
START_EMBEDDED_ACTIVITIES: 1n << 39n,
MODERATE_MEMBERS: 1n << 40n,
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
};

/**
Expand Down
7 changes: 6 additions & 1 deletion typings/index.d.ts
Expand Up @@ -1069,6 +1069,8 @@ export class GuildMember extends PartialTextBasedChannel(Base) {
public guild: Guild;
public readonly id: Snowflake;
public pending: boolean;
public readonly communicationDisabledUntil: Date | null;
public communicationDisabledUntilTimestamp: number | null;
public readonly joinedAt: Date | null;
public joinedTimestamp: number | null;
public readonly kickable: boolean;
Expand All @@ -1084,6 +1086,7 @@ export class GuildMember extends PartialTextBasedChannel(Base) {
public readonly voice: VoiceState;
public avatarURL(options?: ImageURLOptions): string | null;
public ban(options?: BanOptions): Promise<GuildMember>;
public timeout(timeout: number | null, reason?: string): Promise<GuildMember>;
public fetch(force?: boolean): Promise<GuildMember>;
public createDM(force?: boolean): Promise<DMChannel>;
public deleteDM(): Promise<DMChannel>;
Expand Down Expand Up @@ -4481,6 +4484,7 @@ export interface GuildMemberEditData {
mute?: boolean;
deaf?: boolean;
channel?: GuildVoiceChannelResolvable | null;
communicationDisabledUntil?: number | null;
}

export type GuildMemberResolvable = GuildMember | UserResolvable;
Expand Down Expand Up @@ -4971,7 +4975,8 @@ export type PermissionString =
| 'CREATE_PRIVATE_THREADS'
| 'USE_EXTERNAL_STICKERS'
| 'SEND_MESSAGES_IN_THREADS'
| 'START_EMBEDDED_ACTIVITIES';
| 'START_EMBEDDED_ACTIVITIES'
| 'MODERATE MEMBERS';
Jiralite marked this conversation as resolved.
Show resolved Hide resolved

export type RecursiveArray<T> = ReadonlyArray<T | RecursiveArray<T>>;

Expand Down