diff --git a/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js b/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js new file mode 100644 index 00000000..aa9fca0 --- /dev/null +++ b/src/client/websocket/handlers/CHANNEL_RECIPIENT_ADD.js @@ -0,0 +1,15 @@ +'use strict'; +const { Events } = require('../../../util/Constants'); +module.exports = (client, packet) => { + /** + * Emitted whenever a recipient is added from a group DM. + * @event Client#channelRecipientAdd + * @param {PartialGroupDMChannel} channel Group DM channel + * @param {User} user User + */ + const channel = client.channels.cache.get(packet.d.channel_id); + if (!channel) return; + channel._recipients = channel._recipients.push(packet.d.user); + const user = client.users._add(packet.d.user); + client.emit(Events.CHANNEL_RECIPIENT_ADD, channel, user); +}; diff --git a/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js b/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js new file mode 100644 index 00000000..6ba821c --- /dev/null +++ b/src/client/websocket/handlers/CHANNEL_RECIPIENT_REMOVE.js @@ -0,0 +1,15 @@ +'use strict'; +const { Events } = require('../../../util/Constants'); +module.exports = (client, packet) => { + /** + * Emitted whenever a recipient is removed from a group DM. + * @event Client#channelRecipientRemove + * @param {PartialGroupDMChannel} channel Group DM channel + * @param {User} user User + */ + const channel = client.channels.cache.get(packet.d.channel_id); + if (!channel) return; + channel._recipients = channel._recipients.filter(r => r !== packet.d.user.id); + const user = client.users._add(packet.d.user); + client.emit(Events.CHANNEL_RECIPIENT_REMOVE, channel, user); +}; diff --git a/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js b/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js index 3c6b405..cb613c5 100644 --- a/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js +++ b/src/client/websocket/handlers/RELATIONSHIP_REMOVE.js @@ -4,6 +4,7 @@ const { Events, RelationshipTypes } = require('../../../util/Constants'); module.exports = (client, { d: data }) => { client.relationships.cache.delete(data.id); + client.user.friendNicknames.delete(data.id); /** * Emitted whenever a relationship is delete. * @event Client#relationshipRemove diff --git a/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js b/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js new file mode 100644 index 00000000..6fa196d --- /dev/null +++ b/src/client/websocket/handlers/RELATIONSHIP_UPDATE.js @@ -0,0 +1,18 @@ +'use strict'; + +const { Events, RelationshipTypes } = require('../../../util/Constants'); + +module.exports = (client, { d: data }) => { + client.relationships.cache.set(data.id, data.type); + /** + * Emitted whenever a relationship is updated. + * @event Client#relationshipUpdate + * @param {Snowflake} user The userID that was updated + * @param {RelationshipTypes} type The new relationship type + * @param {Object} data The raw data + */ + if ('nickname' in data) { + client.user.friendNicknames.set(data.id, data.nickname); + } + client.emit(Events.RELATIONSHIP_UPDATE, data.id, RelationshipTypes[data.type], data); +}; diff --git a/src/client/websocket/handlers/index.js b/src/client/websocket/handlers/index.js index a44d719..d5d8ac5 100644 --- a/src/client/websocket/handlers/index.js +++ b/src/client/websocket/handlers/index.js @@ -5,6 +5,7 @@ const handlers = Object.fromEntries([ ['RESUMED', require('./RESUMED')], ['RELATIONSHIP_ADD', require('./RELATIONSHIP_ADD')], ['RELATIONSHIP_REMOVE', require('./RELATIONSHIP_REMOVE')], + ['RELATIONSHIP_UPDATE', require('./RELATIONSHIP_UPDATE')], ['APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE', require('./APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE')], ['APPLICATION_COMMAND_CREATE', require('./APPLICATION_COMMAND_CREATE')], ['APPLICATION_COMMAND_DELETE', require('./APPLICATION_COMMAND_DELETE')], @@ -34,6 +35,8 @@ const handlers = Object.fromEntries([ ['CHANNEL_DELETE', require('./CHANNEL_DELETE')], ['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')], ['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')], + ['CHANNEL_RECIPIENT_ADD', require('./CHANNEL_RECIPIENT_ADD')], + ['CHANNEL_RECIPIENT_REMOVE', require('./CHANNEL_RECIPIENT_REMOVE')], ['MESSAGE_ACK', require('./MESSAGE_ACK')], ['MESSAGE_CREATE', require('./MESSAGE_CREATE')], ['MESSAGE_DELETE', require('./MESSAGE_DELETE')], diff --git a/src/managers/RelationshipManager.js b/src/managers/RelationshipManager.js index adcb604..01ebab5 100644 --- a/src/managers/RelationshipManager.js +++ b/src/managers/RelationshipManager.js @@ -91,6 +91,7 @@ class RelationshipManager { _setup(users) { if (!Array.isArray(users)) return; for (const relationShip of users) { + this.client.user.friendNicknames.set(relationShip.id, relationShip.nickname); this.cache.set(relationShip.id, relationShip.type); } } @@ -211,6 +212,23 @@ class RelationshipManager { return true; } + /** + * Changes the nickname of a friend. + * @param {UserResolvable} user The user to change the nickname + * @param {?string} nickname New nickname + * @returns {Promise} + */ + async setNickname(user, nickname) { + const id = this.resolveId(user); + if (this.cache.get(id) !== RelationshipTypes.FRIEND) return false; + await this.client.api.users['@me'].relationships[id].patch({ + data: { + nickname: typeof nickname === 'string' ? nickname : null, + }, + }); + return true; + } + /** * Blocks a user. * @param {UserResolvable} user User to block diff --git a/src/structures/ClientApplication.js b/src/structures/ClientApplication.js index d4cb84b..0d63011 100644 --- a/src/structures/ClientApplication.js +++ b/src/structures/ClientApplication.js @@ -115,7 +115,7 @@ class ClientApplication extends Application { */ this.popularCommands = new Collection(); data.popular_application_command_ids.forEach(id => { - this.popularCommands.set(id, this.commands.cache.get(id)); + this.popularCommands.set(id, this.commands?.cache?.get(id)); }); } @@ -147,10 +147,11 @@ class ClientApplication extends Application { const app = await this.client.api.oauth2.authorize.get({ query: { client_id: this.id, - scope: 'bot', + scope: 'bot applications.commands', }, }); - this.client.users._add(app.bot); + const user = this.client.users._add(app.bot); + user._partial = false; this._patch(app.application); return this; } diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 7427dfc..5020821 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -77,6 +77,13 @@ class ClientUser extends User { if ('bio' in data) { this.bio = data.bio; } + + /** + * The friend nicknames cache of the client user. + * @type {Collection} + * @private + */ + this.friendNicknames = new Collection(); } /** diff --git a/src/structures/PartialGroupDMChannel.js b/src/structures/PartialGroupDMChannel.js index 59a63cf..42d666b 100644 --- a/src/structures/PartialGroupDMChannel.js +++ b/src/structures/PartialGroupDMChannel.js @@ -29,12 +29,6 @@ class PartialGroupDMChannel extends Channel { */ this.icon = null; - /** - * The recipients of this Group DM Channel. - * @type {Collection} - */ - this.recipients = null; - /** * Messages data * @type {Collection} @@ -68,6 +62,18 @@ class PartialGroupDMChannel extends Channel { this._patch(data); } + /** + * The recipients of this Group DM Channel. + * @type {Collection} + * @readonly + */ + get recipients() { + const collect = new Collection(); + this._recipients.map(recipient => collect.set(recipient.id, this.client.users._add(recipient))); + collect.set(this.client.user.id, this.client.user); + return collect; + } + /** * The owner of this Group DM Channel * @type {?User} @@ -85,8 +91,7 @@ class PartialGroupDMChannel extends Channel { _patch(data) { super._patch(data); if ('recipients' in data) { - this.recipients = new Collection(); - data.recipients.map(recipient => this.recipients.set(recipient.id, this.client.users._add(recipient))); + this._recipients = data.recipients; } if ('last_pin_timestamp' in data) { const date = new Date(data.last_pin_timestamp); diff --git a/src/structures/User.js b/src/structures/User.js index b1c1f1d..7e7e715 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -196,6 +196,15 @@ class User extends Base { return this.client.user.notes.get(this.id); } + /** + * Get friend nickname + * @type {?string} + * @readonly + */ + get nickname() { + return this.client.user.friendNicknames.get(this.id); + } + /** * The voice state of this member * @type {VoiceState} @@ -282,6 +291,16 @@ class User extends Base { return this.client.relationships.addFriend(this); } + /** + * Changes the nickname of the friend + * @param {?string} nickname The nickname to change + * @type {boolean} + * @returns {Promise} + */ + setNickname(nickname) { + return this.client.user.setNickname(this.id, nickname); + } + /** * Send Friend Request to the user * @type {boolean} diff --git a/src/util/Constants.js b/src/util/Constants.js index f686c4f..77a8166 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -358,6 +358,8 @@ exports.Opcodes = { * * CHANNEL_DELETE: channelDelete * * CHANNEL_UPDATE: channelUpdate * * CHANNEL_PINS_UPDATE: channelPinsUpdate + * * CHANNEL_RECIPIENT_REMOVE: channelRecipientRemove + * * CHANNEL_RECIPIENT_ADD: channelRecipientAdd * * MESSAGE_ACK: messageAck * * MESSAGE_CREATE: messageCreate * * MESSAGE_DELETE: messageDelete @@ -443,6 +445,8 @@ exports.Events = { CHANNEL_DELETE: 'channelDelete', CHANNEL_UPDATE: 'channelUpdate', CHANNEL_PINS_UPDATE: 'channelPinsUpdate', + CHANNEL_RECIPIENT_REMOVE: 'channelRecipientRemove', + CHANNEL_RECIPIENT_ADD: 'channelRecipientAdd', MESSAGE_ACK: 'messageAck', MESSAGE_CREATE: 'messageCreate', MESSAGE_DELETE: 'messageDelete', @@ -493,6 +497,7 @@ exports.Events = { GUILD_SCHEDULED_EVENT_USER_REMOVE: 'guildScheduledEventUserRemove', RELATIONSHIP_ADD: 'relationshipAdd', RELATIONSHIP_REMOVE: 'relationshipRemove', + RELATIONSHIP_UPDATE: 'relationshipUpdate', UNHANDLED_PACKET: 'unhandledPacket', }; diff --git a/typings/index.d.ts b/typings/index.d.ts index d9d8090..7129673 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -924,6 +924,8 @@ export class ClientUser extends User { public mfaEnabled: boolean; public readonly presence: ClientPresence; public verified: boolean; + public notes: Collection; + public friendNicknames: Collection; public edit(data: ClientUserEditData): Promise; public setActivity(options?: ActivityOptions): ClientPresence; public setActivity(name: string, options?: ActivityOptions): ClientPresence; @@ -2347,7 +2349,7 @@ export class PartialGroupDMChannel extends TextBasedChannelMixin(Channel, [ private constructor(client: Client, data: RawPartialGroupDMChannelData); public name: string | null; public icon: string | null; - public recipients: Collection; + public readonly recipients: Collection; public messages: MessageManager; public invites: Collection; public lastMessageId: Snowflake | null; @@ -2964,6 +2966,7 @@ export class User extends PartialTextBasedChannel(Base) { public readonly tag: string; public username: string; public readonly note: string | null; + public readonly nickname: string | null; public readonly connectedAccounts: object[]; public readonly premiumSince: Date; public readonly premiumGuildSince: Date; @@ -2985,6 +2988,7 @@ export class User extends PartialTextBasedChannel(Base) { public unBlock(): Promise; public setNote(note?: any): Promise; public getProfile(guildId?: Snowflake): Promise; + public setNickname(nickname: string | null): Promise; public toString(): UserMention; public ring(): Promise; } @@ -4014,6 +4018,7 @@ export class RelationshipManager { public cancelFriendRequest(user: UserResolvable): Promise; public addFriend(user: UserResolvable): Promise; public addBlocked(user: UserResolvable): Promise; + public setNickname(user: UserResolvable, nickname: string|null): Promise; private __cancel(id: Snowflake): Promise; } @@ -4332,6 +4337,8 @@ export interface ClientEvents extends BaseClientEvents { channelCreate: [channel: NonThreadGuildBasedChannel]; channelDelete: [channel: DMChannel | NonThreadGuildBasedChannel]; channelPinsUpdate: [channel: TextBasedChannel, date: Date]; + channelRecipientAdd: [channel: PartialGroupDMChannel, user: User]; + channelRecipientRemove: [channel: PartialGroupDMChannel, user: User]; channelUpdate: [ oldChannel: DMChannel | NonThreadGuildBasedChannel, newChannel: DMChannel | NonThreadGuildBasedChannel, @@ -4425,6 +4432,7 @@ export interface ClientEvents extends BaseClientEvents { guildScheduledEventUserRemove: [guildScheduledEvent: GuildScheduledEvent, user: User]; relationshipAdd: [id: Snowflake, type: RelationshipTypes]; relationshipRemove: [id: Snowflake]; + relationshipUpdate: [id: Snowflake, type: RelationshipTypes, data: object]; unhandledPacket: [packet: { op: GatewayOpcodes | number; d?: any; s?: number; t?: string }, shard: WebSocketShard]; update: [oldVersion: string, newVersion: string]; } @@ -4470,6 +4478,8 @@ export interface ConstantsEvents { CHANNEL_DELETE: 'channelDelete'; CHANNEL_UPDATE: 'channelUpdate'; CHANNEL_PINS_UPDATE: 'channelPinsUpdate'; + CHANNEL_RECIPIENT_ADD: 'channelRecipientAdd'; + CHANNEL_RECIPIENT_REMOVE: 'channelRecipientRemove'; MESSAGE_ACK: 'messageAck'; MESSAGE_CREATE: 'messageCreate'; MESSAGE_DELETE: 'messageDelete'; @@ -4520,6 +4530,7 @@ export interface ConstantsEvents { GUILD_SCHEDULED_EVENT_USER_REMOVE: 'guildScheduledEventUserRemove'; RELATIONSHIP_ADD: 'relationshipAdd'; RELATIONSHIP_REMOVE: 'relationshipRemove'; + RELATIONSHIP_UPDATE: 'relationshipUpdate'; UNHANDLED_PACKET: 'unhandledPacket'; }