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

Implement access and retrieval of message_reference #1686

Merged
merged 19 commits into from
Jul 30, 2021
18 changes: 17 additions & 1 deletion src/main/java/net/dv8tion/jda/api/entities/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ public interface Message extends ISnowflake, Formattable
"(?:\\?\\S*)?(?:#\\S*)?", // Useless query or URN appendix
Pattern.CASE_INSENSITIVE);

/**
* Returns the {@link MessageReference} for this Message. This will be null if this Message has no reference.
arynxd marked this conversation as resolved.
Show resolved Hide resolved
*
* You can access all the information about a reference through this object.
arynxd marked this conversation as resolved.
Show resolved Hide resolved
* Additionally you can retrieve the referenced Message if discord did not load it in time.
arynxd marked this conversation as resolved.
Show resolved Hide resolved
*
* @return The message reference, or null.
*/
@Nullable
MessageReference getMessageReference();

/**
* Referenced message.
*
Expand All @@ -224,7 +235,12 @@ public interface Message extends ISnowflake, Formattable
* @return The referenced message, or null
*/
@Nullable
Message getReferencedMessage();
arynxd marked this conversation as resolved.
Show resolved Hide resolved
default Message getReferencedMessage()
arynxd marked this conversation as resolved.
Show resolved Hide resolved
{
return getMessageReference() != null
? getMessageReference().getMessage()
: null;
}

/**
* An immutable list of all mentioned {@link net.dv8tion.jda.api.entities.User Users}.
Expand Down
255 changes: 255 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/MessageReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.dv8tion.jda.api.entities;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.exceptions.MissingAccessException;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.requests.Route;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
* An object representing a reference in a Discord message.
* @see Message#getMessageReference()
*/
public class MessageReference
{
private final long messageId;
private final long channelId;
private final long guildId;

private final JDA api;
private final MessageChannel channel;
private final Guild guild;
private Message referencedMessage;

public MessageReference(long messageId, long channelId, long guildId, @Nullable Message referencedMessage, JDA api)
{
this.messageId = messageId;
this.channelId = channelId;
this.guildId = guildId;
this.referencedMessage = referencedMessage;

TextChannel tc = api.getTextChannelById(channelId);

if (tc == null)
Copy link
Member

Choose a reason for hiding this comment

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

To prepare for threads we should do:

if (guildId == 0L)
    this.channel = api.getPrivateChannelById(channelId);
else
    this.channel = (MessageChannel) api.getGuildChannelById(channelId);
this.guild = api.getGuildById(guild); // is null if guildId = 0 anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I assume we just trust that the API gave us the ID for a message channel? Is it worth instaneof checking it

Copy link
Member

Choose a reason for hiding this comment

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

Well messages should only ever be inside message channels, if discord changes this that would break lots of things anyway.

{
this.channel = api.getPrivateChannelById(channelId);
arynxd marked this conversation as resolved.
Show resolved Hide resolved
this.guild = null;
}
else
{
this.channel = tc;
this.guild = api.getGuildById(guildId);
arynxd marked this conversation as resolved.
Show resolved Hide resolved
}

this.api = api;
}

/**
* Retrieves the referenced message for this message.
* If the message already exists, it will be returned immediately.
arynxd marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>The following {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} are possible:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS}
* <br>The request was attempted after the account lost access to the {@link net.dv8tion.jda.api.entities.Guild Guild}
* typically due to being kicked or removed, or after {@link net.dv8tion.jda.api.Permission#MESSAGE_READ Permission.MESSAGE_READ}
* was revoked in the {@link net.dv8tion.jda.api.entities.TextChannel TextChannel}</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The request was attempted after the account lost {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY}
* in the {@link net.dv8tion.jda.api.entities.TextChannel TextChannel}.</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_MESSAGE UNKNOWN_MESSAGE}
* <br>The message has already been deleted.</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
* <br>The request was attempted after the channel was deleted.</li>
* </ul>
*
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If this reference refers to a {@link net.dv8tion.jda.api.entities.TextChannel TextChannel} and the logged in account does not have
arynxd marked this conversation as resolved.
Show resolved Hide resolved
* <ul>
* <li>{@link net.dv8tion.jda.api.Permission#MESSAGE_READ Permission.MESSAGE_READ}</li>
* <li>{@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY}</li>
* </ul>
*
* @return {@link net.dv8tion.jda.api.requests.RestAction RestAction} - Type: {@link net.dv8tion.jda.api.entities.Message}
*/
@Nonnull
public RestAction<Message> resolve()
arynxd marked this conversation as resolved.
Show resolved Hide resolved
{
checkPermission(Permission.MESSAGE_READ);
checkPermission(Permission.MESSAGE_HISTORY);

JDAImpl jda = (JDAImpl) getJDA();
Message referenced = getMessage();

if (referenced != null)
return new CompletedRestAction<>(jda, referenced);

Route.CompiledRoute route = Route.Messages.GET_MESSAGE.compile(getChannelId(), getMessageId());


arynxd marked this conversation as resolved.
Show resolved Hide resolved
return new RestActionImpl<>(jda, route, (response, request) -> {
Message created = jda.getEntityBuilder().createMessage(response.getObject(), getChannel(), false);
arynxd marked this conversation as resolved.
Show resolved Hide resolved
this.referencedMessage = created;
return created;
});
}

/**
* The resolved message, if available.
*
* <p>This will have different meaning depending on the {@link Message#getType() type} of message.
* Usually, this is a {@link MessageType#INLINE_REPLY INLINE_REPLY} reference.
* This can be null even if the type is {@link MessageType#INLINE_REPLY INLINE_REPLY}, when the message it references doesn't exist or discord wasn't able to resolve it in time.
* @see #resolve()
arynxd marked this conversation as resolved.
Show resolved Hide resolved
*
* @return The referenced message, or null if this is not available
*
* @see #resolve()
*/
@Nullable
public Message getMessage()
arynxd marked this conversation as resolved.
Show resolved Hide resolved
{
return referencedMessage;
}

/**
* The channel from which this message originates.
* <br>Messages from other guilds can be referenced, in which case JDA may not have the channel cached.
*
* @return The origin channel for this message reference, or null if this is not available
*
* @see #getChannelId()
*/
@Nullable
public MessageChannel getChannel()
{
return channel;
}


/**
* The guild for this reference.
* <br>This will be null if the message did not come from a guild, the guild was not provided, or JDA did not have the guild cached
*
* @return The guild, or null if this is not available
*
* @see #getGuildId()
*/
@Nullable
public Guild getGuild()
{
return guild;
}

/**
* Returns the message id for this reference, or 0 if no message id was provided.
*
* @return The message id, or 0.
*/
public long getMessageIdLong()
{
return messageId;
}

/**
* Returns the channel id for this reference, or 0 if no channel id was provided.
*
* @return The channel id, or 0.
*/
public long getChannelIdLong()
{
return channelId;
}

/**
* Returns the guild id for this reference, or 0 if no guild id was provided.
*
* @return The guild id, or 0.
*/
public long getGuildIdLong()
arynxd marked this conversation as resolved.
Show resolved Hide resolved
{
return guildId;
}

/**
* Returns the message id for this reference, or 0 if no message id was provided.
*
* @return The message id, or 0.
*/
@Nonnull
public String getMessageId()
{
return Long.toUnsignedString(getMessageIdLong());
}

/**
* Returns the channel id for this reference, or 0 if no channel id was provided.
*
* @return The channel id, or 0.
*/
@Nonnull
public String getChannelId()
{
return Long.toUnsignedString(getChannelIdLong());
}

/**
* Returns the guild id for this reference, or 0 if no guild id was provided.
*
* @return The guild id, or 0.
*/
@Nonnull
public String getGuildId()
{
return Long.toUnsignedString(getGuildIdLong());
}

/**
* Returns the JDA instance related to this message reference.
*
* @return The corresponding JDA instance
*/
@Nonnull
public JDA getJDA()
{
return api;
}

private void checkPermission(Permission permission)
{
if (guild == null) return;
arynxd marked this conversation as resolved.
Show resolved Hide resolved

Member selfMember = guild.getSelfMember();
GuildChannel guildChannel = (GuildChannel) channel;

if (!selfMember.hasPermission(guildChannel, Permission.VIEW_CHANNEL))
arynxd marked this conversation as resolved.
Show resolved Hide resolved
throw new MissingAccessException(guildChannel, Permission.VIEW_CHANNEL);
if (!selfMember.hasPermission(permission))
arynxd marked this conversation as resolved.
Show resolved Hide resolved
throw new InsufficientPermissionException(guildChannel, permission);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.apache.commons.collections4.Bag;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

arynxd marked this conversation as resolved.
Show resolved Hide resolved
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.OffsetDateTime;
Expand Down Expand Up @@ -104,6 +106,14 @@ protected void appendFormat(Formatter formatter, int width, int precision, boole
}
}

@Nullable
@Override
public MessageReference getMessageReference()
{
unsupported();
return null;
}

@Override
public Message getReferencedMessage()
{
Expand Down
19 changes: 17 additions & 2 deletions src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,21 @@ else if (MISSING_CHANNEL.equals(ex.getMessage()))
}
}

MessageReference messageReference = null;

if (!jsonObject.isNull("message_reference")) // always contains the channel + message id for a referenced message
{ // used for when referenced_message is not provided
DataObject messageReferenceJson = jsonObject.getObject("message_reference");

messageReference = new MessageReference(
messageReferenceJson.getLong("message_id", 0),
messageReferenceJson.getLong("channel_id", 0),
messageReferenceJson.getLong("guild_id", 0),
referencedMessage,
api
arynxd marked this conversation as resolved.
Show resolved Hide resolved
);
}

List<ActionRow> components = Collections.emptyList();
Optional<DataArray> componentsArrayOpt = jsonObject.optArray("components");
if (componentsArrayOpt.isPresent())
Expand All @@ -1234,13 +1249,13 @@ else if (MISSING_CHANNEL.equals(ex.getMessage()))
throw new IllegalArgumentException(UNKNOWN_MESSAGE_TYPE);
if (!type.isSystem())
{
message = new ReceivedMessage(id, channel, type, referencedMessage, fromWebhook,
message = new ReceivedMessage(id, channel, type, messageReference, fromWebhook,
mentionsEveryone, mentionedUsers, mentionedRoles, tts, pinned,
content, nonce, user, member, activity, editTime, reactions, attachments, embeds, stickers, components, flags);
}
else
{
message = new SystemMessage(id, channel, type, fromWebhook,
message = new SystemMessage(id, channel, type, messageReference, fromWebhook,
mentionsEveryone, mentionedUsers, mentionedRoles, tts, pinned,
content, nonce, user, member, activity, editTime, reactions, attachments, embeds, stickers, flags);
return message; // We don't need to parse mentions for system messages, they are always empty anyway
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class ReceivedMessage extends AbstractMessage
protected final long id;
protected final MessageType type;
protected final MessageChannel channel;
protected final Message referencedMessage;
protected final MessageReference messageReference;
protected final boolean fromWebhook;
protected final boolean mentionsEveryone;
protected final boolean pinned;
Expand Down Expand Up @@ -88,15 +88,15 @@ public class ReceivedMessage extends AbstractMessage
protected List<String> invites = null;

public ReceivedMessage(
long id, MessageChannel channel, MessageType type, Message referencedMessage,
long id, MessageChannel channel, MessageType type, MessageReference messageReference,
boolean fromWebhook, boolean mentionsEveryone, TLongSet mentionedUsers, TLongSet mentionedRoles, boolean tts, boolean pinned,
String content, String nonce, User author, Member member, MessageActivity activity, OffsetDateTime editTime,
List<MessageReaction> reactions, List<Attachment> attachments, List<MessageEmbed> embeds, List<MessageSticker> stickers, List<ActionRow> components, int flags)
{
super(content, nonce, tts);
this.id = id;
this.channel = channel;
this.referencedMessage = referencedMessage;
this.messageReference = messageReference;
this.type = type;
this.api = (channel != null) ? (JDAImpl) channel.getJDA() : null;
this.fromWebhook = fromWebhook;
Expand Down Expand Up @@ -129,10 +129,11 @@ public JDA getJDA()
return api;
}

@Nullable
@Override
public Message getReferencedMessage()
public MessageReference getMessageReference()
{
return referencedMessage;
return messageReference;
}

@Override
Expand Down
Loading