All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dv8tion.jda.internal.entities.ReceivedMessage Maven / Gradle / Ivy

Go to download

Java wrapper for the popular chat & VOIP service: Discord https://discord.com

There is a newer version: 5.1.0
Show newest version
/*
 * 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.internal.entities;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel;
import net.dv8tion.jda.api.entities.channel.concrete.Category;
import net.dv8tion.jda.api.entities.channel.concrete.NewsChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.channel.unions.GuildMessageChannelUnion;
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
import net.dv8tion.jda.api.entities.sticker.StickerItem;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.exceptions.PermissionException;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
import net.dv8tion.jda.api.requests.restaction.MessageEditAction;
import net.dv8tion.jda.api.requests.restaction.ThreadChannelAction;
import net.dv8tion.jda.api.requests.restaction.pagination.ReactionPaginationAction;
import net.dv8tion.jda.api.utils.AttachedFile;
import net.dv8tion.jda.api.utils.MarkdownSanitizer;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ReceivedMessage extends AbstractMessage
{
    public static boolean didContentIntentWarning = false;
    private final Object mutex = new Object();

    protected final JDAImpl api;
    protected final long id;
    protected final MessageType type;
    protected final MessageChannel channel;
    protected final MessageReference messageReference;
    protected final boolean fromWebhook;
    protected final long applicationId;
    protected final boolean pinned;
    protected final User author;
    protected final Member member;
    protected final MessageActivity activity;
    protected final OffsetDateTime editedTime;
    protected final Mentions mentions;
    protected final List reactions;
    protected final List attachments;
    protected final List embeds;
    protected final List stickers;
    protected final List components;
    protected final int flags;
    protected final Message.Interaction interaction;
    protected final ThreadChannel startedThread;
    protected final int position;

    // LAZY EVALUATED
    protected String altContent = null;
    protected String strippedContent = null;

    protected List invites = null;

    public ReceivedMessage(
            long id, MessageChannel channel, MessageType type, MessageReference messageReference,
            boolean fromWebhook, long applicationId, boolean  tts, boolean pinned,
            String content, String nonce, User author, Member member, MessageActivity activity, OffsetDateTime editTime,
            Mentions mentions, List reactions, List attachments, List embeds,
            List stickers, List components,
            int flags, Message.Interaction interaction, ThreadChannel startedThread, int position)
    {
        super(content, nonce, tts);
        this.id = id;
        this.channel = channel;
        this.messageReference = messageReference;
        this.type = type;
        this.api = (JDAImpl) channel.getJDA();
        this.fromWebhook = fromWebhook;
        this.applicationId = applicationId;
        this.pinned = pinned;
        this.author = author;
        this.member = member;
        this.activity = activity;
        this.editedTime = editTime;
        this.mentions = mentions;
        this.reactions = Collections.unmodifiableList(reactions);
        this.attachments = Collections.unmodifiableList(attachments);
        this.embeds = Collections.unmodifiableList(embeds);
        this.stickers = Collections.unmodifiableList(stickers);
        this.components = Collections.unmodifiableList(components);
        this.flags = flags;
        this.interaction = interaction;
        this.startedThread = startedThread;
        this.position = position;
    }

    private void checkIntent()
    {
        // Checks whether access to content is limited and the message content intent is not enabled
        if (!didContentIntentWarning && !api.isIntent(GatewayIntent.MESSAGE_CONTENT))
        {
            SelfUser selfUser = api.getSelfUser();
            if (!Objects.equals(selfUser, author) && !mentions.getUsers().contains(selfUser) && isFromGuild())
            {
                didContentIntentWarning = true;
                JDAImpl.LOG.warn(
                    "Attempting to access message content without GatewayIntent.MESSAGE_CONTENT.\n" +
                    "Discord now requires to explicitly enable access to this using the MESSAGE_CONTENT intent.\n" +
                    "Useful resources to learn more:\n" +
                    "\t- https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Privileged-Intent-FAQ\n" +
                    "\t- https://jda.wiki/using-jda/gateway-intents-and-member-cache-policy/\n" +
                    "\t- https://jda.wiki/using-jda/troubleshooting/#cannot-get-message-content-attempting-to-access-message-content-without-gatewayintent\n" +
                    "Or suppress this warning if this is intentional with Message.suppressContentIntentWarning()"
                );
            }
        }
    }

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

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

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

    @Nonnull
    @Override
    public RestAction pin()
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot pin ephemeral messages.");
        
        return channel.pinMessageById(getId());
    }

    @Nonnull
    @Override
    public RestAction unpin()
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot unpin ephemeral messages.");
        
        return channel.unpinMessageById(getId());
    }

    @Nonnull
    @Override
    public RestAction addReaction(@Nonnull Emoji emoji)
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot add reactions to ephemeral messages.");
        
        Checks.notNull(emoji, "Emoji");

        boolean missingReaction = reactions.stream()
                   .map(MessageReaction::getEmoji)
                   .noneMatch(r -> r.getAsReactionCode().equals(emoji.getAsReactionCode()));

        if (missingReaction && emoji instanceof RichCustomEmoji)
        {
            Checks.check(((RichCustomEmoji) emoji).canInteract(getJDA().getSelfUser(), channel),
                         "Cannot react with the provided emoji because it is not available in the current channel.");
        }
        return channel.addReactionById(getId(), emoji);
    }

    @Nonnull
    @Override
    public RestAction clearReactions()
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot clear reactions from ephemeral messages.");
        if (!isFromGuild())
            throw new IllegalStateException("Cannot clear reactions from a message in a Group or PrivateChannel.");
        return getGuildChannel().clearReactionsById(getId());
    }

    @Nonnull
    @Override
    public RestAction clearReactions(@Nonnull Emoji emoji)
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot clear reactions from ephemeral messages.");
        if (!isFromGuild())
            throw new IllegalStateException("Cannot clear reactions from a message in a Group or PrivateChannel.");
        return getGuildChannel().clearReactionsById(getId(), emoji);
    }

    @Nonnull
    @Override
    public RestAction removeReaction(@Nonnull Emoji emoji)
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot remove reactions from ephemeral messages.");
        
        return channel.removeReactionById(getId(), emoji);
    }

    @Nonnull
    @Override
    public RestAction removeReaction(@Nonnull Emoji emoji, @Nonnull User user)
    {
        Checks.notNull(user, "User");  // to prevent NPEs
        if (isEphemeral())
            throw new IllegalStateException("Cannot remove reactions from ephemeral messages.");
        // check if the passed user is the SelfUser, then the ChannelType doesn't matter and
        // we can safely remove that
        if (user.equals(getJDA().getSelfUser()))
            return channel.removeReactionById(getIdLong(), emoji);

        if (!isFromGuild())
            throw new IllegalStateException("Cannot remove reactions of others from a message in a Group or PrivateChannel.");
        return getGuildChannel().removeReactionById(getIdLong(), emoji, user);
    }

    @Nonnull
    @Override
    public ReactionPaginationAction retrieveReactionUsers(@Nonnull Emoji emoji)
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot retrieve reactions on ephemeral messages.");
        
        return channel.retrieveReactionUsersById(id, emoji);
    }

    @Nullable
    @Override
    public MessageReaction getReaction(@Nonnull Emoji emoji)
    {
        Checks.notNull(emoji, "Emoji");
        String code = emoji.getAsReactionCode();
        return this.reactions.stream()
                .filter(r -> code.equals(r.getEmoji().getAsReactionCode()))
                .findFirst().orElse(null);
    }

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

    @Nullable
    @Override
    public Interaction getInteraction()
    {
        return interaction;
    }

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

    @Nonnull
    @Override
    public String getJumpUrl()
    {
        return Helpers.format(Message.JUMP_URL, isFromGuild() ? getGuild().getId() : "@me", getChannel().getId(), getId());
    }

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

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

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

    @Override
    public Member getMember()
    {
        return member;
    }

    @Override
    public int getApproximatePosition()
    {
        if (!getChannelType().isThread())
            throw new IllegalStateException("This message was not sent in a thread.");

        return position;
    }

    @Nonnull
    @Override
    public String getContentStripped()
    {
        if (strippedContent != null)
            return strippedContent;
        synchronized (mutex)
        {
            if (strippedContent != null)
                return strippedContent;
            return strippedContent = MarkdownSanitizer.sanitize(getContentDisplay());
        }
    }

    @Nonnull
    @Override
    public String getContentDisplay()
    {
        if (altContent != null)
            return altContent;

        synchronized (mutex)
        {
            if (altContent != null)
                return altContent;
            String tmp = getContentRaw();
            for (User user : mentions.getUsers())
            {
                String name;
                if (isFromGuild() && getGuild().isMember(user))
                    name = getGuild().getMember(user).getEffectiveName();
                else
                    name = user.getName();
                tmp = tmp.replaceAll("<@!?" + Pattern.quote(user.getId()) + '>', '@' + Matcher.quoteReplacement(name));
            }
            for (CustomEmoji emoji : mentions.getCustomEmojis())
            {
                tmp = tmp.replace(emoji.getAsMention(), ":" + emoji.getName() + ":");
            }
            for (GuildChannel mentionedChannel : mentions.getChannels())
            {
                tmp = tmp.replace(mentionedChannel.getAsMention(), '#' + mentionedChannel.getName());
            }
            for (Role mentionedRole : mentions.getRoles())
            {
                tmp = tmp.replace(mentionedRole.getAsMention(), '@' + mentionedRole.getName());
            }
            return altContent = tmp;
        }
    }

    @Nonnull
    @Override
    public String getContentRaw()
    {
        checkIntent();
        return content;
    }

    @Nonnull
    @Override
    public List getInvites()
    {
        if (invites != null)
            return invites;
        synchronized (mutex)
        {
            if (invites != null)
                return invites;
            invites = new ArrayList<>();
            Matcher m = INVITE_PATTERN.matcher(getContentRaw());
            while (m.find())
                invites.add(m.group(1));
            return invites = Collections.unmodifiableList(invites);
        }
    }

    @Override
    public String getNonce()
    {
        return nonce;
    }

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

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

    @Nonnull
    @Override
    public MessageChannelUnion getChannel()
    {
        return (MessageChannelUnion) channel;
    }

    @Nonnull
    @Override
    public GuildMessageChannelUnion getGuildChannel()
    {
        if (!isFromGuild())
            throw new IllegalStateException("This message was not sent in a guild.");
        return (GuildMessageChannelUnion) channel;
    }

    @Override
    public Category getCategory()
    {
        Channel channel = this.channel;
        if (channel instanceof ThreadChannel)
            channel = ((ThreadChannel) channel).getParentChannel();

        return channel instanceof ICategorizableChannel
            ? ((ICategorizableChannel) channel).getParentCategory()
            : null;
    }

    @Nonnull
    @Override
    public Guild getGuild()
    {
        return getGuildChannel().getGuild();
    }

    @Nonnull
    @Override
    public List getAttachments()
    {
        checkIntent();
        return attachments;
    }

    @Nonnull
    @Override
    public List getEmbeds()
    {
        checkIntent();
        return embeds;
    }

    @Nonnull
    @Override
    public List getComponents()
    {
        checkIntent();
        return components;
    }

    @Nonnull
    @Override
    public Mentions getMentions()
    {
        return mentions;
    }

    @Nonnull
    @Override
    public List getReactions()
    {
        return reactions;
    }

    @Nonnull
    @Override
    public List getStickers()
    {
        return this.stickers;
    }

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

    @Override
    public long getApplicationIdLong()
    {
        return applicationId;
    }

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

    @Nullable
    @Override
    public MessageActivity getActivity()
    {
        return activity;
    }

    @Nonnull
    @Override
    public MessageEditAction editMessage(@Nonnull CharSequence newContent)
    {
        checkUser();
        return channel.editMessageById(getId(), newContent);
    }

    @Nonnull
    @Override
    public MessageEditAction editMessageEmbeds(@Nonnull Collection embeds)
    {
        checkUser();
        return channel.editMessageEmbedsById(getId(), embeds);
    }

    @Nonnull
    @Override
    public MessageEditAction editMessageComponents(@Nonnull Collection components)
    {
        checkUser();
        return channel.editMessageComponentsById(getId(), components);
    }

    @Nonnull
    @Override
    public MessageEditAction editMessageFormat(@Nonnull String format, @Nonnull Object... args)
    {
        checkUser();
        return channel.editMessageFormatById(getId(), format, args);
    }

    @Nonnull
    @Override
    public MessageEditAction editMessageAttachments(@Nonnull Collection attachments)
    {
        checkUser();
        return channel.editMessageAttachmentsById(getId(), attachments);
    }

    @Nonnull
    @Override
    public MessageEditAction editMessage(@Nonnull MessageEditData newContent)
    {
        checkUser();
        return channel.editMessageById(getId(), newContent);
    }

    private void checkUser()
    {
        if (!getJDA().getSelfUser().equals(getAuthor()))
            throw new IllegalStateException("Attempted to update message that was not sent by this account. You cannot modify other User's messages!");
    }

    @Nonnull
    @Override
    public AuditableRestAction delete()
    {
        if (isEphemeral())
        {
            throw new IllegalStateException("Cannot delete ephemeral messages.");
        }
        if (!getJDA().getSelfUser().equals(getAuthor()))
        {
            if (isFromType(ChannelType.PRIVATE))
                throw new IllegalStateException("Cannot delete another User's messages in a PrivateChannel.");

            GuildMessageChannel gChan = getGuildChannel();
            Member sMember = getGuild().getSelfMember();
            Checks.checkAccess(sMember, gChan);
            if (!sMember.hasPermission(gChan, Permission.MESSAGE_MANAGE))
                throw new InsufficientPermissionException(gChan, Permission.MESSAGE_MANAGE);
        }
        if (!type.canDelete())
            throw new IllegalStateException("Cannot delete messages of type " + type);
        return channel.deleteMessageById(getIdLong());
    }

    @Nonnull
    @Override
    public AuditableRestAction suppressEmbeds(boolean suppressed)
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot suppress embeds on ephemeral messages.");
        
        if (!getJDA().getSelfUser().equals(getAuthor()))
        {
            if (isFromType(ChannelType.PRIVATE))
                throw new PermissionException("Cannot suppress embeds of others in a PrivateChannel.");

            GuildMessageChannel gChan = getGuildChannel();
            if (!getGuild().getSelfMember().hasPermission(gChan, Permission.MESSAGE_MANAGE))
                throw new InsufficientPermissionException(gChan, Permission.MESSAGE_MANAGE);
        }
        JDAImpl jda = (JDAImpl) getJDA();
        Route.CompiledRoute route = Route.Messages.EDIT_MESSAGE.compile(getChannel().getId(), getId());
        int newFlags = flags;
        int suppressionValue = MessageFlag.EMBEDS_SUPPRESSED.getValue();
        if (suppressed)
            newFlags |= suppressionValue;
        else
            newFlags &= ~suppressionValue;
        return new AuditableRestActionImpl<>(jda, route, DataObject.empty().put("flags", newFlags));
    }

    @Nonnull
    @Override
    public RestAction crosspost()
    {
        if (isEphemeral())
            throw new IllegalStateException("Cannot crosspost ephemeral messages.");
        
        if (getFlags().contains(MessageFlag.CROSSPOSTED))
            return new CompletedRestAction<>(getJDA(), this);

        if (!(getChannel() instanceof NewsChannel))
            throw new IllegalStateException("This message was not sent in a news channel");

        NewsChannel newsChannel = (NewsChannel) getChannel();
        Checks.checkAccess(getGuild().getSelfMember(), newsChannel);
        if (!getAuthor().equals(getJDA().getSelfUser()) && !getGuild().getSelfMember().hasPermission(newsChannel, Permission.MESSAGE_MANAGE))
            throw new InsufficientPermissionException(newsChannel, Permission.MESSAGE_MANAGE);
        return newsChannel.crosspostMessageById(getId());
    }

    @Override
    public boolean isSuppressedEmbeds()
    {
        return (this.flags & MessageFlag.EMBEDS_SUPPRESSED.getValue()) > 0;
    }

    @Nonnull
    @Override
    public EnumSet getFlags()
    {
        return MessageFlag.fromBitField(flags);
    }

    @Override
    public long getFlagsRaw()
    {
        return flags;
    }

    @Override
    public boolean isEphemeral()
    {
        return (this.flags & MessageFlag.EPHEMERAL.getValue()) != 0;
    }

    @Override
    public boolean isSuppressedNotifications()
    {
        return (this.flags & MessageFlag.NOTIFICATIONS_SUPPRESSED.getValue()) != 0;
    }

    @Nullable
    @Override
    public ThreadChannel getStartedThread()
    {
        return this.startedThread;
    }

    @Override
    public ThreadChannelAction createThreadChannel(String name)
    {
        return getGuildChannel().asThreadContainer().createThreadChannel(name, this.getIdLong());
    }

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

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

    @Override
    @SuppressWarnings("deprecation")
    public String toString()
    {
        return new EntityString(this)
                .addMetadata("author", author.getDiscriminator().equals("0000") ? author.getName() : author.getAsTag())
                .addMetadata("content", String.format("%.20s ...", this))
                .toString();
    }

    @Override
    protected void unsupported()
    {
        throw new UnsupportedOperationException("This operation is not supported on received messages!");
    }

    @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 ? getContentRaw() : getContentDisplay();

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

        appendFormat(formatter, width, precision, leftJustified, out);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy