Skip to content

Commit

Permalink
feat: add support for guild templates (#4907)
Browse files Browse the repository at this point in the history
Co-authored-by: Noel <buechler.noel@outlook.com>
  • Loading branch information
izexi and iCrawl committed Nov 21, 2020
1 parent eaecd0e commit 2b2994b
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 4 deletions.
1 change: 1 addition & 0 deletions esm/discord.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const {
GuildEmoji,
GuildMember,
GuildPreview,
GuildTemplate,
Integration,
Invite,
Message,
Expand Down
18 changes: 18 additions & 0 deletions src/client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const UserManager = require('../managers/UserManager');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const ClientApplication = require('../structures/ClientApplication');
const GuildPreview = require('../structures/GuildPreview');
const GuildTemplate = require('../structures/GuildTemplate');
const Invite = require('../structures/Invite');
const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
Expand Down Expand Up @@ -254,6 +255,23 @@ class Client extends BaseClient {
.then(data => new Invite(this, data));
}

/**
* Obtains a template from Discord.
* @param {GuildTemplateResolvable} template Template code or URL
* @returns {Promise<GuildTemplate>}
* @example
* client.fetchGuildTemplate('https://discord.new/FKvmczH2HyUf')
* .then(template => console.log(`Obtained template with code: ${template.code}`))
* .catch(console.error);
*/
fetchGuildTemplate(template) {
const code = DataResolver.resolveGuildTemplateCode(template);
return this.api.guilds
.templates(code)
.get()
.then(data => new GuildTemplate(this, data));
}

/**
* Obtains a webhook from Discord.
* @param {Snowflake} id ID of the webhook
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module.exports = {
GuildEmoji: require('./structures/GuildEmoji'),
GuildMember: require('./structures/GuildMember'),
GuildPreview: require('./structures/GuildPreview'),
GuildTemplate: require('./structures/GuildTemplate'),
Integration: require('./structures/Integration'),
Invite: require('./structures/Invite'),
Message: require('./structures/Message'),
Expand Down
28 changes: 28 additions & 0 deletions src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { deprecate } = require('util');
const Base = require('./Base');
const GuildAuditLogs = require('./GuildAuditLogs');
const GuildPreview = require('./GuildPreview');
const GuildTemplate = require('./GuildTemplate');
const Integration = require('./Integration');
const Invite = require('./Invite');
const VoiceRegion = require('./VoiceRegion');
Expand Down Expand Up @@ -733,6 +734,20 @@ class Guild extends Base {
);
}

/**
* Fetches a collection of templates from this guild.
* Resolves with a collection mapping templates by their codes.
* @returns {Promise<Collection<string, GuildTemplate>>}
*/
fetchTemplates() {
return this.client.api
.guilds(this.id)
.templates.get()
.then(templates =>
templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection()),
);
}

/**
* The data for creating an integration.
* @typedef {Object} IntegrationData
Expand All @@ -753,6 +768,19 @@ class Guild extends Base {
.then(() => this);
}

/**
* Creates a template for the guild.
* @param {string} name The name for the template
* @param {string} [description] The description for the template
* @returns {Promise<GuildTemplate>}
*/
createTemplate(name, description) {
return this.client.api
.guilds(this.id)
.templates.post({ data: { name, description } })
.then(data => new GuildTemplate(this.client, data));
}

/**
* Fetches a collection of invites to this guild.
* Resolves with a collection mapping invites by their codes.
Expand Down
224 changes: 224 additions & 0 deletions src/structures/GuildTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
'use strict';

const Base = require('./Base');
const { Events } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');

/**
* Represents the template for a guild.
* @extends {Base}
*/
class GuildTemplate extends Base {
/**
* @param {Client} client The instantiating client
* @param {Object} data The raw data for the template
*/
constructor(client, data) {
super(client);
this._patch(data);
}

/**
* Builds or updates the template with the provided data.
* @param {Object} data The raw data for the template
* @returns {GuildTemplate}
* @private
*/
_patch(data) {
/**
* The unique code of this template
* @type {string}
*/
this.code = data.code;

/**
* The name of this template
* @type {string}
*/
this.name = data.name;

/**
* The description of this template
* @type {?string}
*/
this.description = data.description;

/**
* The amount of times this template has been used
* @type {number}
*/
this.usageCount = data.usage_count;

/**
* The ID of the user that created this template
* @type {Snowflake}
*/
this.creatorID = data.creator_id;

/**
* The user that created this template
* @type {User}
*/
this.creator = this.client.users.add(data.creator);

/**
* The time of when this template was created at
* @type {Date}
*/
this.createdAt = new Date(data.created_at);

/**
* The time of when this template was last synced to the guild
* @type {Date}
*/
this.updatedAt = new Date(data.updated_at);

/**
* The ID of the guild that this template belongs to
* @type {Snowflake}
*/
this.guildID = data.source_guild_id;

/**
* The data of the guild that this template would create
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/guild#guild-resource}
*/
this.serializedGuild = data.serialized_source_guild;

/**
* Whether this template has unsynced changes
* @type {?boolean}
*/
this.unSynced = 'is_dirty' in data ? Boolean(data.is_dirty) : null;

return this;
}

/**
* Creates a guild based from this template.
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
* @param {string} name The name of the guild
* @param {BufferResolvable|Base64Resolvable} [icon] The icon for the guild
* @returns {Promise<Guild>}
*/
async createGuild(name, icon) {
const { client } = this;
const data = await client.api.guilds.templates(this.code).post({
data: {
name,
icon: await DataResolver.resolveImage(icon),
},
});
// eslint-disable-next-line consistent-return
return new Promise(resolve => {
const createdGuild = client.guilds.cache.get(data.id);
if (createdGuild) return resolve(createdGuild);

const resolveGuild = guild => {
client.off(Events.GUILD_CREATE, handleGuild);
client.decrementMaxListeners();
resolve(guild);
};

const handleGuild = guild => {
if (guild.id === data.id) {
client.clearTimeout(timeout);
resolveGuild(guild);
}
};

client.incrementMaxListeners();
client.on(Events.GUILD_CREATE, handleGuild);

const timeout = client.setTimeout(() => resolveGuild(client.guilds.add(data)), 10000);
});
}

/**
* Updates the metadata on this template.
* @param {Object} options Options for the template
* @param {string} [options.name] The name of this template
* @param {string} [options.description] The description of this template
* @returns {Promise<GuildTemplate>}
*/
edit({ name, description } = {}) {
return this.client.api
.guilds(this.guildID)
.templates(this.code)
.patch({ data: { name, description } })
.then(data => this._patch(data));
}

/**
* Deletes this template.
* @returns {Promise<GuildTemplate>}
*/
delete() {
return this.client.api
.guilds(this.guildID)
.templates(this.code)
.delete()
.then(() => this);
}

/**
* Syncs this template to the current state of the guild.
* @returns {Promise<GuildTemplate>}
*/
sync() {
return this.client.api
.guilds(this.guildID)
.templates(this.code)
.put()
.then(data => this._patch(data));
}

/**
* The timestamp of when this template was created at
* @type {number}
* @readonly
*/
get createdTimestamp() {
return this.createdAt.getTime();
}

/**
* The timestamp of when this template was last synced to the guild
* @type {number}
* @readonly
*/
get updatedTimestamp() {
return this.updatedAt.getTime();
}

/**
* The guild that this template belongs to
* @type {?Guild}
* @readonly
*/
get guild() {
return this.client.guilds.get(this.guildID) || null;
}
/**
* The URL of this template
* @type {string}
* @readonly
*/
get url() {
return `${this.client.options.http.template}/${this.code}`;
}

/**
* When concatenated with a string, this automatically returns the templates's code instead of the template object.
* @returns {string}
* @example
* // Logs: Template: FKvmczH2HyUf
* console.log(`Template: ${guildTemplate}!`);
*/
toString() {
return this.code;
}
}

module.exports = GuildTemplate;
6 changes: 6 additions & 0 deletions src/util/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,14 @@ exports.DefaultOptions = {
* @property {string} [api='https://discord.com/api'] Base url of the API
* @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN
* @property {string} [invite='https://discord.gg'] Base url of invites
* @property {string} [template='https://discord.new'] Base url of templates
*/
http: {
version: 7,
api: 'https://discord.com/api',
cdn: 'https://cdn.discordapp.com',
invite: 'https://discord.gg',
template: 'https://discord.new',
},
};

Expand Down Expand Up @@ -520,6 +522,7 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
* * UNKNOWN_EMOJI
* * UNKNOWN_WEBHOOK
* * UNKNOWN_BAN
* * UNKNOWN_GUILD_TEMPLATE
* * BOT_PROHIBITED_ENDPOINT
* * BOT_ONLY_ENDPOINT
* * CHANNEL_HIT_WRITE_RATELIMIT
Expand All @@ -532,6 +535,7 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
* * MAXIMUM_CHANNELS
* * MAXIMUM_ATTACHMENTS
* * MAXIMUM_INVITES
* * GUILD_ALREADY_HAS_TEMPLATE
* * UNAUTHORIZED
* * ACCOUNT_VERIFICATION_REQUIRED
* * REQUEST_ENTITY_TOO_LARGE
Expand Down Expand Up @@ -584,6 +588,7 @@ exports.APIErrors = {
UNKNOWN_EMOJI: 10014,
UNKNOWN_WEBHOOK: 10015,
UNKNOWN_BAN: 10026,
UNKNOWN_GUILD_TEMPLATE: 10057,
BOT_PROHIBITED_ENDPOINT: 20001,
BOT_ONLY_ENDPOINT: 20002,
CHANNEL_HIT_WRITE_RATELIMIT: 20028,
Expand All @@ -596,6 +601,7 @@ exports.APIErrors = {
MAXIMUM_CHANNELS: 30013,
MAXIMUM_ATTACHMENTS: 30015,
MAXIMUM_INVITES: 30016,
GUILD_ALREADY_HAS_TEMPLATE: 30031,
UNAUTHORIZED: 40001,
ACCOUNT_VERIFICATION_REQUIRED: 40002,
REQUEST_ENTITY_TOO_LARGE: 40005,
Expand Down
Loading

0 comments on commit 2b2994b

Please sign in to comment.