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;