net.dv8tion.jda.core.entities.impl.MessageImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.dv8tion.jda.core.entities.impl.MessageImpl.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.impl;

import net.dv8tion.jda.client.entities.Group;
import net.dv8tion.jda.core.JDA;
import net.dv8tion.jda.core.MessageBuilder;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.*;
import net.dv8tion.jda.core.exceptions.PermissionException;
import net.dv8tion.jda.core.requests.RestAction;
import net.dv8tion.jda.core.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.core.utils.Checks;
import net.dv8tion.jda.core.utils.Helpers;
import org.json.JSONObject;

import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MessageImpl implements Message {
    private static final Pattern EMOTE_PATTERN = Pattern.compile("<:([^:]+):([0-9]+)>");

    private final JDAImpl api;
    private final long id;
    private final MessageType type;
    private final MessageChannel channel;
    private final boolean fromWebhook;
    private boolean mentionsEveryone = false;
    private boolean isTTS = false;
    private boolean pinned;
    private String content;
    private String subContent = null;
    private String strippedContent = null;
    private User author;
    private OffsetDateTime time;
    private OffsetDateTime editedTime = null;
    private List<User> mentionedUsers = new LinkedList<>();
    private List<TextChannel> mentionedChannels = new LinkedList<>();
    private List<Role> mentionedRoles = new LinkedList<>();
    private List<Attachment> attachments = new LinkedList<>();
    private List<MessageEmbed> embeds = new LinkedList<>();
    private List<Emote> emotes = null;
    private List<MessageReaction> reactions = new LinkedList<>();

    public MessageImpl(long id, MessageChannel channel, boolean fromWebhook) {
        this(id, channel, fromWebhook, MessageType.DEFAULT);
    }

    public MessageImpl(long id, MessageChannel channel, boolean fromWebhook, MessageType type) {
        this.id = id;
        this.channel = channel;
        this.api = (channel != null) ? (JDAImpl) channel.getJDA() : null;
        this.fromWebhook = fromWebhook;
        this.type = type;
    }

    @Override
    public JDA getJDA() {
        return api;
    }

    @Override
    public boolean isPinned() {
        return pinned;
    }

    @Override
    public RestAction<Void> pin() {
        return channel.pinMessageById(getIdLong());
    }

    @Override
    public RestAction<Void> unpin() {
        return channel.unpinMessageById(getIdLong());
    }

    @Override
    public RestAction<Void> addReaction(Emote emote) {
        Checks.notNull(emote, "Emote");

        MessageReaction reaction = reactions.parallelStream()
                .filter(r -> Objects.equals(r.getEmote().getId(), emote.getId())).findFirst().orElse(null);

        if (reaction == null) {
            checkFake(emote, "Emote");
            if (!emote.canInteract(api.getSelfUser(), channel))
                throw new IllegalArgumentException(
                        "Cannot react with the provided emote because it is not available in the current channel.");
        } else if (reaction.isSelf()) {
            return new RestAction.EmptyRestAction<>(getJDA(), null);
        }

        return channel.addReactionById(getIdLong(), emote);
    }

    @Override
    public RestAction<Void> addReaction(String unicode) {
        Checks.notEmpty(unicode, "Provided Unicode");

        MessageReaction reaction = reactions.parallelStream()
                .filter(r -> Objects.equals(r.getEmote().getName(), unicode)).findFirst().orElse(null);

        if (reaction != null && reaction.isSelf())
            return new RestAction.EmptyRestAction<>(getJDA(), null);

        return channel.addReactionById(getIdLong(), unicode);
    }

    @Override
    public RestAction<Void> clearReactions() {
        if (!isFromType(ChannelType.TEXT))
            throw new IllegalStateException("Cannot clear reactions from a message in a Group or PrivateChannel.");
        return getTextChannel().clearReactionsById(getId());
    }

    @Override
    public MessageType getType() {
        return type;
    }

    @Override
    public long getIdLong() {
        return id;
    }

    @Override
    public List<User> getMentionedUsers() {
        return Collections.unmodifiableList(mentionedUsers);
    }

    @Override
    public boolean isMentioned(User user) {
        return mentionsEveryone() || mentionedUsers.contains(user);
    }

    @Override
    public List<TextChannel> getMentionedChannels() {
        return Collections.unmodifiableList(mentionedChannels);
    }

    @Override
    public List<Role> getMentionedRoles() {
        return Collections.unmodifiableList(mentionedRoles);
    }

    @Override
    public boolean mentionsEveryone() {
        return mentionsEveryone;
    }

    @Override
    public boolean isEdited() {
        return editedTime != null;
    }

    @Override
    public OffsetDateTime getEditedTime() {
        return editedTime;
    }

    @Override
    public User getAuthor() {
        return author;
    }

    @Override
    public Member getMember() {
        return isFromType(ChannelType.TEXT) ? getGuild().getMember(getAuthor()) : null;
    }

    @Override
    public synchronized String getStrippedContent() {
        if (strippedContent == null) {
            String tmp = getContent();
            //all the formatting keys to keep track of
            String[] keys = new String[] { "*", "_", "`", "~~" };

            //find all tokens (formatting strings described above)
            TreeSet<FormatToken> tokens = new TreeSet<>((t1, t2) -> Integer.compare(t1.start, t2.start));
            for (String key : keys) {
                Matcher matcher = Pattern.compile(Pattern.quote(key)).matcher(tmp);
                while (matcher.find()) {
                    tokens.add(new FormatToken(key, matcher.start()));
                }
            }

            //iterate over all tokens, find all matching pairs, and add them to the list toRemove
            Stack<FormatToken> stack = new Stack<>();
            List<FormatToken> toRemove = new ArrayList<>();
            boolean inBlock = false;
            for (FormatToken token : tokens) {
                if (stack.empty() || !stack.peek().format.equals(token.format)
                        || stack.peek().start + token.format.length() == token.start) {
                    //we are at opening tag
                    if (!inBlock) {
                        //we are outside of block -> handle normally
                        if (token.format.equals("`")) {
                            //block start... invalidate all previous tags
                            stack.clear();
                            inBlock = true;
                        }
                        stack.push(token);
                    } else if (token.format.equals("`")) {
                        //we are inside of a block -> handle only block tag
                        stack.push(token);
                    }
                } else if (!stack.empty()) {
                    //we found a matching close-tag
                    toRemove.add(stack.pop());
                    toRemove.add(token);
                    if (token.format.equals("`") && stack.empty()) {
                        //close tag closed the block
                        inBlock = false;
                    }
                }
            }

            //sort tags to remove by their start-index and iteratively build the remaining string
            Collections.sort(toRemove, (t1, t2) -> Integer.compare(t1.start, t2.start));
            StringBuilder out = new StringBuilder();
            int currIndex = 0;
            for (FormatToken formatToken : toRemove) {
                if (currIndex < formatToken.start) {
                    out.append(tmp.substring(currIndex, formatToken.start));
                }
                currIndex = formatToken.start + formatToken.format.length();
            }
            if (currIndex < tmp.length()) {
                out.append(tmp.substring(currIndex));
            }
            //return the stripped text, escape all remaining formatting characters (did not have matching open/close before or were left/right of block
            strippedContent = out.toString().replace("*", "\\*").replace("_", "\\_").replace("~", "\\~");
        }
        return strippedContent;
    }

    @Override
    public synchronized String getContent() {
        if (subContent == null) {
            String tmp = content;
            for (User user : mentionedUsers) {
                if (isFromType(ChannelType.PRIVATE) || isFromType(ChannelType.GROUP)) {
                    tmp = tmp.replace("<@" + user.getId() + '>', '@' + user.getName())
                            .replace("<@!" + user.getId() + '>', '@' + user.getName());
                } else {
                    String name;
                    if (getGuild().isMember(user))
                        name = getGuild().getMember(user).getEffectiveName();
                    else
                        name = user.getName();
                    tmp = tmp.replace("<@" + user.getId() + '>', '@' + name).replace("<@!" + user.getId() + '>',
                            '@' + name);
                }
            }
            for (Emote emote : getEmotes()) {
                tmp = tmp.replace(emote.getAsMention(), ":" + emote.getName() + ":");
            }
            for (TextChannel mentionedChannel : mentionedChannels) {
                tmp = tmp.replace("<#" + mentionedChannel.getId() + '>', '#' + mentionedChannel.getName());
            }
            for (Role mentionedRole : mentionedRoles) {
                tmp = tmp.replace("<@&" + mentionedRole.getId() + '>', '@' + mentionedRole.getName());
            }
            subContent = tmp;
        }
        return subContent;
    }

    @Override
    public String getRawContent() {
        return content;
    }

    @Override
    public boolean isFromType(ChannelType type) {
        return getChannelType() == type;
    }

    @Override
    public ChannelType getChannelType() {
        return channel.getType();
    }

    @Override
    public MessageChannel getChannel() {
        return channel;
    }

    @Override
    public PrivateChannel getPrivateChannel() {
        return isFromType(ChannelType.PRIVATE) ? (PrivateChannel) channel : null;
    }

    @Override
    public Group getGroup() {
        return isFromType(ChannelType.GROUP) ? (Group) channel : null;
    }

    @Override
    public TextChannel getTextChannel() {
        return isFromType(ChannelType.TEXT) ? (TextChannel) channel : null;
    }

    @Override
    public Guild getGuild() {
        return isFromType(ChannelType.TEXT) ? getTextChannel().getGuild() : null;
    }

    @Override
    public List<Attachment> getAttachments() {
        return Collections.unmodifiableList(attachments);
    }

    @Override
    public List<MessageEmbed> getEmbeds() {
        return Collections.unmodifiableList(embeds);
    }

    @Override
    public synchronized List<Emote> getEmotes() {
        if (this.emotes == null) {
            emotes = new LinkedList<>();
            Matcher matcher = EMOTE_PATTERN.matcher(getRawContent());
            while (matcher.find()) {
                final String emoteIdString = matcher.group(2);
                final long emoteId = Long.parseLong(emoteIdString);
                String emoteName = matcher.group(1);
                Emote emote = api.getEmoteById(emoteIdString);
                if (emote == null)
                    emote = new EmoteImpl(emoteId, api).setName(emoteName);
                emotes.add(emote);
            }
            emotes = Collections.unmodifiableList(emotes);
        }
        return emotes;
    }

    @Override
    public List<MessageReaction> getReactions() {
        return Collections.unmodifiableList(new LinkedList<>(reactions));
    }

    @Override
    public boolean isWebhookMessage() {
        return fromWebhook;
    }

    @Override
    public boolean isTTS() {
        return isTTS;
    }

    @Override
    public RestAction<Message> editMessage(String newContent) {
        return editMessage(new MessageBuilder().append(newContent).build());
    }

    @Override
    public RestAction<Message> editMessage(MessageEmbed newContent) {
        return editMessage(new MessageBuilder().setEmbed(newContent).build());
    }

    @Override
    public RestAction<Message> editMessageFormat(String format, Object... args) {
        Checks.notBlank(format, "Format String");
        return editMessage(new MessageBuilder().appendFormat(format, args).build());
    }

    @Override
    public RestAction<Message> editMessage(Message newContent) {
        if (!api.getSelfUser().equals(getAuthor()))
            throw new IllegalStateException(
                    "Attempted to update message that was not sent by this account. You cannot modify other User's messages!");

        return getChannel().editMessageById(getIdLong(), newContent);
    }

    @Override
    public AuditableRestAction<Void> delete() {
        if (!getJDA().getSelfUser().equals(getAuthor())) {
            if (isFromType(ChannelType.PRIVATE) || isFromType(ChannelType.GROUP))
                throw new IllegalStateException(
                        "Cannot delete another User's messages in a Group or PrivateChannel.");
            else if (!getGuild().getSelfMember().hasPermission((TextChannel) getChannel(),
                    Permission.MESSAGE_MANAGE))
                throw new PermissionException(Permission.MESSAGE_MANAGE);
        }
        return channel.deleteMessageById(getIdLong());
    }

    public MessageImpl setPinned(boolean pinned) {
        this.pinned = pinned;
        return this;
    }

    public MessageImpl setMentionedUsers(List<User> mentionedUsers) {
        this.mentionedUsers = mentionedUsers;
        return this;
    }

    public MessageImpl setMentionedChannels(List<TextChannel> mentionedChannels) {
        this.mentionedChannels = mentionedChannels;
        return this;
    }

    public MessageImpl setMentionedRoles(List<Role> mentionedRoles) {
        this.mentionedRoles = mentionedRoles;
        return this;
    }

    public MessageImpl setMentionsEveryone(boolean mentionsEveryone) {
        this.mentionsEveryone = mentionsEveryone;
        return this;
    }

    public MessageImpl setTTS(boolean TTS) {
        isTTS = TTS;
        return this;
    }

    public MessageImpl setTime(OffsetDateTime time) {
        this.time = time;
        return this;
    }

    public MessageImpl setEditedTime(OffsetDateTime editedTime) {
        this.editedTime = editedTime;
        return this;
    }

    public MessageImpl setAuthor(User author) {
        this.author = author;
        return this;
    }

    public MessageImpl setContent(String content) {
        this.content = content;
        return this;
    }

    public MessageImpl setAttachments(List<Attachment> attachments) {
        this.attachments = attachments;
        return this;
    }

    public MessageImpl setEmbeds(List<MessageEmbed> embeds) {
        this.embeds = embeds;
        return this;
    }

    public MessageImpl setReactions(List<MessageReaction> reactions) {
        this.reactions = reactions;
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof MessageImpl))
            return false;
        MessageImpl oMsg = (MessageImpl) o;
        return this == oMsg || this.id == oMsg.id;
    }

    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }

    @Override
    public String toString() {
        return author != null ? String.format("M:%#s:%.20s(%s)", author, this, getId())
                : String.format("M:%.20s", this); // this message was made using MessageBuilder
    }

    public JSONObject toJSONObject() {
        JSONObject obj = new JSONObject();
        obj.put("content", content);
        obj.put("tts", isTTS);
        if (!embeds.isEmpty())
            obj.put("embed", ((MessageEmbedImpl) embeds.get(0)).toJSONObject());
        return obj;
    }

    private void checkPermission(Permission permission) {
        if (channel.getType() == ChannelType.TEXT) {
            Channel location = (Channel) channel;
            if (!location.getGuild().getSelfMember().hasPermission(location, permission))
                throw new PermissionException(permission);
        }
    }

    private void checkFake(IFakeable o, String name) {
        if (o.isFake())
            throw new IllegalArgumentException("We are unable to use a fake " + name + " in this situation!");
    }

    @Override
    public void formatTo(Formatter formatter, int flags, int width, int precision) {
        boolean upper = (flags & FormattableFlags.UPPERCASE) == FormattableFlags.UPPERCASE;
        boolean leftJustified = (flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags.LEFT_JUSTIFY;
        boolean alt = (flags & FormattableFlags.ALTERNATE) == FormattableFlags.ALTERNATE;

        String out = alt ? getRawContent() : getContent();

        if (upper)
            out = out.toUpperCase(formatter.locale());

        try {
            Appendable appendable = formatter.out();
            if (precision > -1 && out.length() > precision) {
                appendable.append(Helpers.truncate(out, precision - 3)).append("...");
                return;
            }

            if (leftJustified)
                appendable.append(Helpers.rightPad(out, width));
            else
                appendable.append(Helpers.leftPad(out, width));
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }

    private static class FormatToken {
        public final String format;
        public final int start;

        public FormatToken(String format, int start) {
            this.format = format;
            this.start = start;
        }
    }
}