Added D4J, configured logging. Also basic event handler.
parent
6bc355b9ac
commit
dd30fbecdc
@ -1,2 +1,10 @@
|
|||||||
# Bot display name
|
# Bot display name
|
||||||
general.bot_name = DCF-enabled Bot
|
general.bot_name = DCF Bot
|
||||||
|
|
||||||
|
# Initial connection token...
|
||||||
|
discord.token = tokenGoesHere
|
||||||
|
|
||||||
|
# ... or username/password combination if you don't use bot mode
|
||||||
|
# comment out discord.token if using us/pw tuple
|
||||||
|
#discord.username = email
|
||||||
|
#discord.password = superSecretPassword
|
@ -0,0 +1,80 @@
|
|||||||
|
package net.pingex.dcf.core;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import sx.blah.discord.api.EventSubscriber;
|
||||||
|
import sx.blah.discord.handle.impl.events.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very core events handler, should be running independently from the whole events manager thingy.
|
||||||
|
* It handles things like auto reconnect after gateway disconnection, etc.
|
||||||
|
*/
|
||||||
|
public class CoreEventsHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Logger
|
||||||
|
*/
|
||||||
|
private static Logger LOGGER = LogManager.getLogger(CoreEventsHandler.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance
|
||||||
|
*/
|
||||||
|
private static CoreEventsHandler INSTANCE = new CoreEventsHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current instance
|
||||||
|
*/
|
||||||
|
public static CoreEventsHandler getInstance()
|
||||||
|
{
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic constructor, enforcing singleton
|
||||||
|
*/
|
||||||
|
private CoreEventsHandler()
|
||||||
|
{}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onReady(ReadyEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.info("Connection with user #" + event.getClient().getOurUser().getID() + " ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onDiscordDisconnected(DiscordDisconnectedEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Discord connection with user #" + event.getClient().getOurUser().getID() + "lost (" + event.getReason().toString() + "). Reconnecting");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onMessageSend(MessageSendEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.trace("Sent message to channel #" + event.getMessage().getChannel().getID() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onMention(MentionEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.trace("Received mention from channel #" + event.getMessage().getChannel().getID() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onMessageReceived(MessageReceivedEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.trace("Received message from channel #" + event.getMessage().getChannel().getID() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onGuildUnavailable(GuildUnavailableEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Guild #" + event.getGuild().getID() + " is unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventSubscriber
|
||||||
|
public void onGuildCreate(GuildCreateEvent event)
|
||||||
|
{
|
||||||
|
LOGGER.info("Joined guild #" + event.getGuild().getID() + ".");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package net.pingex.dcf.core;
|
||||||
|
|
||||||
|
import net.pingex.dcf.util.DiscordInteractionsUtil;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import sx.blah.discord.api.ClientBuilder;
|
||||||
|
import sx.blah.discord.api.IDiscordClient;
|
||||||
|
import sx.blah.discord.util.DiscordException;
|
||||||
|
import sx.blah.discord.util.HTTP429Exception;
|
||||||
|
import sx.blah.discord.util.MessageBuilder;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage of all active connection to Discord gateway
|
||||||
|
*/
|
||||||
|
public class GatewayConnectionsManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Main datastore
|
||||||
|
*/
|
||||||
|
private Set<IDiscordClient> connectionsDatastore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of registered listeners for each connection, as we can't access registered listeners in IDC dispatcher.
|
||||||
|
*/
|
||||||
|
private Map<IDiscordClient, Set<Object>> registeredListeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton unique instance
|
||||||
|
*/
|
||||||
|
private static final GatewayConnectionsManager INSTANCE = new GatewayConnectionsManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger
|
||||||
|
*/
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(GatewayConnectionsManager.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
*/
|
||||||
|
public static GatewayConnectionsManager getInstance()
|
||||||
|
{
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor, to enforce singleton
|
||||||
|
*/
|
||||||
|
private GatewayConnectionsManager()
|
||||||
|
{
|
||||||
|
connectionsDatastore = new HashSet<>();
|
||||||
|
registeredListeners = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a connection and login automatically
|
||||||
|
* @param builder Filled and builder ready for log in
|
||||||
|
*/
|
||||||
|
public void registerConnection(ClientBuilder builder)
|
||||||
|
{
|
||||||
|
LOGGER.info("Registering new connection");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IDiscordClient builtConnection = builder.login();
|
||||||
|
connectionsDatastore.add(builtConnection);
|
||||||
|
builtConnection.getDispatcher().registerListener(CoreEventsHandler.getInstance()); // Register the core event handler independently from the events package
|
||||||
|
//updateListeners(builtConnection, null); // TODO: EventRegistry
|
||||||
|
}
|
||||||
|
catch (DiscordException e)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Failed to login to Discord.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout and unregister specified connection
|
||||||
|
* @param client Target connection
|
||||||
|
*/
|
||||||
|
public void unregisterConnection(IDiscordClient client)
|
||||||
|
{
|
||||||
|
LOGGER.info("Unregistering connection with user #" + client.getOurUser().getID());
|
||||||
|
|
||||||
|
DiscordInteractionsUtil.disconnect(client);
|
||||||
|
connectionsDatastore.remove(client);
|
||||||
|
registeredListeners.remove(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update listeners for target connection
|
||||||
|
* @param target Target conection, must be registered
|
||||||
|
* @param refListeners Reference listeners list
|
||||||
|
*/
|
||||||
|
public void updateListeners(IDiscordClient target, Set<Object> refListeners)
|
||||||
|
{
|
||||||
|
LOGGER.debug("Updating listeners for target " + target.getOurUser().getID());
|
||||||
|
|
||||||
|
if(!connectionsDatastore.contains(target)) return;
|
||||||
|
if(!registeredListeners.containsKey(target)) registeredListeners.put(target, new HashSet<>());
|
||||||
|
|
||||||
|
Set<Object> currentListeners = registeredListeners.get(target);
|
||||||
|
|
||||||
|
// Case 1: item is not registered in IDC
|
||||||
|
long toRegister = refListeners.stream().filter(item -> !currentListeners.contains(item)).count();
|
||||||
|
refListeners.stream().filter(item -> !currentListeners.contains(item)).forEach(item ->
|
||||||
|
{
|
||||||
|
target.getDispatcher().registerListener(item);
|
||||||
|
currentListeners.add(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Case 2: item is registered, but shouldn't be
|
||||||
|
long toUnregister = currentListeners.stream().filter(item -> !refListeners.contains(item)).count();
|
||||||
|
currentListeners.stream().filter(item -> !refListeners.contains(item)).forEach(item ->
|
||||||
|
{
|
||||||
|
target.getDispatcher().unregisterListener(item);
|
||||||
|
currentListeners.remove(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
LOGGER.debug("Registered " + toRegister + " listeners and unregistered " + toUnregister + " listeners.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all connections listeners
|
||||||
|
* @param refListeners Reference listeners list
|
||||||
|
*/
|
||||||
|
public void updateAllListeners(Set<Object> refListeners)
|
||||||
|
{
|
||||||
|
for(IDiscordClient i : connectionsDatastore) updateListeners(i, refListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect a disconnected gateway connection
|
||||||
|
* @param target Disconnected connection
|
||||||
|
*/
|
||||||
|
public void reconnect(IDiscordClient target)
|
||||||
|
{
|
||||||
|
if(!connectionsDatastore.contains(target)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
target.login();
|
||||||
|
}
|
||||||
|
catch (DiscordException e)
|
||||||
|
{
|
||||||
|
LOGGER.warn("User #" + target.getOurUser().getID() + " failed to reconnect to the gateway.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package net.pingex.dcf.util;
|
||||||
|
|
||||||
|
import net.jodah.failsafe.*;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import sx.blah.discord.api.IDiscordClient;
|
||||||
|
import sx.blah.discord.handle.obj.IChannel;
|
||||||
|
import sx.blah.discord.handle.obj.IMessage;
|
||||||
|
import sx.blah.discord.util.DiscordException;
|
||||||
|
import sx.blah.discord.util.HTTP429Exception;
|
||||||
|
import sx.blah.discord.util.MissingPermissionsException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains some useful functions to avoid having to rewrite the same code over and over
|
||||||
|
*/
|
||||||
|
public class DiscordInteractionsUtil
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Logger
|
||||||
|
*/
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(DiscordInteractionsUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount of retries for an interaction
|
||||||
|
*/
|
||||||
|
public static final int MAX_GW_RETRIES = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay between retries
|
||||||
|
*/
|
||||||
|
public static final int GW_RETRY_DELAY = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry policy for sending a message
|
||||||
|
*/
|
||||||
|
private static final RetryPolicy MESSAGE_RETRY_POLICY = new RetryPolicy()
|
||||||
|
.retryOn(HTTP429Exception.class, DiscordException.class)
|
||||||
|
.withDelay(GW_RETRY_DELAY, TimeUnit.SECONDS)
|
||||||
|
.withMaxRetries(MAX_GW_RETRIES)
|
||||||
|
.abortOn(MissingPermissionsException.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message
|
||||||
|
* @param channel Channel to send the message
|
||||||
|
* @param message Message to send
|
||||||
|
* @return The sent message, or {@code Optional.empty()}
|
||||||
|
*/
|
||||||
|
public static Optional<IMessage> sendMessage(IChannel channel, String message)
|
||||||
|
{
|
||||||
|
LOGGER.debug("Attempting to send a message (error-tolerant operation).");
|
||||||
|
|
||||||
|
SyncFailsafe fs = Failsafe.with(MESSAGE_RETRY_POLICY)
|
||||||
|
.onFailedAttempt(failure -> LOGGER.warn("Failed to send message.", failure))
|
||||||
|
.onRetry((c, f, stats) -> LOGGER.warn("Message sending failure #{}. Retrying.", stats.getExecutions()))
|
||||||
|
.onSuccess((result, context) -> LOGGER.debug("Sent message after {} attempts.", context.getExecutions()))
|
||||||
|
.onAbort(failure -> LOGGER.warn("Aborted sending message.", failure));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Optional.of((IMessage) fs.get(() -> channel.sendMessage(message)));
|
||||||
|
}
|
||||||
|
catch(FailsafeException e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Failed to send message (exceeded amounts or trials or aborted)", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy for a gateway disconnection
|
||||||
|
*/
|
||||||
|
private static final RetryPolicy GW_LOGOUT_POLICY = new RetryPolicy()
|
||||||
|
.retryOn(HTTP429Exception.class, DiscordException.class)
|
||||||
|
.withDelay(GW_RETRY_DELAY, TimeUnit.SECONDS)
|
||||||
|
.withMaxRetries(MAX_GW_RETRIES);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the gateway
|
||||||
|
* @param client Client interface
|
||||||
|
*/
|
||||||
|
public static void disconnect(IDiscordClient client)
|
||||||
|
{
|
||||||
|
LOGGER.debug("Attempting to disconnect user #{} from the gateway (error-tolerant operation).", client.getOurUser().getID());
|
||||||
|
|
||||||
|
SyncFailsafe fs = Failsafe.with(GW_LOGOUT_POLICY)
|
||||||
|
.onFailedAttempt(failure -> LOGGER.warn("Failed to disconnect.", failure))
|
||||||
|
.onRetry((result, failure, context) -> LOGGER.warn("Disconnection failure #{}. Retrying.", context.getExecutions()))
|
||||||
|
.onSuccess((result, context) -> LOGGER.info("Disconnected user {} from gateway after {} attempts.", client.getOurUser().getID(), context.getExecutions()));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fs.run(client::logout);
|
||||||
|
}
|
||||||
|
catch(FailsafeException e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Connection #{} failed to disconnect (amount of trials exceeded)", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue