From 81cfe004cd425ddba6522c0668604358ba5d5cff Mon Sep 17 00:00:00 2001 From: John Grosh Date: Sat, 12 May 2018 13:12:59 -0400 Subject: [PATCH] Allow null user_id and provide partial Webhooks (#675) * Added IllegalStateException when trying to use newClient() on a Webhook with no token * improved docs, made Webhook extend IFakeable, and added more exceptions --- .../dv8tion/jda/core/audit/AuditLogEntry.java | 29 +++++++++++-- .../jda/core/entities/EntityBuilder.java | 41 +++++++++++-------- .../dv8tion/jda/core/entities/Webhook.java | 25 ++++++++--- .../jda/core/entities/impl/WebhookImpl.java | 17 +++++++- .../pagination/AuditLogPaginationAction.java | 15 ++++++- 5 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/dv8tion/jda/core/audit/AuditLogEntry.java b/src/main/java/net/dv8tion/jda/core/audit/AuditLogEntry.java index 625ef02154..d6321b646e 100644 --- a/src/main/java/net/dv8tion/jda/core/audit/AuditLogEntry.java +++ b/src/main/java/net/dv8tion/jda/core/audit/AuditLogEntry.java @@ -20,13 +20,17 @@ import net.dv8tion.jda.core.entities.Guild; import net.dv8tion.jda.core.entities.ISnowflake; import net.dv8tion.jda.core.entities.User; +import net.dv8tion.jda.core.entities.Webhook; import net.dv8tion.jda.core.entities.impl.GuildImpl; import net.dv8tion.jda.core.entities.impl.UserImpl; +import net.dv8tion.jda.core.entities.impl.WebhookImpl; import net.dv8tion.jda.core.utils.Checks; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; /** * Single entry for an {@link net.dv8tion.jda.core.requests.restaction.pagination.AuditLogPaginationAction @@ -41,20 +45,22 @@ public class AuditLogEntry implements ISnowflake protected final long targetId; protected final GuildImpl guild; protected final UserImpl user; + protected final WebhookImpl webhook; protected final String reason; protected final Map changes; protected final Map options; protected final ActionType type; - public AuditLogEntry(ActionType type, long id, long targetId, GuildImpl guild, UserImpl user, String reason, - Map changes, Map options) + public AuditLogEntry(ActionType type, long id, long targetId, GuildImpl guild, UserImpl user, WebhookImpl webhook, + String reason, Map changes, Map options) { this.type = type; this.id = id; this.targetId = targetId; this.guild = guild; this.user = user; + this.webhook = webhook; this.reason = reason; this.changes = changes != null && !changes.isEmpty() ? Collections.unmodifiableMap(changes) @@ -93,6 +99,17 @@ public String getTargetId() { return Long.toUnsignedString(targetId); } + + /** + * The {@link net.dv8tion.jda.core.entities.Webhook Webhook} that the target id of this audit-log entry refers to + * + * @return Possibly-null Webhook instance + */ + @Nullable + public Webhook getWebhook() + { + return webhook; + } /** * The {@link net.dv8tion.jda.core.entities.Guild Guild} this audit-log entry refers to @@ -108,8 +125,9 @@ public Guild getGuild() * The {@link net.dv8tion.jda.core.entities.User User} responsible * for this action. * - * @return The User instance + * @return Possibly-null User instance */ + @Nullable public User getUser() { return user; @@ -120,6 +138,7 @@ public User getUser() * * @return Possibly-null reason String */ + @Nullable public String getReason() { return reason; @@ -157,6 +176,7 @@ public Map getChanges() * * @return Possibly-null value corresponding to the specified key */ + @Nullable public AuditLogChange getChangeByKey(final AuditLogKey key) { return key == null ? null : getChangeByKey(key.getKey()); @@ -171,6 +191,7 @@ public AuditLogChange getChangeByKey(final AuditLogKey key) * * @return Possibly-null value corresponding to the specified key */ + @Nullable public AuditLogChange getChangeByKey(final String key) { return changes.get(key); @@ -231,6 +252,7 @@ public Map getOptions() * * @return Possibly-null value corresponding to the specified key */ + @Nullable @SuppressWarnings("unchecked") public T getOptionByName(String name) { @@ -252,6 +274,7 @@ public T getOptionByName(String name) * * @return Possibly-null value corresponding to the specified option constant */ + @Nullable public T getOption(AuditLogOption option) { Checks.notNull(option, "Option"); diff --git a/src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java b/src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java index 83c59d7369..0285d77aa4 100644 --- a/src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java @@ -485,9 +485,9 @@ public void createGuildVoiceStatePass(GuildImpl guildObj, JSONArray voiceStates) } } - public User createFakeUser(JSONObject user, boolean modifyCache) { return createUser(user, true, modifyCache); } - public User createUser(JSONObject user) { return createUser(user, false, true); } - private User createUser(JSONObject user, boolean fake, boolean modifyCache) + public UserImpl createFakeUser(JSONObject user, boolean modifyCache) { return createUser(user, true, modifyCache); } + public UserImpl createUser(JSONObject user) { return createUser(user, false, true); } + private UserImpl createUser(JSONObject user, boolean fake, boolean modifyCache) { final long id = user.getLong("id"); UserImpl userObj; @@ -1157,7 +1157,7 @@ public PermissionOverride createPermissionOverride(JSONObject override, Channel return permOverride.setAllow(allow).setDeny(deny); } - public Webhook createWebhook(JSONObject object) + public WebhookImpl createWebhook(JSONObject object) { final long id = object.getLong("id"); final long guildId = object.getLong("guild_id"); @@ -1179,17 +1179,25 @@ public Webhook createWebhook(JSONObject object) .put("avatar", avatar); User defaultUser = createFakeUser(fakeUser, false); - JSONObject ownerJson = object.getJSONObject("user"); - final long userId = ownerJson.getLong("id"); - - User owner = api.getUserById(userId); - if (owner == null) + JSONObject ownerJson = object.optJSONObject("user"); + User owner = null; + + if (ownerJson != null) { - ownerJson.put("id", userId); - owner = createFakeUser(ownerJson, false); - } + final long userId = ownerJson.getLong("id"); - return new WebhookImpl(channel, id).setToken(token).setOwner(channel.getGuild().getMember(owner)).setUser(defaultUser); + owner = api.getUserById(userId); + if (owner == null) + { + ownerJson.put("id", userId); + owner = createFakeUser(ownerJson, false); + } + } + + return new WebhookImpl(channel, id) + .setToken(token) + .setOwner(owner == null ? null : channel.getGuild().getMember(owner)) + .setUser(defaultUser); } public Relationship createRelationship(JSONObject relationshipJson) @@ -1366,7 +1374,7 @@ public AuthorizedApplication createAuthorizedApplication(JSONObject object) return new AuthorizedApplicationImpl(api, authId, description, iconId, id, name, scopes); } - public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson, JSONObject userJson) + public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson, JSONObject userJson, JSONObject webhookJson) { final long targetId = Helpers.optLong(entryJson, "target_id", 0); final long id = entryJson.getLong("id"); @@ -1375,7 +1383,8 @@ public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson, final JSONObject options = entryJson.isNull("options") ? null : entryJson.getJSONObject("options"); final String reason = entryJson.optString("reason", null); - final UserImpl user = (UserImpl) createFakeUser(userJson, false); + final UserImpl user = userJson == null ? null : createFakeUser(userJson, false); + final WebhookImpl webhook = webhookJson == null ? null : createWebhook(webhookJson); final Set changesList; final ActionType type = ActionType.from(typeKey); @@ -1398,7 +1407,7 @@ public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson, CaseInsensitiveMap optionMap = options != null ? new CaseInsensitiveMap<>(options.toMap()) : null; - return new AuditLogEntry(type, id, targetId, guild, user, reason, changeMap, optionMap); + return new AuditLogEntry(type, id, targetId, guild, user, webhook, reason, changeMap, optionMap); } public AuditLogChange createAuditLogChange(JSONObject change) diff --git a/src/main/java/net/dv8tion/jda/core/entities/Webhook.java b/src/main/java/net/dv8tion/jda/core/entities/Webhook.java index ba41052007..c61ace308c 100644 --- a/src/main/java/net/dv8tion/jda/core/entities/Webhook.java +++ b/src/main/java/net/dv8tion/jda/core/entities/Webhook.java @@ -23,13 +23,14 @@ import net.dv8tion.jda.webhook.WebhookClientBuilder; import javax.annotation.CheckReturnValue; +import javax.annotation.Nullable; /** * An object representing Webhooks in Discord * * @since 3.0 */ -public interface Webhook extends ISnowflake +public interface Webhook extends ISnowflake, IFakeable { /** @@ -57,11 +58,12 @@ public interface Webhook extends ISnowflake TextChannel getChannel(); /** - * The owner of this Webhook. + * The owner of this Webhook. This will be null for fake Webhooks, such as those retrieved from Audit Logs. * - * @return A {@link net.dv8tion.jda.core.entities.Member Member} instance - * representing the owner of this Webhook + * @return Possibly-null {@link net.dv8tion.jda.core.entities.Member Member} instance + * representing the owner of this Webhook. */ + @Nullable Member getOwner(); /** @@ -96,15 +98,19 @@ public interface Webhook extends ISnowflake * The execute token for this Webhook. *
This can be used to modify/delete/execute * this Webhook. + * + *

Note: Fake Webhooks, such as those retrieved from Audit Logs, do not contain a token * * @return The execute token for this Webhook */ + @Nullable String getToken(); /** * The {@code POST} route for this Webhook. *
This contains the {@link #getToken() token} and {@link #getId() id} - * of this Webhook. + * of this Webhook. Fake Webhooks without tokens (such as those retrieved from Audit Logs) + * will return a URL without a token. * *

The route returned by this method does not need permission checks * to be executed. @@ -122,6 +128,9 @@ public interface Webhook extends ISnowflake /** * Deletes this Webhook. * + * @throws IllegalStateException + * if the Webhook is fake, such as the Webhooks retrieved from Audit Logs + * * @return {@link net.dv8tion.jda.core.requests.restaction.AuditableRestAction AuditableRestAction} *
The rest action to delete this Webhook. */ @@ -135,6 +144,9 @@ public interface Webhook extends ISnowflake * @throws net.dv8tion.jda.core.exceptions.InsufficientPermissionException * If the currently logged in account does not have {@link net.dv8tion.jda.core.Permission#MANAGE_WEBHOOKS Permission.MANAGE_WEBHOOKS} * + * @throws IllegalStateException + * if the Webhook is fake, such as the Webhooks retrieved from Audit Logs + * * @return The {@link net.dv8tion.jda.core.managers.WebhookManager WebhookManager} for this Webhook */ WebhookManager getManager(); @@ -160,6 +172,9 @@ public interface Webhook extends ISnowflake * *

Remember to close the WebhookClient once you don't need it anymore to free resources! * + * @throws IllegalStateException + * if the Webhook is fake, such as the Webhooks retrieved from Audit Logs + * * @return The new WebhookClientBuilder */ WebhookClientBuilder newClient(); diff --git a/src/main/java/net/dv8tion/jda/core/entities/impl/WebhookImpl.java b/src/main/java/net/dv8tion/jda/core/entities/impl/WebhookImpl.java index f17334ca62..1dab1cc7ed 100644 --- a/src/main/java/net/dv8tion/jda/core/entities/impl/WebhookImpl.java +++ b/src/main/java/net/dv8tion/jda/core/entities/impl/WebhookImpl.java @@ -99,12 +99,15 @@ public String getToken() @Override public String getUrl() { - return Requester.DISCORD_API_PREFIX + "webhooks/" + getId() + "/" + getToken(); + return Requester.DISCORD_API_PREFIX + "webhooks/" + getId() + (getToken() == null ? "" : "/" + getToken()); } @Override public AuditableRestAction delete() { + if (isFake()) + throw new IllegalStateException("Fake Webhooks (such as those retrieved from Audit Logs) " + + "cannot be used for deletion!"); Route.CompiledRoute route = Route.Webhooks.DELETE_TOKEN_WEBHOOK.compile(getId(), token); return new AuditableRestAction(getJDA(), route) { @@ -122,6 +125,9 @@ protected void handleResponse(Response response, Request request) @Override public WebhookManager getManager() { + if (isFake()) + throw new IllegalStateException("Fake Webhooks (such as those retrieved from Audit Logs) " + + "cannot provide a WebhookManager!"); WebhookManager mng = manager; if (mng == null) { @@ -155,6 +161,9 @@ public net.dv8tion.jda.core.managers.WebhookManagerUpdatable getManagerUpdatable @Override public WebhookClientBuilder newClient() { + if (isFake()) + throw new IllegalStateException("Fake Webhooks (such as those retrieved from Audit Logs) " + + "cannot be used to create a WebhookClient!"); return new WebhookClientBuilder(id, token); } @@ -164,6 +173,12 @@ public long getIdLong() return id; } + @Override + public boolean isFake() + { + return token == null; + } + /* -- Impl Setters -- */ public WebhookImpl setOwner(Member member) diff --git a/src/main/java/net/dv8tion/jda/core/requests/restaction/pagination/AuditLogPaginationAction.java b/src/main/java/net/dv8tion/jda/core/requests/restaction/pagination/AuditLogPaginationAction.java index dc686762d0..aa4d01c61e 100644 --- a/src/main/java/net/dv8tion/jda/core/requests/restaction/pagination/AuditLogPaginationAction.java +++ b/src/main/java/net/dv8tion/jda/core/requests/restaction/pagination/AuditLogPaginationAction.java @@ -29,6 +29,7 @@ import net.dv8tion.jda.core.requests.Request; import net.dv8tion.jda.core.requests.Response; import net.dv8tion.jda.core.requests.Route; +import net.dv8tion.jda.core.utils.Helpers; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -193,6 +194,7 @@ protected void handleResponse(Response response, Request> re JSONObject obj = response.getObject(); JSONArray users = obj.getJSONArray("users"); + JSONArray webhooks = obj.getJSONArray("webhooks"); JSONArray entries = obj.getJSONArray("audit_log_entries"); List list = new ArrayList<>(entries.length()); @@ -204,13 +206,22 @@ protected void handleResponse(Response response, Request> re JSONObject user = users.getJSONObject(i); userMap.put(user.getLong("id"), user); } + + TLongObjectMap webhookMap = new TLongObjectHashMap<>(); + for (int i = 0; i < webhooks.length(); i++) + { + JSONObject webhook = webhooks.getJSONObject(i); + webhookMap.put(webhook.getLong("id"), webhook); + } + for (int i = 0; i < entries.length(); i++) { try { JSONObject entry = entries.getJSONObject(i); - JSONObject user = userMap.get(entry.getLong("user_id")); - AuditLogEntry result = builder.createAuditLogEntry((GuildImpl) guild, entry, user); + JSONObject user = userMap.get(Helpers.optLong(entry, "user_id", 0)); + JSONObject webhook = webhookMap.get(Helpers.optLong(entry, "target_id", 0)); + AuditLogEntry result = builder.createAuditLogEntry((GuildImpl) guild, entry, user, webhook); list.add(result); if (this.useCache) this.cached.add(result);