Group DM channel

Feature list: Create, Add member, Remove member, fetchInvite, createInvite, setName, setIcon, send Message, leave .-.
This commit is contained in:
March 7th 2022-04-03 11:53:09 +07:00
parent ed996371b0
commit 54d0e9d272
8 changed files with 561 additions and 307 deletions

View File

@ -117,8 +117,6 @@ User {
bot: false, bot: false,
system: false, system: false,
flags: UserFlagsBitField { bitfield: 256 }, flags: UserFlagsBitField { bitfield: 256 },
friend: false,
blocked: false,
note: null, note: null,
connectedAccounts: [], connectedAccounts: [],
premiumSince: 1623357181151, premiumSince: 1623357181151,
@ -156,6 +154,40 @@ Guild {}
``` ```
</details> </details>
## Group DM
<details>
<summary><strong>Click to show</strong></summary>
Code:
```js
/* Create */
const memberAdd = [
client.users.cache.get('id1'),
client.users.cache.get('id2'),
...
client.users.cache.get('id9')
]
// Max member add to Group: 9, Min: 2
await client.channels.createGroupDM(memberAdd);
/* Edit */
const groupDM = client.channels.cache.get('id');
await groupDM.setName('New Name');
await groupDM.setIcon('iconURL');
await groupDM.getInvite();
await groupDM.fetchInvite();
await groupDM.removeInvite(invite);
await groupDM.addMember(user);
await groupDM.removeMember(user);
/* Text Channel not Bulk delete */
await groupDM.send('Hello World');
await groupDM.delete(); // Leave
```
Response
```js
Guild {}
```
</details>
## Custom Status and RPC ## Custom Status and RPC
<details> <details>

View File

@ -1,6 +1,6 @@
{ {
"name": "discord.js-selfbot-v13", "name": "discord.js-selfbot-v13",
"version": "1.2.6", "version": "1.2.7",
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]", "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
"main": "./src/index.js", "main": "./src/index.js",
"types": "./typings/index.d.ts", "types": "./typings/index.d.ts",
@ -47,6 +47,7 @@
"@types/ws": "^8.5.2", "@types/ws": "^8.5.2",
"axios": "^0.26.1", "axios": "^0.26.1",
"bignumber.js": "^9.0.2", "bignumber.js": "^9.0.2",
"bufferutil": "^4.0.6",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"discord-api-types": "^0.27.3", "discord-api-types": "^0.27.3",
"discord-bettermarkdown": "^1.1.0", "discord-bettermarkdown": "^1.1.0",
@ -59,6 +60,7 @@
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"undici": "^4.15.0", "undici": "^4.15.0",
"utf-8-validate": "^5.0.9",
"ws": "^8.5.0" "ws": "^8.5.0"
}, },
"engines": { "engines": {

View File

@ -56,7 +56,7 @@ const Messages = {
EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.', EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.',
EMBED_DESCRIPTION: 'MessageEmbed description must be a string.', EMBED_DESCRIPTION: 'MessageEmbed description must be a string.',
EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.', EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.',
/* add */ /* add */
EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.', EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.',
BUTTON_LABEL: 'MessageButton label must be a string', BUTTON_LABEL: 'MessageButton label must be a string',
@ -152,6 +152,10 @@ const Messages = {
INVITE_NOT_FOUND: 'Could not find the requested invite.', INVITE_NOT_FOUND: 'Could not find the requested invite.',
NOT_OWNER_GROUP_DM_CHANNEL: "You can't do this action [Missing Permission]",
USER_ALREADY_IN_GROUP_DM_CHANNEL: 'User is already in the channel.',
USER_NOT_IN_GROUP_DM_CHANNEL: 'User is not in the channel.',
DELETE_GROUP_DM_CHANNEL: DELETE_GROUP_DM_CHANNEL:
"Bots don't have access to Group DM Channels and cannot delete them", "Bots don't have access to Group DM Channels and cannot delete them",
FETCH_GROUP_DM_CHANNEL: FETCH_GROUP_DM_CHANNEL:
@ -200,7 +204,8 @@ const Messages = {
APPLICATION_ID_INVALID: "The application isn't BOT", APPLICATION_ID_INVALID: "The application isn't BOT",
INVALID_NITRO: 'Invalid Nitro Code', INVALID_NITRO: 'Invalid Nitro Code',
MESSAGE_ID_NOT_FOUND: 'Message ID not found', MESSAGE_ID_NOT_FOUND: 'Message ID not found',
MESSAGE_EMBED_LINK_LENGTH: 'Message content with embed link length is too long', MESSAGE_EMBED_LINK_LENGTH:
'Message content with embed link length is too long',
}; };
for (const [name, message] of Object.entries(Messages)) register(name, message); for (const [name, message] of Object.entries(Messages)) register(name, message);

View File

@ -4,6 +4,8 @@ const process = require('node:process');
const CachedManager = require('./CachedManager'); const CachedManager = require('./CachedManager');
const { Channel } = require('../structures/Channel'); const { Channel } = require('../structures/Channel');
const { Events, ThreadChannelTypes } = require('../util/Constants'); const { Events, ThreadChannelTypes } = require('../util/Constants');
const User = require('../structures/User');
const PartialGroupDMChannel = require('../structures/PartialGroupDMChannel');
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
@ -113,8 +115,27 @@ class ChannelManager extends CachedManager {
} }
const data = await this.client.api.channels(id).get(); const data = await this.client.api.channels(id).get();
// delete in cache
this._remove(id);
return this._add(data, null, { cache, allowUnknownGuild }); return this._add(data, null, { cache, allowUnknownGuild });
} }
// Create Group DM
/**
* Create Group DM
* @param {Array<Discord.User>} recipients Array of recipients
* @returns {PartialGroupDMChannel} Channel
*/
async createGroupDM(recipients) {
// Check
if (!recipients || !Array.isArray(recipients)) throw new Error('No recipients || Invalid Type (Array)');
recipients = recipients.filter(r => r instanceof User && r.id && r.friend);
console.log(recipients);
if (recipients.length < 2 || recipients.length > 9) throw new Error('Invalid Users length (2 - 9)');
const data = await this.client.api.users['@me'].channels.post({
data: { recipients: recipients.map((r) => r.id) },
});
return this._add(data, null, { cache: true, allowUnknownGuild: true });
}
} }
module.exports = ChannelManager; module.exports = ChannelManager;

View File

@ -1 +0,0 @@
// Todo: create, add, remove, update

View File

@ -2,6 +2,14 @@
const { Channel } = require('./Channel'); const { Channel } = require('./Channel');
const { Error } = require('../errors'); const { Error } = require('../errors');
const { Collection } = require('discord.js');
const { Message } = require('./Message');
const MessageManager = require('../managers/MessageManager');
const User = require('./User');
const DataResolver = require('../util/DataResolver');
const TextBasedChannel = require('./interfaces/TextBasedChannel');
const Invite = require('./Invite');
/** /**
* Represents a Partial Group DM Channel on Discord. * Represents a Partial Group DM Channel on Discord.
@ -10,7 +18,6 @@ const { Error } = require('../errors');
class PartialGroupDMChannel extends Channel { class PartialGroupDMChannel extends Channel {
constructor(client, data) { constructor(client, data) {
super(client, data); super(client, data);
/** /**
* The name of this Group DM Channel * The name of this Group DM Channel
* @type {?string} * @type {?string}
@ -33,7 +40,84 @@ class PartialGroupDMChannel extends Channel {
* The recipients of this Group DM Channel. * The recipients of this Group DM Channel.
* @type {PartialRecipient[]} * @type {PartialRecipient[]}
*/ */
this.recipients = data.recipients; this.recipients = new Collection();
/**
* Messages data
* @type {Collection}
*/
this.messages = new MessageManager(this);
/**
* Last Message ID
* @type {?snowflake<Message.id>}
*/
this.lastMessageId = null;
/**
* Last Pin Timestamp
* @type {UnixTimestamp}
*/
this.lastPinTimestamp = null;
/**
* The owner of this Group DM Channel
* @type {?User}
* @readonly
*/
this.owner = client.users.cache.get(data.owner_id);
this.ownerId = data.owner_id;
/**
* Invites fetch
*/
this.invites = new Collection();
this._setup(client, data);
}
/**
*
* @param {Discord.Client} client
* @param {object} data
* @private
*/
_setup(client, data) {
if ('recipients' in data) {
Promise.all(
data.recipients.map((recipient) => {
this.recipients.set(
recipient.id,
client.users.cache.get(data.owner_id) || recipient,
);
}),
);
}
if ('last_pin_timestamp' in data) {
const date = new Date(data.last_pin_timestamp);
this.lastPinTimestamp = date.getTime();
}
if ('last_message_id' in data) {
this.lastMessageId = data.last_message_id;
}
}
/**
*
* @param {Object} data name, icon
* @returns
* @private
*/
async edit(data) {
const _data = {};
if ('name' in data) _data.name = data.name?.trim() ?? null;
if (typeof data.icon !== 'undefined')
_data.icon = await DataResolver.resolveImage(data.icon);
const newData = await this.client.api.channels(this.id).patch({
data: _data,
});
return this.client.actions.ChannelUpdate.handle(newData).updated;
} }
/** /**
@ -42,16 +126,85 @@ class PartialGroupDMChannel extends Channel {
* @returns {?string} * @returns {?string}
*/ */
iconURL({ format, size } = {}) { iconURL({ format, size } = {}) {
return this.icon && this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size); return (
this.icon &&
this.client.rest.cdn.GDMIcon(this.id, this.icon, format, size)
);
} }
delete() { async addMember(user) {
return Promise.reject(new Error('DELETE_GROUP_DM_CHANNEL')); if (this.ownerId !== this.client.user.id)
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
if (!user instanceof User)
return Promise.reject(
new TypeError('User is not an instance of Discord.User'),
);
if (this.recipients.get(user.id)) return Promise.reject(new Error('USER_ALREADY_IN_GROUP_DM_CHANNEL'));
//
await this.client.api.channels[this.id].recipients[user.id].put();
this.recipients.set(user.id, user);
return this;
} }
fetch() { async removeMember(user) {
return Promise.reject(new Error('FETCH_GROUP_DM_CHANNEL')); if (this.ownerId !== this.client.user.id)
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
if (!user instanceof User)
return Promise.reject(
new TypeError('User is not an instance of Discord.User'),
);
if (!this.recipients.get(user.id)) return Promise.reject(new Error('USER_NOT_IN_GROUP_DM_CHANNEL'));
await this.client.api.channels[this.id].recipients[user.id].delete();
this.recipients.delete(user.id);
return this;
} }
setName(name) {
return this.edit({ name });
}
setIcon(icon) {
return this.edit({ icon });
}
async getInvite() {
const inviteCode = await this.client.api.channels(this.id).invites.post({
data: {
max_age: 86400,
},
});
const invite = new Invite(this.client, inviteCode);
this.invites.set(invite.code, invite);
return invite;
}
async fetchInvite(force = false) {
if (this.ownerId !== this.client.user.id)
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
if (!force && this.invites.size) return this.invites;
const invites = await this.client.api.channels(this.id).invites.get();
await Promise.all(invites.map(invite => this.invites.set(invite.code, new Invite(this.client, invite))));
return this.invites;
}
async removeInvite(invite) {
if (this.ownerId !== this.client.user.id)
return Promise.reject(new Error('NOT_OWNER_GROUP_DM_CHANNEL'));
if (!invite instanceof Invite)
return Promise.reject(new TypeError('Invite is not an instance of Discord.Invite'));
await this.client.api.channels(this.id).invites[invite.code].delete();
this.invites.delete(invite.code);
return this;
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
get lastMessage() { }
get lastPinAt() { }
send() { }
sendTyping() { }
} }
TextBasedChannel.applyToClass(PartialGroupDMChannel, false);
module.exports = PartialGroupDMChannel; module.exports = PartialGroupDMChannel;

View File

@ -14,120 +14,134 @@ const ApplicationCommandManager = require('../managers/ApplicationCommandManager
* @extends {Base} * @extends {Base}
*/ */
class User extends Base { class User extends Base {
constructor(client, data) { constructor(client, data) {
super(client); super(client);
/** /**
* The user's id * The user's id
* @type {Snowflake} * @type {Snowflake}
*/ */
this.id = data.id; this.id = data.id;
this.bot = null; this.bot = null;
this.system = null; this.system = null;
this.flags = null; this.flags = null;
this.friend = client.friends.cache.has(this.id);
this.blocked = client.blocked.cache.has(this.id);
// Code written by https://github.com/aiko-chan-ai // Code written by https://github.com/aiko-chan-ai
this.connectedAccounts = []; this.connectedAccounts = [];
this.premiumSince = null; this.premiumSince = null;
this.premiumGuildSince = null; this.premiumGuildSince = null;
this.mutualGuilds = new Collection(); this.mutualGuilds = new Collection();
this.applications = null; this.applications = null;
this.note = null; this.note = null;
this._patch(data); this._patch(data);
} }
_patch(data) { _patch(data) {
if ('username' in data) { if ('username' in data) {
/** /**
* The username of the user * The username of the user
* @type {?string} * @type {?string}
*/ */
this.username = data.username; this.username = data.username;
} else { } else {
this.username ??= null; this.username ??= null;
} }
if ('bot' in data) { if ('bot' in data) {
/** /**
* Whether or not the user is a bot * Whether or not the user is a bot
* @type {?boolean} * @type {?boolean}
*/ */
this.bot = Boolean(data.bot); this.bot = Boolean(data.bot);
if (this.bot == true) { if (this.bot == true) {
this.applications = new ApplicationCommandManager(this.client, undefined, this); this.applications = new ApplicationCommandManager(
} this.client,
} else if (!this.partial && typeof this.bot !== 'boolean') { undefined,
this.bot = false; this,
} );
}
} else if (!this.partial && typeof this.bot !== 'boolean') {
this.bot = false;
}
if ('discriminator' in data) { if ('discriminator' in data) {
/** /**
* A discriminator based on username for the user * A discriminator based on username for the user
* @type {?string} * @type {?string}
*/ */
this.discriminator = data.discriminator; this.discriminator = data.discriminator;
} else { } else {
this.discriminator ??= null; this.discriminator ??= null;
} }
if ('avatar' in data) { if ('avatar' in data) {
/** /**
* The user avatar's hash * The user avatar's hash
* @type {?string} * @type {?string}
*/ */
this.avatar = data.avatar; this.avatar = data.avatar;
} else { } else {
this.avatar ??= null; this.avatar ??= null;
} }
if ('banner' in data) { if ('banner' in data) {
/** /**
* The user banner's hash * The user banner's hash
* <info>The user must be force fetched for this property to be present or be updated</info> * <info>The user must be force fetched for this property to be present or be updated</info>
* @type {?string} * @type {?string}
*/ */
this.banner = data.banner; this.banner = data.banner;
} else if (this.banner !== null) { } else if (this.banner !== null) {
this.banner ??= undefined; this.banner ??= undefined;
} }
if ('accent_color' in data) { if ('accent_color' in data) {
/** /**
* The base 10 accent color of the user's banner * The base 10 accent color of the user's banner
* <info>The user must be force fetched for this property to be present or be updated</info> * <info>The user must be force fetched for this property to be present or be updated</info>
* @type {?number} * @type {?number}
*/ */
this.accentColor = data.accent_color; this.accentColor = data.accent_color;
} else if (this.accentColor !== null) { } else if (this.accentColor !== null) {
this.accentColor ??= undefined; this.accentColor ??= undefined;
} }
if ('system' in data) { if ('system' in data) {
/** /**
* Whether the user is an Official Discord System user (part of the urgent message system) * Whether the user is an Official Discord System user (part of the urgent message system)
* @type {?boolean} * @type {?boolean}
*/ */
this.system = Boolean(data.system); this.system = Boolean(data.system);
} else if (!this.partial && typeof this.system !== 'boolean') { } else if (!this.partial && typeof this.system !== 'boolean') {
this.system = false; this.system = false;
} }
if ('public_flags' in data) { if ('public_flags' in data) {
/** /**
* The flags for this user * The flags for this user
* @type {?UserFlags} * @type {?UserFlags}
*/ */
this.flags = new UserFlags(data.public_flags); this.flags = new UserFlags(data.public_flags);
} }
} }
/**
* Friend ?
* @readonly
*/
get friend() {
return this.client.friends.cache.has(this.id);
}
/**
* Blocked ?
* @readonly
*/
get blocked() {
return this.client.blocked.cache.has(this.id);
}
// Code written by https://github.com/aiko-chan-ai // Code written by https://github.com/aiko-chan-ai
_ProfilePatch(data) { _ProfilePatch(data) {
@ -223,221 +237,233 @@ class User extends Base {
.relationships[this.id].delete.then((_) => _); .relationships[this.id].delete.then((_) => _);
} }
/**
* Whether this User is a partial
* @type {boolean}
* @readonly
*/
get partial() {
return typeof this.username !== 'string';
}
/** /**
* Whether this User is a partial * The timestamp the user was created at
* @type {boolean} * @type {number}
* @readonly * @readonly
*/ */
get partial() { get createdTimestamp() {
return typeof this.username !== 'string'; return SnowflakeUtil.timestampFrom(this.id);
} }
/** /**
* The timestamp the user was created at * The time the user was created at
* @type {number} * @type {Date}
* @readonly * @readonly
*/ */
get createdTimestamp() { get createdAt() {
return SnowflakeUtil.timestampFrom(this.id); return new Date(this.createdTimestamp);
} }
/** /**
* The time the user was created at * A link to the user's avatar.
* @type {Date} * @param {ImageURLOptions} [options={}] Options for the Image URL
* @readonly * @returns {?string}
*/ */
get createdAt() { avatarURL({ format, size, dynamic } = {}) {
return new Date(this.createdTimestamp); if (!this.avatar) return null;
} return this.client.rest.cdn.Avatar(
this.id,
this.avatar,
format,
size,
dynamic,
);
}
/** /**
* A link to the user's avatar. * A link to the user's default avatar
* @param {ImageURLOptions} [options={}] Options for the Image URL * @type {string}
* @returns {?string} * @readonly
*/ */
avatarURL({ format, size, dynamic } = {}) { get defaultAvatarURL() {
if (!this.avatar) return null; return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5);
return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic); }
}
/** /**
* A link to the user's default avatar * A link to the user's avatar if they have one.
* @type {string} * Otherwise a link to their default avatar will be returned.
* @readonly * @param {ImageURLOptions} [options={}] Options for the Image URL
*/ * @returns {string}
get defaultAvatarURL() { */
return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5); displayAvatarURL(options) {
} return this.avatarURL(options) ?? this.defaultAvatarURL;
}
/** /**
* A link to the user's avatar if they have one. * The hexadecimal version of the user accent color, with a leading hash
* Otherwise a link to their default avatar will be returned. * <info>The user must be force fetched for this property to be present</info>
* @param {ImageURLOptions} [options={}] Options for the Image URL * @type {?string}
* @returns {string} * @readonly
*/ */
displayAvatarURL(options) { get hexAccentColor() {
return this.avatarURL(options) ?? this.defaultAvatarURL; if (typeof this.accentColor !== 'number') return this.accentColor;
} return `#${this.accentColor.toString(16).padStart(6, '0')}`;
}
/** /**
* The hexadecimal version of the user accent color, with a leading hash * A link to the user's banner.
* <info>The user must be force fetched for this property to be present</info> * <info>This method will throw an error if called before the user is force fetched.
* @type {?string} * See {@link User#banner} for more info</info>
* @readonly * @param {ImageURLOptions} [options={}] Options for the Image URL
*/ * @returns {?string}
get hexAccentColor() { */
if (typeof this.accentColor !== 'number') return this.accentColor; bannerURL({ format, size, dynamic } = {}) {
return `#${this.accentColor.toString(16).padStart(6, '0')}`; if (typeof this.banner === 'undefined')
} throw new Error('USER_BANNER_NOT_FETCHED');
if (!this.banner) return null;
return this.client.rest.cdn.Banner(
this.id,
this.banner,
format,
size,
dynamic,
);
}
/** /**
* A link to the user's banner. * The Discord "tag" (e.g. `hydrabolt#0001`) for this user
* <info>This method will throw an error if called before the user is force fetched. * @type {?string}
* See {@link User#banner} for more info</info> * @readonly
* @param {ImageURLOptions} [options={}] Options for the Image URL */
* @returns {?string} get tag() {
*/ return typeof this.username === 'string'
bannerURL({ format, size, dynamic } = {}) { ? `${this.username}#${this.discriminator}`
if (typeof this.banner === 'undefined') throw new Error('USER_BANNER_NOT_FETCHED'); : null;
if (!this.banner) return null; }
return this.client.rest.cdn.Banner(this.id, this.banner, format, size, dynamic);
}
/** /**
* The Discord "tag" (e.g. `hydrabolt#0001`) for this user * The DM between the client's user and this user
* @type {?string} * @type {?DMChannel}
* @readonly * @readonly
*/ */
get tag() { get dmChannel() {
return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null; return this.client.users.dmChannel(this.id);
} }
/** /**
* The DM between the client's user and this user * Creates a DM channel between the client and the user.
* @type {?DMChannel} * @param {boolean} [force=false] Whether to skip the cache check and request the API
* @readonly * @returns {Promise<DMChannel>}
*/ */
get dmChannel() { createDM(force = false) {
return this.client.users.dmChannel(this.id); return this.client.users.createDM(this.id, force);
} }
/** /**
* Creates a DM channel between the client and the user. * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful.
* @param {boolean} [force=false] Whether to skip the cache check and request the API * @returns {Promise<DMChannel>}
* @returns {Promise<DMChannel>} */
*/ deleteDM() {
createDM(force = false) { return this.client.users.deleteDM(this.id);
return this.client.users.createDM(this.id, force); }
}
/** /**
* Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. * Checks if the user is equal to another.
* @returns {Promise<DMChannel>} * It compares id, username, discriminator, avatar, banner, accent color, and bot flags.
*/ * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties.
deleteDM() { * @param {User} user User to compare with
return this.client.users.deleteDM(this.id); * @returns {boolean}
} */
equals(user) {
return (
user &&
this.id === user.id &&
this.username === user.username &&
this.discriminator === user.discriminator &&
this.avatar === user.avatar &&
this.flags?.bitfield === user.flags?.bitfield &&
this.banner === user.banner &&
this.accentColor === user.accentColor
);
}
/** /**
* Checks if the user is equal to another. * Compares the user with an API user object
* It compares id, username, discriminator, avatar, banner, accent color, and bot flags. * @param {APIUser} user The API user object to compare
* It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. * @returns {boolean}
* @param {User} user User to compare with * @private
* @returns {boolean} */
*/ _equals(user) {
equals(user) { return (
return ( user &&
user && this.id === user.id &&
this.id === user.id && this.username === user.username &&
this.username === user.username && this.discriminator === user.discriminator &&
this.discriminator === user.discriminator && this.avatar === user.avatar &&
this.avatar === user.avatar && this.flags?.bitfield === user.public_flags &&
this.flags?.bitfield === user.flags?.bitfield && ('banner' in user ? this.banner === user.banner : true) &&
this.banner === user.banner && ('accent_color' in user ? this.accentColor === user.accent_color : true)
this.accentColor === user.accentColor );
); }
}
/** /**
* Compares the user with an API user object * Fetches this user's flags.
* @param {APIUser} user The API user object to compare * @param {boolean} [force=false] Whether to skip the cache check and request the API
* @returns {boolean} * @returns {Promise<UserFlags>}
* @private */
*/ fetchFlags(force = false) {
_equals(user) { return this.client.users.fetchFlags(this.id, { force });
return ( }
user &&
this.id === user.id &&
this.username === user.username &&
this.discriminator === user.discriminator &&
this.avatar === user.avatar &&
this.flags?.bitfield === user.public_flags &&
('banner' in user ? this.banner === user.banner : true) &&
('accent_color' in user ? this.accentColor === user.accent_color : true)
);
}
/** /**
* Fetches this user's flags. * Fetches this user.
* @param {boolean} [force=false] Whether to skip the cache check and request the API * @param {boolean} [force=true] Whether to skip the cache check and request the API
* @returns {Promise<UserFlags>} * @returns {Promise<User>}
*/ */
fetchFlags(force = false) { fetch(force = true) {
return this.client.users.fetchFlags(this.id, { force }); return this.client.users.fetch(this.id, { force });
} }
/** /**
* Fetches this user. * When concatenated with a string, this automatically returns the user's mention instead of the User object.
* @param {boolean} [force=true] Whether to skip the cache check and request the API * @returns {string}
* @returns {Promise<User>} * @example
*/ * // Logs: Hello from <@123456789012345678>!
fetch(force = true) { * console.log(`Hello from ${user}!`);
return this.client.users.fetch(this.id, { force }); */
} toString() {
return `<@${this.id}>`;
}
/** toJSON(...props) {
* When concatenated with a string, this automatically returns the user's mention instead of the User object. const json = super.toJSON(
* @returns {string} {
* @example createdTimestamp: true,
* // Logs: Hello from <@123456789012345678>! defaultAvatarURL: true,
* console.log(`Hello from ${user}!`); hexAccentColor: true,
*/ tag: true,
toString() { },
return `<@${this.id}>`; ...props,
} );
json.avatarURL = this.avatarURL();
json.displayAvatarURL = this.displayAvatarURL();
json.bannerURL = this.banner ? this.bannerURL() : this.banner;
return json;
}
toJSON(...props) { /**
const json = super.toJSON( * Set note to user
{ * @param {String<User.note>} note Note to set
createdTimestamp: true, * @returns {Promise<User.note>}
defaultAvatarURL: true, */
hexAccentColor: true, async setNote(note = null) {
tag: true, await this.client.api.users['@me'].notes(id).put({ data: { note } });
}, return (this.note = note);
...props, }
);
json.avatarURL = this.avatarURL();
json.displayAvatarURL = this.displayAvatarURL();
json.bannerURL = this.banner ? this.bannerURL() : this.banner;
return json;
}
/** // These are here only for documentation purposes - they are implemented by TextBasedChannel
* Set note to user /* eslint-disable no-empty-function */
* @param {String<User.note>} note Note to set send() {}
* @returns {Promise<User.note>}
*/
async setNote(note = null) {
await this.client.api.users['@me']
.notes(id)
.put({ data: { note } });
return this.note = note;
}
// These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */
send() {}
} }
TextBasedChannel.applyToClass(User); TextBasedChannel.applyToClass(User);

20
typings/index.d.ts vendored
View File

@ -1897,12 +1897,25 @@ export class OAuth2Guild extends BaseGuild {
public permissions: Readonly<Permissions>; public permissions: Readonly<Permissions>;
} }
export class PartialGroupDMChannel extends Channel { export class PartialGroupDMChannel extends TextBasedChannelMixin(Channel, ['bulkDelete']) {
private constructor(client: Client, data: RawPartialGroupDMChannelData); private constructor(client: Client, data: RawPartialGroupDMChannelData);
public name: string | null; public name: string | null;
public icon: string | null; public icon: string | null;
public recipients: PartialRecipient[]; public recipients: Collection<User>;
public messages: MessageManager<PartialGroupDMChannel>;
public invites: Collection<Invite.code, Invite>;
public lastMessageId: Snowflake | null;
public lastPinTimestamp: String<number> | null;
public owner: User | null;
public ownerId: Snowflake | null;
public iconURL(options?: StaticImageURLOptions): string | null; public iconURL(options?: StaticImageURLOptions): string | null;
public addMember(user: User): Promise<PartialGroupDMChannel>;
public removeMember(user: User): Promise<PartialGroupDMChannel>;
public setName(name: string): Promise<PartialGroupDMChannel>;
public setIcon(icon: Base64Resolvable | null): Promise<PartialGroupDMChannel>;
public getInvite(): Promise<Invite>;
public fetchInvite(force: boolean): Promise<PartialGroupDMChannel.invites>;
public removeInvite(invite: Invite): Promise<PartialGroupDMChannel>;
} }
export class PermissionOverwrites extends Base { export class PermissionOverwrites extends Base {
@ -2449,6 +2462,8 @@ export class User extends PartialTextBasedChannel(Base) {
public banner: string | null | undefined; public banner: string | null | undefined;
public bot: boolean; public bot: boolean;
public readonly createdAt: Date; public readonly createdAt: Date;
public readonly friend: Boolean;
public readonly blocked: Boolean;
public readonly createdTimestamp: number; public readonly createdTimestamp: number;
public discriminator: string; public discriminator: string;
public readonly defaultAvatarURL: string; public readonly defaultAvatarURL: string;
@ -3044,6 +3059,7 @@ export class BaseGuildEmojiManager extends CachedManager<Snowflake, GuildEmoji,
export class ChannelManager extends CachedManager<Snowflake, AnyChannel, ChannelResolvable> { export class ChannelManager extends CachedManager<Snowflake, AnyChannel, ChannelResolvable> {
private constructor(client: Client, iterable: Iterable<RawChannelData>); private constructor(client: Client, iterable: Iterable<RawChannelData>);
public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<AnyChannel | null>; public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<AnyChannel | null>;
public createGroupDM(recipients: Array<User>): Promise<PartialGroupDMChannel>;
} }
export class ClientUserSettingManager extends CachedManager<Snowflake, null> { export class ClientUserSettingManager extends CachedManager<Snowflake, null> {