net.dv8tion.jda.core.entities.MessageHistory.java Source code

Java tutorial

Introduction

Here is the source code for net.dv8tion.jda.core.entities.MessageHistory.java

Source

/*
 *     Copyright 2015-2017 Austin Keener & Michael Ritter & Florian Spie
 *
 * 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.core.entities;

import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.exceptions.PermissionException;
import net.dv8tion.jda.core.requests.Request;
import net.dv8tion.jda.core.requests.Response;
import net.dv8tion.jda.core.requests.RestAction;
import net.dv8tion.jda.core.requests.Route;
import net.dv8tion.jda.core.utils.MiscUtil;
import org.apache.commons.collections4.map.ListOrderedMap;
import org.json.JSONArray;

import javax.annotation.CheckReturnValue;
import java.util.*;

/**
 * Represents an access point to the {@link net.dv8tion.jda.core.entities.Message Message} history of a
 * {@link net.dv8tion.jda.core.entities.MessageChannel MessageChannel}.
 * <br><b>Note:</b> Message order is always in recent to past order. I.e: A message at index 0
 * of a list is more recent than a message at index 1.
 */
public class MessageHistory {
    protected final MessageChannel channel;

    protected final ListOrderedMap<Long, Message> history = new ListOrderedMap<>();

    /**
     * Creates a new MessageHistory object.
     *
     * @param  channel
     *         The {@link net.dv8tion.jda.core.entities.MessageChannel MessageChannel} to retrieval history from.
     */
    public MessageHistory(MessageChannel channel) {
        this.channel = channel;
        if (channel instanceof TextChannel
                && !((TextChannel) channel).getGuild().getSelfMember().hasPermission(Permission.MESSAGE_HISTORY))
            throw new PermissionException(Permission.MESSAGE_HISTORY);
    }

    /**
     * The corresponding JDA instance for this MessageHistory
     *
     * @return The corresponding JDA instance
     */
    public JDA getJDA() {
        return channel.getJDA();
    }

    /**
     * The amount of retrieved {@link net.dv8tion.jda.core.entities.Message Messages}
     * by this MessageHistory.
     * <br>This returns {@code 0} until any call to retrieve messages has completed.
     * See {@link #retrievePast(int)} and {@link #retrieveFuture(int)}!
     *
     * @return Amount of retrieved messages
     */
    public int size() {
        return history.size();
    }

    /**
     * Whether this MessageHistory instance has retrieved any messages.
     *
     * @return True, If this MessageHistory instance has not retrieved any messages from discord.
     */
    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * Returns the {@link net.dv8tion.jda.core.entities.MessageChannel MessageChannel} that this MessageHistory
     * is related to.
     *
     * @return The MessageChannel of this history.
     */
    public MessageChannel getChannel() {
        return channel;
    }

    /**
     * Retrieves messages from Discord that were sent before the oldest sent message in MessageHistory's history cache
     * ({@link #getRetrievedHistory()}).
     * <br>Can only retrieve a <b>maximum</b> of {@code 100} messages at a time.
     * <br>This method has 2 modes of operation: initial retrieval and additional retrieval.
     * <ul>
     *     <li><b>Initial Retrieval</b>
     *     <br>This mode is what is used when no {@link net.dv8tion.jda.core.entities.Message Messages} have been retrieved
     *         yet ({@link #getRetrievedHistory()}'s size is 0). Initial retrieval starts from the most recent message sent
     *         to the channel and retrieves backwards from there. So, if 50 messages are retrieved during this mode, the
     *         most recent 50 messages will be retrieved.</li>
     *
     *     <li><b>Additional Retrieval</b>
     *     <br>This mode is used once some {@link net.dv8tion.jda.core.entities.Message Messages} have already been retrieved
     *         from Discord and are stored in MessageHistory's history ({@link #getRetrievedHistory()}). When retrieving
     *         messages in this mode, MessageHistory will retrieve previous messages starting from the oldest message
     *         stored in MessageHistory.
     *     <br>E.g: If you initially retrieved 10 messages, the next call to this method to retrieve 10 messages would
     *         retrieve the <i>next</i> 10 messages, starting from the oldest message of the 10 previously retrieved messages.</li>
     * </ul>
     * <p>
     * Possible {@link net.dv8tion.jda.core.requests.ErrorResponse ErrorResponses} include:
     * <ul>
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#UNKNOWN_MESSAGE UNKNOWN_MESSAGE}
     *     <br>Can occur if retrieving in Additional Mode and the Message being used as the marker for the last retrieved
     *         Message was deleted. Currently, to fix this, you need to create a new
     *         {@link net.dv8tion.jda.core.entities.MessageHistory MessageHistory} instance.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS}
     *     <br>Can occur if the request for history retrieval was executed <i>after</i> JDA lost access to the Channel,
     *         typically due to the account being removed from the {@link net.dv8tion.jda.core.entities.Guild Guild} or
     *         {@link net.dv8tion.jda.client.entities.Group Group}.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
     *     <br>Can occur if the request for history retrieval was executed <i>after</i> JDA lost the
     *         {@link net.dv8tion.jda.core.Permission#MESSAGE_HISTORY} permission.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
     *     <br>The send request was attempted after the channel was deleted.</li>
     * </ul>
     *
     * @param  amount
     *         The amount of {@link net.dv8tion.jda.core.entities.Message Messages} to retrieve.
     *
     * @throws java.lang.IllegalArgumentException
     *         The the {@code amount} is less than {@code 1} or greater than {@code 100}.
     *
     * @return {@link net.dv8tion.jda.core.requests.RestAction RestAction} -
     *         Type: {@link java.util.List List}{@literal <}{@link net.dv8tion.jda.core.entities.Message Message}{@literal >}
     *         <br>Retrieved Messages are placed in a List and provided in order of most recent to oldest with most recent
     *         starting at index 0. If the list is empty, there were no more messages left to retrieve.
     */
    @CheckReturnValue
    public RestAction<List<Message>> retrievePast(int amount) {
        if (amount > 100 || amount < 1)
            throw new IllegalArgumentException(
                    "Message retrieval limit is between 1 and 100 messages. No more, no less. Limit provided: "
                            + amount);

        Route.CompiledRoute route = Route.Messages.GET_MESSAGE_HISTORY.compile(channel.getId())
                .withQueryParams("limit", Integer.toString(amount));

        if (!history.isEmpty())
            route = route.withQueryParams("before", String.valueOf(history.lastKey()));

        return new RestAction<List<Message>>(getJDA(), route) {
            @Override
            protected void handleResponse(Response response, Request<List<Message>> request) {
                if (!response.isOk()) {
                    request.onFailure(response);
                    return;
                }

                EntityBuilder builder = api.getEntityBuilder();
                ;
                LinkedList<Message> msgs = new LinkedList<>();
                JSONArray historyJson = response.getArray();

                for (int i = 0; i < historyJson.length(); i++)
                    msgs.add(builder.createMessage(historyJson.getJSONObject(i)));

                msgs.forEach(msg -> history.put(msg.getIdLong(), msg));
                request.onSuccess(msgs);
            }
        };
    }

    /**
     * Retrieves messages from Discord that were sent more recently than the most recently sent message in
     * MessageHistory's history cache ({@link #getRetrievedHistory()}).
     * Use case for this method is for getting more recent messages after jumping to a specific point in history
     * using something like {@link MessageChannel#getHistoryAround(String, int)}.
     * <br>This method works in the same way as {@link #retrievePast(int)}'s Additional Retrieval mode.
     * <p>
     * <b>Note:</b> This method can only be used after {@link net.dv8tion.jda.core.entities.Message Messages} have already
     * been retrieved from Discord.
     * <p>
     * Possible {@link net.dv8tion.jda.core.requests.ErrorResponse ErrorResponses} include:
     * <ul>
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#UNKNOWN_MESSAGE UNKNOWN_MESSAGE}
     *     <br>Can occur if retrieving in Additional Mode and the Message being used as the marker for the last retrieved
     *         Message was deleted. Currently, to fix this, you need to create a new
     *         {@link net.dv8tion.jda.core.entities.MessageHistory MessageHistory} instance.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS}
     *     <br>Can occur if the request for history retrieval was executed <i>after</i> JDA lost access to the Channel,
     *         typically due to the account being removed from the {@link net.dv8tion.jda.core.entities.Guild Guild} or
     *         {@link net.dv8tion.jda.client.entities.Group Group}.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
     *     <br>Can occur if the request for history retrieval was executed <i>after</i> JDA lost the
     *         {@link net.dv8tion.jda.core.Permission#MESSAGE_HISTORY} permission.</li>
     *
     *     <li>{@link net.dv8tion.jda.core.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
     *     <br>The send request was attempted after the channel was deleted.</li>
     * </ul>
     *
     * @param  amount
     *         The amount of {@link net.dv8tion.jda.core.entities.Message Messages} to retrieve.
     *
     * @throws java.lang.IllegalArgumentException
     *         The the {@code amount} is less than {@code 1} or greater than {@code 100}.
     * @throws java.lang.IllegalStateException
     *         If no messages have been retrieved by this MessageHistory.
     *
     *
     * @return {@link net.dv8tion.jda.core.requests.RestAction RestAction} -
     *         Type: {@link java.util.List List}{@literal <}{@link net.dv8tion.jda.core.entities.Message Message}{@literal >}
     *         <br>Retrieved Messages are placed in a List and provided in order of most recent to oldest with most recent
     *         starting at index 0. If the list is empty, there were no more messages left to retrieve.
     */
    @CheckReturnValue
    public RestAction<List<Message>> retrieveFuture(int amount) {
        if (amount > 100 || amount < 1)
            throw new IllegalArgumentException(
                    "Message retrieval limit is between 1 and 100 messages. No more, no less. Limit provided: "
                            + amount);

        if (history.isEmpty())
            throw new IllegalStateException(
                    "No messages have been retrieved yet, so there is no message to act as a marker to retrieve more recent messages based on.");

        Route.CompiledRoute route = Route.Messages.GET_MESSAGE_HISTORY.compile(channel.getId())
                .withQueryParams("limit", Integer.toString(amount), "after", String.valueOf(history.firstKey()));
        return new RestAction<List<Message>>(getJDA(), route) {
            @Override
            protected void handleResponse(Response response, Request<List<Message>> request) {
                if (!response.isOk()) {
                    request.onFailure(response);
                    return;
                }

                EntityBuilder builder = api.getEntityBuilder();
                ;
                LinkedList<Message> msgs = new LinkedList<>();
                JSONArray historyJson = response.getArray();

                for (int i = 0; i < historyJson.length(); i++)
                    msgs.add(builder.createMessage(historyJson.getJSONObject(i)));

                for (Iterator<Message> it = msgs.descendingIterator(); it.hasNext();) {
                    Message m = it.next();
                    history.put(0, m.getIdLong(), m);
                }

                request.onSuccess(msgs);
            }
        };
    }

    /**
     * Returns a List of Messages, sorted starting from newest to oldest, of all message that have already been retrieved
     * from Discord with this MessageHistory object using the {@link #retrievePast(int)}, {@link #retrieveFuture(int)}, and
     * {@link net.dv8tion.jda.core.entities.MessageChannel#getHistoryAround(String, int)} methods.
     *
     * @return A List of Messages, sorted newest to oldest.
     */
    public List<Message> getRetrievedHistory() {
        int size = size();
        if (size == 0)
            return Collections.emptyList();
        else if (size == 1)
            return Collections.singletonList(history.getValue(0));
        return Collections.unmodifiableList(new ArrayList<>(history.values()));
    }

    /**
     * Used to get a Message from the set of already retrieved message via it's message Id.
     * <br>If a Message with the provided id has not already been retrieved (thus, doesn't not exist in this MessageHistory
     * object), then this method returns null.
     * <p>
     * <b>Note:</b> This methods is not the same as {@link MessageChannel#getMessageById(String)}, which itself queries
     * Discord. This method is for getting a message that has already been retrieved by this MessageHistory object.
     *
     * @param  id
     *         The id of the requested Message.
     *
     * @throws java.lang.IllegalArgumentException
     *         If the provided {@code id} is null or empty.
     * @throws java.lang.NumberFormatException
     *         If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)}
     *
     * @return Possibly-null Message with the same {@code id} as the one provided.
     */
    public Message getMessageById(String id) {
        return getMessageById(MiscUtil.parseSnowflake(id));
    }

    /**
     * Used to get a Message from the set of already retrieved message via it's message Id.
     * <br>If a Message with the provided id has not already been retrieved (thus, doesn't not exist in this MessageHistory
     * object), then this method returns null.
     * <p>
     * <b>Note:</b> This methods is not the same as {@link MessageChannel#getMessageById(long)}, which itself queries
     * Discord. This method is for getting a message that has already been retrieved by this MessageHistory object.
     *
     * @param  id
     *         The id of the requested Message.
     *
     * @return Possibly-null Message with the same {@code id} as the one provided.
     */
    public Message getMessageById(long id) {
        return history.get(id);
    }
}