Added D4J, configured logging. Also basic event handler.
parent
6bc355b9ac
commit
dd30fbecdc
@ -1,2 +1,10 @@
|
||||
# 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