mirror of
https://github.com/discordjs/discord.js.git
synced 2024-08-21 16:34:43 +12:00
feat: add support for guild templates (#4907)
Co-authored-by: Noel <buechler.noel@outlook.com>
This commit is contained in:
parent
eaecd0e8b7
commit
2b2994badc
8 changed files with 339 additions and 4 deletions
|
@ -65,6 +65,7 @@ export const {
|
|||
GuildEmoji,
|
||||
GuildMember,
|
||||
GuildPreview,
|
||||
GuildTemplate,
|
||||
Integration,
|
||||
Invite,
|
||||
Message,
|
||||
|
|
|
@ -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');
|
||||
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
|
224
src/structures/GuildTemplate.js
Normal file
224
src/structures/GuildTemplate.js
Normal file
|
@ -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;
|
|
@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
|
|
@ -24,16 +24,40 @@ class DataResolver {
|
|||
* @typedef {string} InviteResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give an template code. This can be:
|
||||
* * A template code
|
||||
* * A template URL
|
||||
* @typedef {string} GuildTemplateResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the string to a code based on the passed regex.
|
||||
* @param {string} data The string to resolve
|
||||
* @param {RegExp} regex The RegExp used to extract the code
|
||||
* @returns {string}
|
||||
*/
|
||||
static resolveCode(data, regex) {
|
||||
const match = regex.exec(data);
|
||||
return match ? match[1] || data : data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves InviteResolvable to an invite code.
|
||||
* @param {InviteResolvable} data The invite resolvable to resolve
|
||||
* @returns {string}
|
||||
*/
|
||||
static resolveInviteCode(data) {
|
||||
const inviteRegex = /discord(?:(?:app)?\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i;
|
||||
const match = inviteRegex.exec(data);
|
||||
if (match && match[1]) return match[1];
|
||||
return data;
|
||||
return this.resolveCode(data, /discord(?:(?:app)?\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves GuildTemplateResolvable to a template code.
|
||||
* @param {GuildTemplateResolvable} data The template resolvable to resolve
|
||||
* @returns {string}
|
||||
*/
|
||||
static resolveGuildTemplateCode(data) {
|
||||
return this.resolveCode(data, /discord(?:app)?\.(?:com\/template|new)\/([\w-]{2,255})/i);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
33
typings/index.d.ts
vendored
33
typings/index.d.ts
vendored
|
@ -214,6 +214,7 @@ declare module 'discord.js' {
|
|||
public fetchApplication(): Promise<ClientApplication>;
|
||||
public fetchGuildPreview(guild: GuildResolvable): Promise<GuildPreview>;
|
||||
public fetchInvite(invite: InviteResolvable): Promise<Invite>;
|
||||
public fetchGuildTemplate(template: GuildTemplateResolvable): Promise<GuildTemplate>;
|
||||
public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>;
|
||||
public fetchWebhook(id: Snowflake, token?: string): Promise<Webhook>;
|
||||
public generateInvite(options?: InviteGenerationOptions | PermissionResolvable): Promise<string>;
|
||||
|
@ -524,10 +525,12 @@ declare module 'discord.js' {
|
|||
|
||||
export class DataResolver {
|
||||
public static resolveBase64(data: Base64Resolvable): string;
|
||||
public static resolveCode(data: string, regx: RegExp): string;
|
||||
public static resolveFile(resource: BufferResolvable | Stream): Promise<Buffer | Stream>;
|
||||
public static resolveFileAsBuffer(resource: BufferResolvable | Stream): Promise<Buffer>;
|
||||
public static resolveImage(resource: BufferResolvable | Base64Resolvable): Promise<string>;
|
||||
public static resolveInviteCode(data: InviteResolvable): string;
|
||||
public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string;
|
||||
}
|
||||
|
||||
export class DiscordAPIError extends Error {
|
||||
|
@ -634,6 +637,7 @@ declare module 'discord.js' {
|
|||
public addMember(user: UserResolvable, options: AddGuildMemberOptions): Promise<GuildMember>;
|
||||
public bannerURL(options?: ImageURLOptions): string | null;
|
||||
public createIntegration(data: IntegrationData, reason?: string): Promise<Guild>;
|
||||
public createTemplate(name: string, description?: string): Promise<GuildTemplate>;
|
||||
public delete(): Promise<Guild>;
|
||||
public discoverySplashURL(options?: ImageURLOptions): string | null;
|
||||
public edit(data: GuildEditData, reason?: string): Promise<Guild>;
|
||||
|
@ -646,6 +650,7 @@ declare module 'discord.js' {
|
|||
public fetchIntegrations(options?: FetchIntegrationsOptions): Promise<Collection<string, Integration>>;
|
||||
public fetchInvites(): Promise<Collection<string, Invite>>;
|
||||
public fetchPreview(): Promise<GuildPreview>;
|
||||
public fetchTemplates(): Promise<Collection<GuildTemplate['code'], GuildTemplate>>;
|
||||
public fetchVanityCode(): Promise<string>;
|
||||
public fetchVanityData(): Promise<{ code: string; uses: number }>;
|
||||
public fetchVoiceRegions(): Promise<Collection<string, VoiceRegion>>;
|
||||
|
@ -852,6 +857,29 @@ declare module 'discord.js' {
|
|||
public toString(): string;
|
||||
}
|
||||
|
||||
export class GuildTemplate extends Base {
|
||||
constructor(client: Client, data: object);
|
||||
public readonly createdTimestamp: number;
|
||||
public readonly updatedTimestamp: number;
|
||||
public readonly url: string;
|
||||
public code: string;
|
||||
public name: string;
|
||||
public description: string | null;
|
||||
public usageCount: number;
|
||||
public creator: User;
|
||||
public creatorID: Snowflake;
|
||||
public createdAt: Date;
|
||||
public updatedAt: Date;
|
||||
public guild: Guild | null;
|
||||
public guildID: Snowflake;
|
||||
public serializedGuild: object;
|
||||
public unSynced: boolean | null;
|
||||
public createGuild(name: string, icon?: BufferResolvable | Base64Resolvable): Promise<Guild>;
|
||||
public delete(): Promise<GuildTemplate>;
|
||||
public edit(options?: { name?: string; description?: string }): Promise<GuildTemplate>;
|
||||
public sync(): Promise<GuildTemplate>;
|
||||
}
|
||||
|
||||
export class GuildPreviewEmoji extends BaseGuildEmoji {
|
||||
constructor(client: Client, data: object, guild: GuildPreview);
|
||||
public guild: GuildPreview;
|
||||
|
@ -2101,6 +2129,7 @@ declare module 'discord.js' {
|
|||
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;
|
||||
|
@ -2113,6 +2142,7 @@ declare module 'discord.js' {
|
|||
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;
|
||||
|
@ -2637,6 +2667,7 @@ declare module 'discord.js' {
|
|||
host?: string;
|
||||
cdn?: string;
|
||||
invite?: string;
|
||||
template?: string;
|
||||
}
|
||||
|
||||
type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096;
|
||||
|
@ -2694,6 +2725,8 @@ declare module 'discord.js' {
|
|||
|
||||
type InviteResolvable = string;
|
||||
|
||||
type GuildTemplateResolvable = string;
|
||||
|
||||
type MembershipStates = 'INVITED' | 'ACCEPTED';
|
||||
|
||||
type MessageAdditions = MessageEmbed | MessageAttachment | (MessageEmbed | MessageAttachment)[];
|
||||
|
|
Loading…
Reference in a new issue