Skip to content

Commit

Permalink
Allow null user_id and provide partial Webhooks (#675)
Browse files Browse the repository at this point in the history
* Added IllegalStateException when trying to use newClient() on a Webhook with no token
* improved docs, made Webhook extend IFakeable, and added more exceptions
  • Loading branch information
jagrosh authored and MinnDevelopment committed May 12, 2018
1 parent 8e2607f commit 81cfe00
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 27 deletions.
29 changes: 26 additions & 3 deletions src/main/java/net/dv8tion/jda/core/audit/AuditLogEntry.java
Expand Up @@ -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
Expand All @@ -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<String, AuditLogChange> changes;
protected final Map<String, Object> options;
protected final ActionType type;

public AuditLogEntry(ActionType type, long id, long targetId, GuildImpl guild, UserImpl user, String reason,
Map<String, AuditLogChange> changes, Map<String, Object> options)
public AuditLogEntry(ActionType type, long id, long targetId, GuildImpl guild, UserImpl user, WebhookImpl webhook,
String reason, Map<String, AuditLogChange> changes, Map<String, Object> 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)
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -120,6 +138,7 @@ public User getUser()
*
* @return Possibly-null reason String
*/
@Nullable
public String getReason()
{
return reason;
Expand Down Expand Up @@ -157,6 +176,7 @@ public Map<String, AuditLogChange> getChanges()
*
* @return Possibly-null value corresponding to the specified key
*/
@Nullable
public AuditLogChange getChangeByKey(final AuditLogKey key)
{
return key == null ? null : getChangeByKey(key.getKey());
Expand All @@ -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);
Expand Down Expand Up @@ -231,6 +252,7 @@ public Map<String, Object> getOptions()
*
* @return Possibly-null value corresponding to the specified key
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getOptionByName(String name)
{
Expand All @@ -252,6 +274,7 @@ public <T> T getOptionByName(String name)
*
* @return Possibly-null value corresponding to the specified option constant
*/
@Nullable
public <T> T getOption(AuditLogOption option)
{
Checks.notNull(option, "Option");
Expand Down
41 changes: 25 additions & 16 deletions src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand All @@ -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)
Expand Down Expand Up @@ -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");
Expand All @@ -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<AuditLogChange> changesList;
final ActionType type = ActionType.from(typeKey);

Expand All @@ -1398,7 +1407,7 @@ public AuditLogEntry createAuditLogEntry(GuildImpl guild, JSONObject entryJson,
CaseInsensitiveMap<String, Object> 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)
Expand Down
25 changes: 20 additions & 5 deletions src/main/java/net/dv8tion/jda/core/entities/Webhook.java
Expand Up @@ -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
{

/**
Expand Down Expand Up @@ -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();

/**
Expand Down Expand Up @@ -96,15 +98,19 @@ public interface Webhook extends ISnowflake
* The execute token for this Webhook.
* <br>This can be used to modify/delete/execute
* this Webhook.
*
* <p><b>Note: Fake Webhooks, such as those retrieved from Audit Logs, do not contain a token</b>
*
* @return The execute token for this Webhook
*/
@Nullable
String getToken();

/**
* The {@code POST} route for this Webhook.
* <br>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.
*
* <p>The route returned by this method does not need permission checks
* to be executed.
Expand All @@ -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}
* <br>The rest action to delete this Webhook.
*/
Expand All @@ -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();
Expand All @@ -160,6 +172,9 @@ public interface Webhook extends ISnowflake
*
* <p><b><u>Remember to close the WebhookClient once you don't need it anymore to free resources!</u></b>
*
* @throws IllegalStateException
* if the Webhook is fake, such as the Webhooks retrieved from Audit Logs
*
* @return The new WebhookClientBuilder
*/
WebhookClientBuilder newClient();
Expand Down
Expand Up @@ -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<Void> 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<Void>(getJDA(), route)
{
Expand All @@ -122,6 +125,9 @@ protected void handleResponse(Response response, Request<Void> 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)
{
Expand Down Expand Up @@ -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);
}

Expand All @@ -164,6 +173,12 @@ public long getIdLong()
return id;
}

@Override
public boolean isFake()
{
return token == null;
}

/* -- Impl Setters -- */

public WebhookImpl setOwner(Member member)
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -193,6 +194,7 @@ protected void handleResponse(Response response, Request<List<AuditLogEntry>> re

JSONObject obj = response.getObject();
JSONArray users = obj.getJSONArray("users");
JSONArray webhooks = obj.getJSONArray("webhooks");
JSONArray entries = obj.getJSONArray("audit_log_entries");

List<AuditLogEntry> list = new ArrayList<>(entries.length());
Expand All @@ -204,13 +206,22 @@ protected void handleResponse(Response response, Request<List<AuditLogEntry>> re
JSONObject user = users.getJSONObject(i);
userMap.put(user.getLong("id"), user);
}

TLongObjectMap<JSONObject> 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);
Expand Down

0 comments on commit 81cfe00

Please sign in to comment.