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
26 changes: 26 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.Template;
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,31 @@ public Flux<Region> getRegions() {
.map(data -> new Region(this, data));
}

/**
* Requests to retrieve the guild's templates.
*
* @return A {@link Flux} that continually emits the guild's {@link Template templates}. If an error is received,
* it is emitted through the {@code Flux}.
*/
public Flux<Template> getGuildTemplates(Snowflake guildId) {
return getRestClient().getTemplateService()
.getTemplates(guildId.asLong())
.map(data -> new Template(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 Template} as represented by the supplied
* * ID. If an error is received, it is emitted through the {@code Mono}.
*/
public Mono<Template> getTemplateByCode(String templateCode) {
return getRestClient().getTemplateService()
.getTemplate(templateCode)
.map(data -> new Template(this, data));
}

/**
* Gets the bot user's ID.
*
Expand Down
214 changes: 214 additions & 0 deletions core/src/main/java/discord4j/core/object/Template.java
@@ -0,0 +1,214 @@
package discord4j.core.object;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header


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.TemplateCreateGuildSpec;
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;

public class Template implements DiscordObject {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing javadoc


/** 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 Template} with an associated Discord client and 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 Template(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;
}

/**
* Returns this object underlying raw data structure.
*
* @return an immutable data representation of this object
*/
public TemplateData getData() {
return data;
}

/**
* Returns the template code (unique ID).
*
* @return the template code (unique ID)
*/
public final String getCode() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these methods need to be final. Most of our entity classes are final, though.

return data.code();
}

/**
* Returns the source guild ID.
*
* @return the guild id
*/
public final Snowflake getGuildId() {
return Snowflake.of(guildId);
}

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

/**
* Returns the description of the template.
*
* @return the template description
*/
public final Optional<String> getDescription() {
return data.description();
}

/**
* Returns the amount of times the template has been used.
*
* @return the template usage count
*/
public final int getUsageCount() {
return data.usageCount();
}

/**
* Returns the id of the creator of this template.
*
* @return the creator id
*/
public final Snowflake getCreatorId() {
return Snowflake.of(data.creatorId());
}

/**
* Returns the creator of this template.
*
* @return the creator
*/
public final User getCreator() {
return new User(gateway, data.creator());
}

/**
* Returns an {@link Instant} when this template was created.
*
* @return a timestamp when template was last updated
*/
public final Instant getCreatedAt() {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(data.createdAt(), Instant::from);
}

/**
* Returns an {@link Instant} when this template was last updated.
*
* @return a timestamp when template was last updated
*/
public final 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 final SerializedSourceGuildData getSourceGuild() {
return data.serializedSourceGuild();
}

/**
* Creates a new guild from a template. Fires Guild Create Gateway event.
*
* @return the guild object
*/
public final Mono<Guild> createGuild(final Consumer<? super TemplateCreateGuildSpec> spec) {
return Mono.defer(
() -> {
TemplateCreateGuildSpec mutatedSpec = new TemplateCreateGuildSpec();
spec.accept(mutatedSpec);
return gateway.getRestClient().getTemplateService().createGuild(getCode(),
mutatedSpec.asRequest(), mutatedSpec.getReason());
})
.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 guild with this template. If an error is received, it
* will be emitted through the Mono.
*/
public final Mono<Template> sync() {
return sync(guildId);
}

/**
* Requests to sync this template with the given guild.
*
* @param guildId the guild to fetch current state for the template
* @return a {@link Mono} that, upon subscription, syncs a guild with this template. If an error is received, it
* will be emitted through the Mono.
*/
public final Mono<Template> sync(long guildId) {
return gateway.getRestClient().getTemplateService()
.syncTemplate(guildId, getCode())
.map(data -> new Template(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 Template}. If an error is
* received, it is emitted through the {@code Mono}.
*/
public final Mono<Template> 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(), mutatedSpec.getReason());
})
.map(data -> new Template(gateway, data));
}

/**
* Requests to delete this template while optionally specifying a reason.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to specify the reason? (does that even show up in Audit Log?)

*
* @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 final Mono<Template> delete(Snowflake guildId) {
return gateway.getRestClient().getTemplateService()
.deleteTemplate(guildId.asLong(), getCode())
.map(data -> new Template(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 an template. A guild can only have a single template.
*
* @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 Template} on success. If an error is
* received, it is emitted through the {@code Mono}.
*/
public Mono<Template> 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(), mutatedSpec.getReason());
})
.map(data -> new Template(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 invites of the guild.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Requests to retrieve the invites of the guild.
* Requests to retrieve the templates of the guild.

*
* @return A {@link Flux} that continually emits the {@link ExtendedInvite invites} of the guild. If an error is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @return A {@link Flux} that continually emits the {@link ExtendedInvite invites} of the guild. If an error is
* @return A {@link Flux} that continually emits the {@link Template templates} of the guild. If an error is

* received, it is emitted through the {@code Flux}.
*/
public Flux<Template> getTemplates() {
return gateway.getRestClient().getTemplateService()
.getTemplates(getId().asLong())
.map(data -> new Template(gateway, data));
}

/**
* Requests to change the bot user's nickname in this guild.
*
Expand Down
Expand Up @@ -16,16 +16,11 @@
*/
package discord4j.core.retriever;

import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.GuildEmoji;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.Role;
import discord4j.core.object.entity.User;
import discord4j.common.util.Snowflake;
import discord4j.core.object.entity.*;
import discord4j.core.object.entity.channel.Channel;
import discord4j.core.object.entity.channel.GuildChannel;
import discord4j.core.util.OrderUtil;
import discord4j.common.util.Snowflake;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down
Expand Up @@ -16,15 +16,10 @@
*/
package discord4j.core.retriever;

import discord4j.core.object.entity.Guild;
import discord4j.core.object.entity.GuildEmoji;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.Message;
import discord4j.core.object.entity.Role;
import discord4j.core.object.entity.User;
import discord4j.common.util.Snowflake;
import discord4j.core.object.entity.*;
import discord4j.core.object.entity.channel.Channel;
import discord4j.core.object.entity.channel.GuildChannel;
import discord4j.common.util.Snowflake;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down
Expand Up @@ -16,6 +16,7 @@
*/
package discord4j.core.retriever;

import discord4j.common.util.Snowflake;
import discord4j.core.GatewayDiscordClient;
import discord4j.core.object.entity.*;
import discord4j.core.object.entity.channel.Channel;
Expand All @@ -24,7 +25,6 @@
import discord4j.discordjson.json.*;
import discord4j.rest.RestClient;
import discord4j.rest.util.PaginationUtil;
import discord4j.common.util.Snowflake;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down
Expand Up @@ -89,8 +89,8 @@ public Mono<Role> getRoleById(Snowflake guildId, Snowflake roleId) {
@Override
public Mono<User> getUserById(Snowflake userId) {
return stateView.getUserStore()
.find(userId.asLong())
.map(data -> new User(gateway, data));
.find(userId.asLong())
.map(data -> new User(gateway, data));
}

@Override
Expand Down