
net.dv8tion.jda.internal.interactions.InteractionHookImpl Maven / Gradle / Ivy
/*
* 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.interactions;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.exceptions.InteractionExpiredException;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageDeleteAction;
import net.dv8tion.jda.api.requests.restaction.WebhookMessageRetrieveAction;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.AbstractWebhookClient;
import net.dv8tion.jda.internal.entities.ReceivedMessage;
import net.dv8tion.jda.internal.requests.restaction.*;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.JDALogger;
import javax.annotation.Nonnull;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
public class InteractionHookImpl extends AbstractWebhookClient implements InteractionHook
{
public static final String TIMEOUT_MESSAGE = "Timed out waiting for interaction acknowledgement";
private final DeferrableInteractionImpl interaction;
private final List> readyCallbacks = new LinkedList<>();
private final Future> timeoutHandle;
private final ReentrantLock mutex = new ReentrantLock();
private final String token;
private Exception exception;
private boolean isReady;
private boolean ephemeral;
public InteractionHookImpl(@Nonnull DeferrableInteractionImpl interaction, @Nonnull JDA api)
{
super(api.getSelfUser().getApplicationIdLong(), interaction.getToken(), api);
this.interaction = interaction;
this.token = interaction.getToken();
// 10 second timeout for our failure
this.timeoutHandle = api.getGatewayPool().schedule(() -> this.fail(new TimeoutException(TIMEOUT_MESSAGE)), 10, TimeUnit.SECONDS);
}
public InteractionHookImpl(@Nonnull JDA api, @Nonnull String token)
{
super(api.getSelfUser().getApplicationIdLong(), token, api);
this.interaction = null;
this.token = token;
this.timeoutHandle = null;
this.isReady = true;
}
public boolean ack()
{
return interaction == null || interaction.ack();
}
public boolean isAck()
{
return interaction == null || interaction.isAcknowledged();
}
public void ready()
{
MiscUtil.locked(mutex, () ->
{
if (timeoutHandle != null)
timeoutHandle.cancel(false);
isReady = true;
readyCallbacks.forEach(TriggerRestAction::run);
});
}
public void fail(Exception exception)
{
MiscUtil.locked(mutex, () ->
{
if (!isReady && this.exception == null)
{
this.exception = exception;
if (!readyCallbacks.isEmpty()) // only log this if we even tried any responses
{
if (exception instanceof TimeoutException)
JDALogger.getLog(InteractionHook.class).warn("Up to {} Interaction Followup Messages Timed out! Did you forget to acknowledge the interaction?", readyCallbacks.size());
readyCallbacks.forEach(callback -> callback.fail(exception));
}
}
});
}
private , R> T onReady(T runnable)
{
return MiscUtil.locked(mutex, () ->
{
if (isReady)
runnable.run();
else if (exception != null)
runnable.fail(exception);
else
readyCallbacks.add(runnable);
return runnable;
});
}
@Nonnull
@Override
public InteractionImpl getInteraction()
{
if (interaction == null)
throw new IllegalStateException("Cannot get interaction instance from this webhook.");
return interaction;
}
@Override
public long getExpirationTimestamp()
{
OffsetDateTime creationTime = interaction == null ? OffsetDateTime.now() : interaction.getTimeCreated();
return creationTime.plus(15, ChronoUnit.MINUTES).toEpochSecond() * 1000;
}
@Nonnull
@Override
public InteractionHook setEphemeral(boolean ephemeral)
{
this.ephemeral = ephemeral;
return this;
}
@Nonnull
@Override
public WebhookMessageCreateActionImpl sendRequest()
{
Route.CompiledRoute route = Route.Interactions.CREATE_FOLLOWUP.compile(api.getSelfUser().getApplicationId(), token);
route = route.withQueryParams("wait", "true");
WebhookMessageCreateActionImpl action = new WebhookMessageCreateActionImpl<>(api, route, this::buildMessage).setEphemeral(ephemeral);
action.setCheck(this::checkExpired);
return onReady(action);
}
@Nonnull
@Override
public WebhookMessageEditActionImpl editRequest(String messageId)
{
if (!"@original".equals(messageId))
Checks.isSnowflake(messageId);
Route.CompiledRoute route = Route.Interactions.EDIT_FOLLOWUP.compile(api.getSelfUser().getApplicationId(), token, messageId);
route = route.withQueryParams("wait", "true");
WebhookMessageEditActionImpl action = new WebhookMessageEditActionImpl<>(api, route, this::buildMessage);
action.setCheck(this::checkExpired);
return onReady(action);
}
@Nonnull
@Override
public WebhookMessageDeleteAction deleteMessageById(@Nonnull String messageId)
{
if (!"@original".equals(messageId))
Checks.isSnowflake(messageId);
Route.CompiledRoute route = Route.Interactions.DELETE_FOLLOWUP.compile(api.getSelfUser().getApplicationId(), token, messageId);
WebhookMessageDeleteActionImpl action = new WebhookMessageDeleteActionImpl(api, route);
action.setCheck(this::checkExpired);
return onReady(action);
}
@Nonnull
@Override
public WebhookMessageRetrieveAction retrieveMessageById(@Nonnull String messageId)
{
if (!"@original".equals(messageId))
Checks.isSnowflake(messageId);
Route.CompiledRoute route = Route.Interactions.GET_MESSAGE.compile(api.getSelfUser().getApplicationId(), token, messageId);
WebhookMessageRetrieveActionImpl action = new WebhookMessageRetrieveActionImpl(api, route, (response, request) -> buildMessage(response.getObject()));
action.setCheck(this::checkExpired);
return onReady(action);
}
private boolean checkExpired()
{
if (isExpired())
throw new InteractionExpiredException();
return true;
}
// Creates a message with the resolved channel context from the interaction
// Sometimes we can't resolve the channel and report an unknown type
// Currently known cases where channels can't be resolved:
// - InteractionHook created using id/token factory, has no interaction object to use as context
private Message buildMessage(DataObject json)
{
JDAImpl jda = (JDAImpl) api;
MessageChannel channel = null;
Guild guild = null;
// Try getting context from interaction if available
// This might not be present if the hook was created from id/token instead of an event
if (interaction != null)
{
channel = (MessageChannel) interaction.getChannel();
guild = interaction.getGuild();
}
// Try finding the channel in cache through the id in the message
long channelId = json.getUnsignedLong("channel_id");
if (channel == null)
channel = api.getChannelById(MessageChannel.class, channelId);
// Then build the message with the information we have
ReceivedMessage message = jda.getEntityBuilder().createMessageBestEffort(json, channel, guild);
return message.withHook(this);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy