Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for server templates #876

Merged
merged 14 commits into from Mar 28, 2021
14 changes: 14 additions & 0 deletions core/src/main/java/discord4j/core/GatewayDiscordClient.java
Expand Up @@ -27,6 +27,7 @@
import discord4j.core.event.domain.Event;
import discord4j.core.object.Invite;
import discord4j.core.object.Region;
import discord4j.core.object.GuildTemplate;
import discord4j.core.object.entity.*;
import discord4j.core.object.entity.channel.Channel;
import discord4j.core.object.entity.channel.GuildChannel;
Expand Down Expand Up @@ -249,6 +250,19 @@ public Flux<Region> getRegions() {
.map(data -> new Region(this, data));
}

/**
* Requests to retrieve the template represented by the supplied code.
*
* @param templateCode The code of the template.
* @return A {@link Mono} where, upon successful completion, emits the {@link GuildTemplate} as represented by the
* supplied code. If an error is received, it is emitted through the {@code Mono}.
*/
public Mono<GuildTemplate> getTemplateByCode(String templateCode) {
return getRestClient().getTemplateService()
.getTemplate(templateCode)
.map(data -> new GuildTemplate(this, data));
}

/**
* Gets the bot user's ID.
*
Expand Down
226 changes: 226 additions & 0 deletions core/src/main/java/discord4j/core/object/GuildTemplate.java
@@ -0,0 +1,226 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/
package discord4j.core.object;

import discord4j.common.util.Snowflake;
import discord4j.core.GatewayDiscordClient;
import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.User;
import discord4j.core.spec.GuildTemplateEditSpec;
import discord4j.core.spec.GuildCreateFromTemplateSpec;
import discord4j.discordjson.json.SerializedSourceGuildData;
import discord4j.discordjson.json.TemplateData;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

/**
* A Discord Guild Template.
*
* @see <a href="https://discord.com/developers/docs/resources/template">Template Resource</a>
*/
public final class GuildTemplate implements DiscordObject {

/** The gateway associated to this object. */
private final GatewayDiscordClient gateway;

/** The raw data as represented by Discord. */
private final TemplateData data;

/** The ID of the guild this template is associated to. */
private final long guildId;

/**
* Constructs a {@code GuildTemplate} with an associated {@link GatewayDiscordClient} and Discord data.
*
* @param gateway The {@link GatewayDiscordClient} associated to this object, must be non-null.
* @param data The raw data as represented by Discord, must be non-null.
*/
public GuildTemplate(final GatewayDiscordClient gateway, final TemplateData data) {
this.gateway = Objects.requireNonNull(gateway);
this.data = Objects.requireNonNull(data);
this.guildId = Snowflake.asLong(data.sourceGuildId());
}

@Override
public GatewayDiscordClient getClient() {
return this.gateway;
}

/**
* Gets the data of the template.
*
* @return The data of the template.
*/
public TemplateData getData() {
return data;
}

/**
* Gets the template code (unique ID).
*
* @return The template code (unique ID).
*/
public String getCode() {
return data.code();
}

/**
* Gets the ID of the guild this template is associated with.
*
* @return The source guild ID.
*/
public Snowflake getGuildId() {
return Snowflake.of(guildId);
}

/**
* Gets the name of the template.
*
* @return The template name.
*/
public String getName() {
return data.name();
}

/**
* Gets the description of the template, if present.
*
* @return The template description.
*/
public Optional<String> getDescription() {
return data.description();
}

/**
* Gets the number of times the template has been used.
*
* @return The number of times the template has been used.
*/
public int getUsageCount() {
return data.usageCount();
}

/**
* Gets the ID of the user who created the template.
*
* @return The ID of the user who created the template.
*/
public Snowflake getCreatorId() {
return Snowflake.of(data.creatorId());
}

/**
* Gets the user who created the template.
*
* @return The user who created the template.
*/
public User getCreator() {
return new User(gateway, data.creator());
}

/**
* Gets when the template was created.
*
* @return When the template was created.
*/
public Instant getCreatedAt() {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(data.createdAt(), Instant::from);
}

/**
* Gets when the template was last updated.
*
* @return When the template was last updated.
*/
public Instant getUpdatedAt() {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(data.updatedAt(), Instant::from);
}

/**
* Gets the guild snapshot of the template.
*
* @return The guild snapshot.
*/
public SerializedSourceGuildData getSourceGuild() {
return data.serializedSourceGuild();
}

/**
* Requests to create a new guild from this template.
*
* @param spec A {@link Consumer} that provides a "blank" {@link GuildTemplateEditSpec} to be operated on.
* @return A {@link Mono} where, upon successful completion, emits the {@link Guild created guild}. If an error is
* received, it is emitted through the {@code Mono}.
*/
public Mono<Guild> createGuild(final Consumer<? super GuildCreateFromTemplateSpec> spec) {
return Mono.defer(
() -> {
GuildCreateFromTemplateSpec mutatedSpec = new GuildCreateFromTemplateSpec();
spec.accept(mutatedSpec);
return gateway.getRestClient().getTemplateService()
.createGuild(getCode(), mutatedSpec.asRequest());
})
.map(data -> new Guild(gateway, data));
}

/**
* Requests to sync this template with the guild's current state.
*
* @return a {@link Mono} that, upon subscription, syncs a template with its guild. If an error is received, it
* will be emitted through the Mono.
*/
public Mono<GuildTemplate> sync() {
return gateway.getRestClient().getTemplateService()
.syncTemplate(guildId, getCode())
.map(data -> new GuildTemplate(gateway, data));
}

/**
* Requests to edit this guild template.
*
* @param spec A {@link Consumer} that provides a "blank" {@link GuildTemplateEditSpec} to be operated on.
* @return A {@link Mono} where, upon successful completion, emits the edited {@link GuildTemplate}. If an error is
* received, it is emitted through the {@code Mono}.
*/
public Mono<GuildTemplate> edit(final Consumer<? super GuildTemplateEditSpec> spec) {
return Mono.defer(
() -> {
GuildTemplateEditSpec mutatedSpec = new GuildTemplateEditSpec();
spec.accept(mutatedSpec);
return gateway.getRestClient().getTemplateService()
.modifyTemplate(guildId, getCode(), mutatedSpec.asRequest());
})
.map(data -> new GuildTemplate(gateway, data));
}

/**
* Requests to delete this template.
*
* @return A {@link Mono} where, upon successful completion, emits the deleted template. If an error is received,
* it is emitted through the {@code Mono}.
*/
public Mono<GuildTemplate> delete() {
return gateway.getRestClient().getTemplateService()
.deleteTemplate(guildId, getCode())
.map(data -> new GuildTemplate(gateway, data));
}
}
35 changes: 31 additions & 4 deletions core/src/main/java/discord4j/core/object/entity/Guild.java
Expand Up @@ -18,10 +18,7 @@

import discord4j.common.util.Snowflake;
import discord4j.core.GatewayDiscordClient;
import discord4j.core.object.Ban;
import discord4j.core.object.ExtendedInvite;
import discord4j.core.object.Region;
import discord4j.core.object.VoiceState;
import discord4j.core.object.*;
import discord4j.core.object.audit.AuditLogEntry;
import discord4j.core.object.entity.channel.*;
import discord4j.core.object.presence.Presence;
Expand Down Expand Up @@ -1010,6 +1007,24 @@ public Mono<GuildEmoji> createEmoji(final Consumer<? super GuildEmojiCreateSpec>
.map(data -> new GuildEmoji(gateway, data, getId().asLong()));
}

/**
* Requests to create a template based on this guild.
*
* @param spec A {@link Consumer} that provides a "blank" {@link GuildTemplateCreateSpec} to be operated on.
* @return A {@link Mono} where, upon subscription, emits the created {@link GuildTemplate} on success. If an error
* is received, it is emitted through the {@code Mono}.
*/
public Mono<GuildTemplate> createTemplate(final Consumer<? super GuildTemplateCreateSpec> spec) {
return Mono.defer(
() -> {
GuildTemplateCreateSpec mutatedSpec = new GuildTemplateCreateSpec();
spec.accept(mutatedSpec);
return gateway.getRestClient().getTemplateService()
.createTemplate(getId().asLong(), mutatedSpec.asRequest());
})
.map(data -> new GuildTemplate(gateway, data));
}

/**
* Requests to create a role.
*
Expand Down Expand Up @@ -1368,6 +1383,18 @@ public Flux<ExtendedInvite> getInvites() {
.map(data -> new ExtendedInvite(gateway, data));
}

/**
* Requests to retrieve the templates of the guild.
*
* @return A {@link Flux} that continually emits the {@link GuildTemplate templates} of the guild. If an error is
* received, it is emitted through the {@code Flux}.
*/
public Flux<GuildTemplate> getTemplates() {
return gateway.getRestClient().getTemplateService()
.getTemplates(getId().asLong())
.map(data -> new GuildTemplate(gateway, data));
}

/**
* Requests to change the bot user's nickname in this guild.
*
Expand Down
@@ -0,0 +1,62 @@
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/
package discord4j.core.spec;

import discord4j.discordjson.json.TemplateCreateGuildRequest;
import discord4j.discordjson.possible.Possible;
import discord4j.rest.util.Image;

/**
* Spec used to create a guild from a template.
*
* @see discord4j.core.object.GuildTemplate#createGuild(java.util.function.Consumer)
*/
public class GuildCreateFromTemplateSpec implements Spec<TemplateCreateGuildRequest> {

private String name = null;
private Possible<String> icon = Possible.absent();

/**
* Sets the name for the created guild.
*
* @param name The name of the guild.
* @return This spec.
*/
public GuildCreateFromTemplateSpec setName(String name) {
this.name = name;
return this;
}

/**
* Sets the icon for the created guild.
*
* @param icon The icon of the guild.
* @return This spec.
*/
public GuildCreateFromTemplateSpec setIcon(Image icon) {
this.icon = Possible.of(icon.getDataUri());
return this;
}

@Override
public TemplateCreateGuildRequest asRequest() {
return TemplateCreateGuildRequest.builder()
.name(name)
.icon(icon)
.build();
}
}