From a59968f7de048118e307faa8e7a5990813b6f338 Mon Sep 17 00:00:00 2001 From: Vlad Frangu Date: Fri, 3 May 2019 18:08:07 +0300 Subject: [PATCH] src: add news and store channels, and missing guild props (#3168) * src: Implement store and news channels! * src: Remove code dupe * src: Add missing guild properties * docs: Add a small notice that the channel type may also change * src: Remove re-creation of the MessageStore * lint: Unused Import * src: Requested changes for StoreChannels * typings: Fix typings * src: Moar guild updates * src: Set maximumPresence to the data prop, the already existent one, or default to 5000 * typings: afkChannel is a VC I keep confusing them, ffs Co-Authored-By: vladfrangu * docs: Document that maximumMembers and maximumPresences may be inaccurate before fetching * src Appels requested changes --- src/client/actions/ChannelUpdate.js | 13 ++- .../websocket/handlers/CHANNEL_UPDATE.js | 2 +- src/structures/Channel.js | 10 ++ src/structures/Guild.js | 97 ++++++++++++++++++- src/structures/NewsChannel.js | 18 ++++ src/structures/StoreChannel.js | 22 +++++ src/structures/TextChannel.js | 1 - src/util/Constants.js | 4 + src/util/Structures.js | 2 + typings/index.d.ts | 34 ++++++- 10 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 src/structures/NewsChannel.js create mode 100644 src/structures/StoreChannel.js diff --git a/src/client/actions/ChannelUpdate.js b/src/client/actions/ChannelUpdate.js index b610ea7ca..7b716de07 100644 --- a/src/client/actions/ChannelUpdate.js +++ b/src/client/actions/ChannelUpdate.js @@ -1,14 +1,25 @@ 'use strict'; const Action = require('./Action'); +const Channel = require('../../structures/Channel'); +const { ChannelTypes } = require('../../util/Constants'); class ChannelUpdateAction extends Action { handle(data) { const client = this.client; - const channel = client.channels.get(data.id); + let channel = client.channels.get(data.id); if (channel) { const old = channel._update(data); + + if (ChannelTypes[channel.type.toUpperCase()] !== data.type) { + const newChannel = Channel.create(this.client, data, channel.guild); + for (const [id, message] of channel.messages) newChannel.messages.set(id, message); + newChannel._typing = new Map(channel._typing); + channel = newChannel; + this.client.channels.set(channel.id, channel); + } + return { old, updated: channel, diff --git a/src/client/websocket/handlers/CHANNEL_UPDATE.js b/src/client/websocket/handlers/CHANNEL_UPDATE.js index 7a0df486a..01f5beccf 100644 --- a/src/client/websocket/handlers/CHANNEL_UPDATE.js +++ b/src/client/websocket/handlers/CHANNEL_UPDATE.js @@ -6,7 +6,7 @@ module.exports = (client, packet) => { const { old, updated } = client.actions.ChannelUpdate.handle(packet.d); if (old && updated) { /** - * Emitted whenever a channel is updated - e.g. name change, topic change. + * Emitted whenever a channel is updated - e.g. name change, topic change, channel type change. * @event Client#channelUpdate * @param {DMChannel|GuildChannel} oldChannel The channel before the update * @param {DMChannel|GuildChannel} newChannel The channel after the update diff --git a/src/structures/Channel.js b/src/structures/Channel.js index de5f462e8..ab6adb81a 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -116,6 +116,16 @@ class Channel extends Base { channel = new CategoryChannel(guild, data); break; } + case ChannelTypes.NEWS: { + const NewsChannel = Structures.get('NewsChannel'); + channel = new NewsChannel(guild, data); + break; + } + case ChannelTypes.STORE: { + const StoreChannel = Structures.get('StoreChannel'); + channel = new StoreChannel(guild, data); + break; + } } if (channel) guild.channels.set(channel.id, channel); } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index df18ea347..a5d13fa65 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -186,6 +186,27 @@ class Guild extends Base { */ this.embedEnabled = data.embed_enabled; + /** + * Whether widget images are enabled on this guild + * @type {?boolean} + * @name Guild#widgetEnabled + */ + if (typeof data.widget_enabled !== 'undefined') this.widgetEnabled = data.widget_enabled; + + /** + * The widget channel ID, if enabled + * @type {?string} + * @name Guild#widgetChannelID + */ + if (typeof data.widget_channel_id !== 'undefined') this.widgetChannelID = data.widget_channel_id; + + /** + * The embed channel ID, if enabled + * @type {?string} + * @name Guild#embedChannelID + */ + if (typeof data.embed_channel_id !== 'undefined') this.embedChannelID = data.embed_channel_id; + /** * The verification level of the guild * @type {number} @@ -211,12 +232,46 @@ class Guild extends Base { this.joinedTimestamp = data.joined_at ? new Date(data.joined_at).getTime() : this.joinedTimestamp; /** - * The value set for a guild's default message notifications + * The value set for the guild's default message notifications * @type {DefaultMessageNotifications|number} */ this.defaultMessageNotifications = DefaultMessageNotifications[data.default_message_notifications] || data.default_message_notifications; + /** + * The maximum amount of members the guild can have + * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter + * @type {?number} + * @name Guild#maximumMembers + */ + if (typeof data.max_members !== 'undefined') this.maximumMembers = data.max_members || 250000; + + /** + * The maximum amount of presences the guild can have + * You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter + * @type {?number} + * @name Guild#maximumPresences + */ + if (typeof data.max_presences !== 'undefined') this.maximumPresences = data.max_presences || 5000; + + /** + * The vanity URL code of the guild, if any + * @type {?string} + */ + this.vanityURLCode = data.vanity_url_code; + + /** + * The description of the guild, if any + * @type {?string} + */ + this.description = data.description; + + /** + * The hash of the guild banner + * @type {?string} + */ + this.banner = data.banner; + this.id = data.id; this.available = !data.unavailable; this.features = data.features || this.features || []; @@ -274,6 +329,16 @@ class Guild extends Base { } } + /** + * The URL to this guild's banner. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + bannerURL({ format, size } = {}) { + if (!this.banner) return null; + return this.client.rest.cdn.Banner(this.id, this.banner, format, size); + } + /** * The timestamp the guild was created at * @type {number} @@ -368,6 +433,24 @@ class Guild extends Base { return this.client.channels.get(this.systemChannelID) || null; } + /** + * Widget channel for this guild + * @type {?TextChannel} + * @readonly + */ + get widgetChannel() { + return this.client.channels.get(this.widgetChannelID) || null; + } + + /** + * Embed channel for this guild + * @type {?TextChannel} + * @readonly + */ + get embedChannel() { + return this.client.channels.get(this.embedChannelID) || null; + } + /** * The `@everyone` role of the guild * @type {?Role} @@ -409,6 +492,17 @@ class Guild extends Base { return this.members.resolve(user); } + /** + * Fetches this guild. + * @returns {Promise} + */ + fetch() { + return this.client.api.guilds(this.id).get().then(data => { + this._patch(data); + return this; + }); + } + /** * An object containing information about a guild member's ban. * @typedef {Object} BanInfo @@ -975,6 +1069,7 @@ class Guild extends Base { }); json.iconURL = this.iconURL(); json.splashURL = this.splashURL(); + json.bannerURL = this.bannerURL(); return json; } diff --git a/src/structures/NewsChannel.js b/src/structures/NewsChannel.js new file mode 100644 index 000000000..76727fcdd --- /dev/null +++ b/src/structures/NewsChannel.js @@ -0,0 +1,18 @@ +'use strict'; + +const TextChannel = require('./TextChannel'); + +/** + * Represents a guild news channel on Discord. + * @extends {TextChannel} + */ +class NewsChannel extends TextChannel { + _patch(data) { + super._patch(data); + + // News channels don't have a rate limit per user, remove it + this.rateLimitPerUser = undefined; + } +} + +module.exports = NewsChannel; diff --git a/src/structures/StoreChannel.js b/src/structures/StoreChannel.js new file mode 100644 index 000000000..87cb04064 --- /dev/null +++ b/src/structures/StoreChannel.js @@ -0,0 +1,22 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); + +/** + * Represents a guild store channel on Discord. + * @extends {GuildChannel} + */ +class StoreChannel extends GuildChannel { + _patch(data) { + super._patch(data); + + /** + * If the guild considers this channel NSFW + * @type {boolean} + * @readonly + */ + this.nsfw = data.nsfw; + } +} + +module.exports = StoreChannel; diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index e50bee05a..836c55a08 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -139,7 +139,6 @@ class TextChannel extends GuildChannel { awaitMessages() {} bulkDelete() {} acknowledge() {} - _cacheMessage() {} } TextBasedChannel.applyToClass(TextChannel, true); diff --git a/src/util/Constants.js b/src/util/Constants.js index f314d3d6f..0d8f3b6e3 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -131,6 +131,8 @@ exports.Endpoints = { if (format === 'default') format = hash.startsWith('a_') ? 'gif' : 'webp'; return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size }); }, + Banner: (guildID, hash, format = 'webp', size) => + makeImageUrl(`${root}/banners/${guildID}/${hash}`, { format, size }), Icon: (guildID, hash, format = 'webp', size) => makeImageUrl(`${root}/icons/${guildID}/${hash}`, { format, size }), AppIcon: (clientID, hash, { format = 'webp', size } = {}) => @@ -409,6 +411,8 @@ exports.ChannelTypes = { VOICE: 2, GROUP: 3, CATEGORY: 4, + NEWS: 5, + STORE: 6, }; exports.ClientApplicationAssetTypes = { diff --git a/src/util/Structures.js b/src/util/Structures.js index 2dc26770f..02529d26d 100644 --- a/src/util/Structures.js +++ b/src/util/Structures.js @@ -75,6 +75,8 @@ const structures = { TextChannel: require('../structures/TextChannel'), VoiceChannel: require('../structures/VoiceChannel'), CategoryChannel: require('../structures/CategoryChannel'), + NewsChannel: require('../structures/NewsChannel'), + StoreChannel: require('../structures/StoreChannel'), GuildMember: require('../structures/GuildMember'), Guild: require('../structures/Guild'), Message: require('../structures/Message'), diff --git a/typings/index.d.ts b/typings/index.d.ts index 1186be6ed..ed0430dde 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -122,7 +122,7 @@ declare module 'discord.js' { public readonly createdTimestamp: number; public deleted: boolean; public id: Snowflake; - public type: 'dm' | 'text' | 'voice' | 'category' | 'unknown'; + public type: 'dm' | 'text' | 'voice' | 'category' | 'news' | 'store' | 'unknown'; public delete(reason?: string): Promise; public fetch(): Promise; public toString(): string; @@ -393,9 +393,7 @@ declare module 'discord.js' { private _sortedChannels(channel: Channel): Collection; private _memberSpeakUpdate(user: Snowflake, speaking: boolean): void; - protected setup(data: any): void; - - public readonly afkChannel: VoiceChannel | null; + public readonly afkChannel: VoiceChannel; public afkChannelID: Snowflake; public afkTimeout: number; public applicationID: Snowflake; @@ -432,13 +430,25 @@ declare module 'discord.js' { public readonly systemChannel: TextChannel | null; public systemChannelID: Snowflake; public verificationLevel: number; + public maximumMembers: number; + public maximumPresences: number; + public vanityURLCode: string; + public description: string; + public banner: string; + public widgetEnabled: boolean; + public widgetChannelID: Snowflake; + public readonly widgetChannel: TextChannel; + public embedChannelID: Snowflake; + public readonly embedChannel: TextChannel; public readonly verified: boolean; public readonly voiceConnection: VoiceConnection | null; public addMember(user: UserResolvable, options: AddGuildMemberOptions): Promise; + public bannerURL(options?: AvatarOptions): string; public createIntegration(data: IntegrationData, reason?: string): Promise; public delete(): Promise; public edit(data: GuildEditData, reason?: string): Promise; public equals(guild: Guild): boolean; + public fetch(): Promise; public fetchAuditLogs(options?: GuildAuditLogsFetchOptions): Promise; public fetchBans(): Promise>; public fetchIntegrations(): Promise>; @@ -532,6 +542,11 @@ declare module 'discord.js' { public updateOverwrite(userOrRole: RoleResolvable | UserResolvable, options: PermissionOverwriteOption, reason?: string): Promise; } + export class StoreChannel extends GuildChannel { + constructor(guild: Guild, data?: object); + public nsfw: boolean; + } + export class GuildEmoji extends Emoji { constructor(client: Client, data: object, guild: Guild); private _roles: string[]; @@ -1064,6 +1079,17 @@ declare module 'discord.js' { public fetchWebhooks(): Promise>; } + export class NewsChannel extends TextBasedChannel(GuildChannel) { + constructor(guild: Guild, data?: object); + public readonly members: Collection; + public messages: MessageStore; + public nsfw: boolean; + public topic: string; + public createWebhook(name: string, options?: { avatar?: BufferResolvable | Base64Resolvable, reason?: string }): Promise; + public setNSFW(nsfw: boolean, reason?: string): Promise; + public fetchWebhooks(): Promise>; + } + export class User extends PartialTextBasedChannel(Base) { constructor(client: Client, data: object); public avatar: string | null;