Skip to content

Commit 2b2994b

Browse files
izexiiCrawl
andauthored
feat: add support for guild templates (#4907)
Co-authored-by: Noel <buechler.noel@outlook.com>
1 parent eaecd0e commit 2b2994b

File tree

8 files changed

+339
-4
lines changed

8 files changed

+339
-4
lines changed

esm/discord.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const {
6565
GuildEmoji,
6666
GuildMember,
6767
GuildPreview,
68+
GuildTemplate,
6869
Integration,
6970
Invite,
7071
Message,

src/client/Client.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const UserManager = require('../managers/UserManager');
1212
const ShardClientUtil = require('../sharding/ShardClientUtil');
1313
const ClientApplication = require('../structures/ClientApplication');
1414
const GuildPreview = require('../structures/GuildPreview');
15+
const GuildTemplate = require('../structures/GuildTemplate');
1516
const Invite = require('../structures/Invite');
1617
const VoiceRegion = require('../structures/VoiceRegion');
1718
const Webhook = require('../structures/Webhook');
@@ -254,6 +255,23 @@ class Client extends BaseClient {
254255
.then(data => new Invite(this, data));
255256
}
256257

258+
/**
259+
* Obtains a template from Discord.
260+
* @param {GuildTemplateResolvable} template Template code or URL
261+
* @returns {Promise<GuildTemplate>}
262+
* @example
263+
* client.fetchGuildTemplate('https://discord.new/FKvmczH2HyUf')
264+
* .then(template => console.log(`Obtained template with code: ${template.code}`))
265+
* .catch(console.error);
266+
*/
267+
fetchGuildTemplate(template) {
268+
const code = DataResolver.resolveGuildTemplateCode(template);
269+
return this.api.guilds
270+
.templates(code)
271+
.get()
272+
.then(data => new GuildTemplate(this, data));
273+
}
274+
257275
/**
258276
* Obtains a webhook from Discord.
259277
* @param {Snowflake} id ID of the webhook

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ module.exports = {
7777
GuildEmoji: require('./structures/GuildEmoji'),
7878
GuildMember: require('./structures/GuildMember'),
7979
GuildPreview: require('./structures/GuildPreview'),
80+
GuildTemplate: require('./structures/GuildTemplate'),
8081
Integration: require('./structures/Integration'),
8182
Invite: require('./structures/Invite'),
8283
Message: require('./structures/Message'),

src/structures/Guild.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { deprecate } = require('util');
44
const Base = require('./Base');
55
const GuildAuditLogs = require('./GuildAuditLogs');
66
const GuildPreview = require('./GuildPreview');
7+
const GuildTemplate = require('./GuildTemplate');
78
const Integration = require('./Integration');
89
const Invite = require('./Invite');
910
const VoiceRegion = require('./VoiceRegion');
@@ -733,6 +734,20 @@ class Guild extends Base {
733734
);
734735
}
735736

737+
/**
738+
* Fetches a collection of templates from this guild.
739+
* Resolves with a collection mapping templates by their codes.
740+
* @returns {Promise<Collection<string, GuildTemplate>>}
741+
*/
742+
fetchTemplates() {
743+
return this.client.api
744+
.guilds(this.id)
745+
.templates.get()
746+
.then(templates =>
747+
templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection()),
748+
);
749+
}
750+
736751
/**
737752
* The data for creating an integration.
738753
* @typedef {Object} IntegrationData
@@ -753,6 +768,19 @@ class Guild extends Base {
753768
.then(() => this);
754769
}
755770

771+
/**
772+
* Creates a template for the guild.
773+
* @param {string} name The name for the template
774+
* @param {string} [description] The description for the template
775+
* @returns {Promise<GuildTemplate>}
776+
*/
777+
createTemplate(name, description) {
778+
return this.client.api
779+
.guilds(this.id)
780+
.templates.post({ data: { name, description } })
781+
.then(data => new GuildTemplate(this.client, data));
782+
}
783+
756784
/**
757785
* Fetches a collection of invites to this guild.
758786
* Resolves with a collection mapping invites by their codes.

src/structures/GuildTemplate.js

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
'use strict';
2+
3+
const Base = require('./Base');
4+
const { Events } = require('../util/Constants');
5+
const DataResolver = require('../util/DataResolver');
6+
7+
/**
8+
* Represents the template for a guild.
9+
* @extends {Base}
10+
*/
11+
class GuildTemplate extends Base {
12+
/**
13+
* @param {Client} client The instantiating client
14+
* @param {Object} data The raw data for the template
15+
*/
16+
constructor(client, data) {
17+
super(client);
18+
this._patch(data);
19+
}
20+
21+
/**
22+
* Builds or updates the template with the provided data.
23+
* @param {Object} data The raw data for the template
24+
* @returns {GuildTemplate}
25+
* @private
26+
*/
27+
_patch(data) {
28+
/**
29+
* The unique code of this template
30+
* @type {string}
31+
*/
32+
this.code = data.code;
33+
34+
/**
35+
* The name of this template
36+
* @type {string}
37+
*/
38+
this.name = data.name;
39+
40+
/**
41+
* The description of this template
42+
* @type {?string}
43+
*/
44+
this.description = data.description;
45+
46+
/**
47+
* The amount of times this template has been used
48+
* @type {number}
49+
*/
50+
this.usageCount = data.usage_count;
51+
52+
/**
53+
* The ID of the user that created this template
54+
* @type {Snowflake}
55+
*/
56+
this.creatorID = data.creator_id;
57+
58+
/**
59+
* The user that created this template
60+
* @type {User}
61+
*/
62+
this.creator = this.client.users.add(data.creator);
63+
64+
/**
65+
* The time of when this template was created at
66+
* @type {Date}
67+
*/
68+
this.createdAt = new Date(data.created_at);
69+
70+
/**
71+
* The time of when this template was last synced to the guild
72+
* @type {Date}
73+
*/
74+
this.updatedAt = new Date(data.updated_at);
75+
76+
/**
77+
* The ID of the guild that this template belongs to
78+
* @type {Snowflake}
79+
*/
80+
this.guildID = data.source_guild_id;
81+
82+
/**
83+
* The data of the guild that this template would create
84+
* @type {Object}
85+
* @see {@link https://discord.com/developers/docs/resources/guild#guild-resource}
86+
*/
87+
this.serializedGuild = data.serialized_source_guild;
88+
89+
/**
90+
* Whether this template has unsynced changes
91+
* @type {?boolean}
92+
*/
93+
this.unSynced = 'is_dirty' in data ? Boolean(data.is_dirty) : null;
94+
95+
return this;
96+
}
97+
98+
/**
99+
* Creates a guild based from this template.
100+
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
101+
* @param {string} name The name of the guild
102+
* @param {BufferResolvable|Base64Resolvable} [icon] The icon for the guild
103+
* @returns {Promise<Guild>}
104+
*/
105+
async createGuild(name, icon) {
106+
const { client } = this;
107+
const data = await client.api.guilds.templates(this.code).post({
108+
data: {
109+
name,
110+
icon: await DataResolver.resolveImage(icon),
111+
},
112+
});
113+
// eslint-disable-next-line consistent-return
114+
return new Promise(resolve => {
115+
const createdGuild = client.guilds.cache.get(data.id);
116+
if (createdGuild) return resolve(createdGuild);
117+
118+
const resolveGuild = guild => {
119+
client.off(Events.GUILD_CREATE, handleGuild);
120+
client.decrementMaxListeners();
121+
resolve(guild);
122+
};
123+
124+
const handleGuild = guild => {
125+
if (guild.id === data.id) {
126+
client.clearTimeout(timeout);
127+
resolveGuild(guild);
128+
}
129+
};
130+
131+
client.incrementMaxListeners();
132+
client.on(Events.GUILD_CREATE, handleGuild);
133+
134+
const timeout = client.setTimeout(() => resolveGuild(client.guilds.add(data)), 10000);
135+
});
136+
}
137+
138+
/**
139+
* Updates the metadata on this template.
140+
* @param {Object} options Options for the template
141+
* @param {string} [options.name] The name of this template
142+
* @param {string} [options.description] The description of this template
143+
* @returns {Promise<GuildTemplate>}
144+
*/
145+
edit({ name, description } = {}) {
146+
return this.client.api
147+
.guilds(this.guildID)
148+
.templates(this.code)
149+
.patch({ data: { name, description } })
150+
.then(data => this._patch(data));
151+
}
152+
153+
/**
154+
* Deletes this template.
155+
* @returns {Promise<GuildTemplate>}
156+
*/
157+
delete() {
158+
return this.client.api
159+
.guilds(this.guildID)
160+
.templates(this.code)
161+
.delete()
162+
.then(() => this);
163+
}
164+
165+
/**
166+
* Syncs this template to the current state of the guild.
167+
* @returns {Promise<GuildTemplate>}
168+
*/
169+
sync() {
170+
return this.client.api
171+
.guilds(this.guildID)
172+
.templates(this.code)
173+
.put()
174+
.then(data => this._patch(data));
175+
}
176+
177+
/**
178+
* The timestamp of when this template was created at
179+
* @type {number}
180+
* @readonly
181+
*/
182+
get createdTimestamp() {
183+
return this.createdAt.getTime();
184+
}
185+
186+
/**
187+
* The timestamp of when this template was last synced to the guild
188+
* @type {number}
189+
* @readonly
190+
*/
191+
get updatedTimestamp() {
192+
return this.updatedAt.getTime();
193+
}
194+
195+
/**
196+
* The guild that this template belongs to
197+
* @type {?Guild}
198+
* @readonly
199+
*/
200+
get guild() {
201+
return this.client.guilds.get(this.guildID) || null;
202+
}
203+
/**
204+
* The URL of this template
205+
* @type {string}
206+
* @readonly
207+
*/
208+
get url() {
209+
return `${this.client.options.http.template}/${this.code}`;
210+
}
211+
212+
/**
213+
* When concatenated with a string, this automatically returns the templates's code instead of the template object.
214+
* @returns {string}
215+
* @example
216+
* // Logs: Template: FKvmczH2HyUf
217+
* console.log(`Template: ${guildTemplate}!`);
218+
*/
219+
toString() {
220+
return this.code;
221+
}
222+
}
223+
224+
module.exports = GuildTemplate;

src/util/Constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@ exports.DefaultOptions = {
8181
* @property {string} [api='https://discord.com/api'] Base url of the API
8282
* @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN
8383
* @property {string} [invite='https://discord.gg'] Base url of invites
84+
* @property {string} [template='https://discord.new'] Base url of templates
8485
*/
8586
http: {
8687
version: 7,
8788
api: 'https://discord.com/api',
8889
cdn: 'https://cdn.discordapp.com',
8990
invite: 'https://discord.gg',
91+
template: 'https://discord.new',
9092
},
9193
};
9294

@@ -520,6 +522,7 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
520522
* * UNKNOWN_EMOJI
521523
* * UNKNOWN_WEBHOOK
522524
* * UNKNOWN_BAN
525+
* * UNKNOWN_GUILD_TEMPLATE
523526
* * BOT_PROHIBITED_ENDPOINT
524527
* * BOT_ONLY_ENDPOINT
525528
* * CHANNEL_HIT_WRITE_RATELIMIT
@@ -532,6 +535,7 @@ exports.VerificationLevels = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'VERY_HIGH'];
532535
* * MAXIMUM_CHANNELS
533536
* * MAXIMUM_ATTACHMENTS
534537
* * MAXIMUM_INVITES
538+
* * GUILD_ALREADY_HAS_TEMPLATE
535539
* * UNAUTHORIZED
536540
* * ACCOUNT_VERIFICATION_REQUIRED
537541
* * REQUEST_ENTITY_TOO_LARGE
@@ -584,6 +588,7 @@ exports.APIErrors = {
584588
UNKNOWN_EMOJI: 10014,
585589
UNKNOWN_WEBHOOK: 10015,
586590
UNKNOWN_BAN: 10026,
591+
UNKNOWN_GUILD_TEMPLATE: 10057,
587592
BOT_PROHIBITED_ENDPOINT: 20001,
588593
BOT_ONLY_ENDPOINT: 20002,
589594
CHANNEL_HIT_WRITE_RATELIMIT: 20028,
@@ -596,6 +601,7 @@ exports.APIErrors = {
596601
MAXIMUM_CHANNELS: 30013,
597602
MAXIMUM_ATTACHMENTS: 30015,
598603
MAXIMUM_INVITES: 30016,
604+
GUILD_ALREADY_HAS_TEMPLATE: 30031,
599605
UNAUTHORIZED: 40001,
600606
ACCOUNT_VERIFICATION_REQUIRED: 40002,
601607
REQUEST_ENTITY_TOO_LARGE: 40005,

0 commit comments

Comments
 (0)