package com.forgeessentials.chat.irc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.event.ClickEvent.Action;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import org.pircbotx.exception.IrcException;
import org.pircbotx.exception.NickAlreadyInUseException;
import org.pircbotx.hooks.ListenerAdapter;
import org.pircbotx.hooks.events.ConnectEvent;
import org.pircbotx.hooks.events.DisconnectEvent;
import org.pircbotx.hooks.events.JoinEvent;
import org.pircbotx.hooks.events.KickEvent;
import org.pircbotx.hooks.events.MessageEvent;
import org.pircbotx.hooks.events.NickChangeEvent;
import org.pircbotx.hooks.events.PartEvent;
import org.pircbotx.hooks.events.PrivateMessageEvent;
import org.pircbotx.hooks.events.QuitEvent;
import com.forgeessentials.chat.ModuleChat;
import com.forgeessentials.chat.irc.command.CommandHelp;
import com.forgeessentials.chat.irc.command.CommandListPlayers;
import com.forgeessentials.chat.irc.command.CommandMessage;
import com.forgeessentials.chat.irc.command.CommandReply;
import com.forgeessentials.core.ForgeEssentials;
import com.forgeessentials.core.misc.Translator;
import com.forgeessentials.core.moduleLauncher.config.ConfigLoader;
import com.forgeessentials.util.output.ChatOutputHandler;
import com.forgeessentials.util.output.LoggingHandler;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent;
public class IrcHandler extends ListenerAdapter<PircBotX> implements ConfigLoader
{
private static final String CATEGORY = ModuleChat.CONFIG_CATEGORY + ".IRC";
private static final String CHANNELS_HELP = "List of channels to connect to, together with the # character";
private static final String ADMINS_HELP = "List of priviliged users that can use more commands via the IRC bot";
public static final String COMMAND_CHAR = "%";
public static final String COMMAND_MC_CHAR = "!";
private PircBotX bot;
private String server;
private int port;
private String botName;
private String serverPassword;
private String nickPassword;
private Set<String> channels = new HashSet<>();
private Set<String> admins = new HashSet<>();
private boolean twitchMode;
private boolean showEvents;
private boolean showGameEvents;
private boolean showMessages;
private boolean sendMessages;
private String ircHeader;
private String ircHeaderGlobal;
private String mcHeader;
private int messageDelay;
private boolean allowCommands;
private boolean allowMcCommands;
public final Map<String, IrcCommand> commands = new HashMap<>();
// This map is used to keep the ICommandSender from being recycled by the garbage collector,
// so they can be used as WeakReferences in CommandReply
public final Map<User, IrcCommandSender> ircUserCache = new HashMap<>();
/* ------------------------------------------------------------ */
public IrcHandler()
{
ForgeEssentials.getConfigManager().registerLoader(ModuleChat.CONFIG_FILE, this);
MinecraftForge.EVENT_BUS.register(this);
registerCommand(new CommandHelp());
registerCommand(new CommandListPlayers());
registerCommand(new CommandMessage());
registerCommand(new CommandReply());
}
public static IrcHandler getInstance()
{
return ModuleChat.instance.ircHandler;
}
public void registerCommand(IrcCommand command)
{
for (String commandName : command.getCommandNames())
if (commands.put(commandName, command) != null)
LoggingHandler.felog.warn(String.format("IRC command name %s used twice!", commandName));
}
public void connect()
{
if (bot != null)
disconnect();
LoggingHandler.felog.info("Initializing IRC connection");
bot = new PircBotX();
bot.getListenerManager().addListener(this);
bot.setName(botName);
bot.setLogin(botName);
bot.setVerbose(false);
bot.setAutoNickChange(true);
bot.setMessageDelay(messageDelay);
bot.setCapEnabled(!twitchMode);
if (twitchMode)
// Prevent pesky messages from jtv because we are sending too fast
bot.setMessageDelay(3000);
try
{
LoggingHandler.felog.info(String.format("Attempting to join IRC server %s on port %d", server, port));
bot.connect(server, port, serverPassword.isEmpty() ? null : serverPassword);
bot.identify(nickPassword);
LoggingHandler.felog.info("Attempting to join channels...");
for (String channel : channels)
{
LoggingHandler.felog.info(String.format("Attempting to join #%s", channel));
bot.joinChannel(channel);
}
LoggingHandler.felog.info("IRC bot connected");
}
catch (NickAlreadyInUseException e)
{
LoggingHandler.felog.warn("[IRC] Connection failed, assigned nick already in use");
}
catch (IOException e)
{
LoggingHandler.felog.warn("[IRC] Connection failed, could not reach the server");
}
catch (IrcException e)
{
LoggingHandler.felog.warn("[IRC] Connection failed: " + e.getMessage());
}
}
public void disconnect()
{
if (bot != null)
{
bot.shutdown();
ircUserCache.clear();
bot = null;
}
}
public boolean isConnected()
{
return bot != null && bot.isConnected();
}
public Set<User> getIrcUsers()
{
return bot.getUsers();
}
public Collection<String> getIrcUserNames()
{
List<String> users = new ArrayList<>();
for (User user : bot.getUsers())
users.add(user.getNick());
return users;
}
/* ------------------------------------------------------------ */
@Override
public void load(Configuration config, boolean isReload)
{
config.addCustomCategoryComment(CATEGORY, "Configure the built-in IRC bot here");
server = config.get(CATEGORY, "server", "irc.something.com", "Server address").getString();
port = config.get(CATEGORY, "port", 5555, "Server port").getInt();
botName = config.get(CATEGORY, "botName", "FEIRCBot", "Bot name").getString();
serverPassword = config.get(CATEGORY, "serverPassword", "", "Server password").getString();
nickPassword = config.get(CATEGORY, "nickPassword", "", "NickServ password").getString();
twitchMode = config.get(CATEGORY, "twitchMode", false, "If set to true, sets connection to twitch mode").getBoolean();
showEvents = config.get(CATEGORY, "showEvents", true, "Show IRC events ingame (e.g., join, leave, kick, etc.)").getBoolean();
showGameEvents = config.get(CATEGORY, "showGameEvents", true, "Show game events in IRC (e.g., join, leave, death, etc.)").getBoolean();
showMessages = config.get(CATEGORY, "showMessages", true, "Show chat messages from IRC ingame").getBoolean();
sendMessages = config.get(CATEGORY, "sendMessages", false, "If enabled, ingame messages will be sent to IRC as well").getBoolean();
ircHeader = config.get(CATEGORY, "ircHeader", "[\u00a7cIRC\u00a7r]<%s> ", "Header for messages sent from IRC. Must contain one \"%s\"").getString();
ircHeaderGlobal = config.get(CATEGORY, "ircHeaderGlobal", "[\u00a7cIRC\u00a7r] ", "Header for IRC events. Must NOT contain any \"%s\"").getString();
mcHeader = config.get(CATEGORY, "mcHeader", "<%s> %s", "Header for messages sent from MC to IRC. Must contain two \"%s\"").getString();
messageDelay = config.get(CATEGORY, "messageDelay", 0, "Delay between messages sent to IRC").getInt();
allowCommands = config.get(CATEGORY, "allowCommands", true, "If enabled, allows usage of bot commands").getBoolean();
allowMcCommands = config.get(CATEGORY, "allowMcCommands", true,
"If enabled, allows usage of MC commands through the bot (only if the IRC user is in the admins list)").getBoolean();
channels.clear();
for (String channel : config.get(CATEGORY, "channels", new String[] { "#someChannelName" }, CHANNELS_HELP).getStringList())
channels.add(channel);
admins.clear();
for (String admin : config.get(CATEGORY, "admins", new String[] {}, ADMINS_HELP).getStringList())
admins.add(admin);
// mcHeader = config.get(CATEGORY, "mcFormat", "<%username> %message",
// "String for formatting messages posted to the IRC channel by the bot").getString();
boolean connectToIrc = config.get(CATEGORY, "enable", false, "Enable IRC interoperability?").getBoolean(false);
if (connectToIrc)
connect();
else
disconnect();
}
@Override
public void save(Configuration config)
{
// TODO Auto-generated method stub
}
@Override
public boolean supportsCanonicalConfig()
{
return true;
}
/* ------------------------------------------------------------ */
public void sendMessage(User user, String message)
{
if (!isConnected())
return;
// ignore messages to jtv
if (twitchMode && user.getNick() == "jtv")
return;
user.sendMessage(message);
}
public void sendMessage(String message)
{
if (isConnected())
for (String channel : channels)
bot.sendMessage(channel, message);
}
public void sendPlayerMessage(ICommandSender sender, IChatComponent message)
{
if (isConnected())
sendMessage(String.format(mcHeader, sender.getCommandSenderName(), ChatOutputHandler.stripFormatting(message.getUnformattedText())));
}
private void mcSendMessage(String message, User user)
{
String filteredMessage = ModuleChat.instance.censor.filterIRC(message);
ModuleChat.instance.logChatMessage("IRC-" + user.getNick(), filteredMessage);
String headerText = String.format(ircHeader, user.getNick());
IChatComponent header = ModuleChat.clickChatComponent(headerText, Action.SUGGEST_COMMAND, "/ircpm " + user.getNick() + " ");
IChatComponent messageComponent = ModuleChat.filterChatLinks(ChatOutputHandler.formatColors(filteredMessage));
ChatOutputHandler.broadcast(new ChatComponentTranslation("%s%s", header, messageComponent));
}
private void mcSendMessage(String message)
{
String filteredMessage = ModuleChat.instance.censor.filterIRC(message);
IChatComponent header = ModuleChat.clickChatComponent(ircHeaderGlobal, Action.SUGGEST_COMMAND, "/irc ");
IChatComponent messageComponent = ModuleChat.filterChatLinks(ChatOutputHandler.formatColors(filteredMessage));
ChatOutputHandler.broadcast(new ChatComponentTranslation("%s%s", header, messageComponent));
}
public ICommandSender getIrcUser(String username)
{
if (!isConnected())
return null;
for (User user : bot.getUsers())
{
if (user.getNick().equals(username))
{
IrcCommandSender sender = new IrcCommandSender(user);
ircUserCache.put(sender.getUser(), sender);
return sender;
}
}
return null;
}
private void processCommand(User user, String cmdLine)
{
String[] args = cmdLine.split(" ");
String commandName = args[0].substring(1);
args = Arrays.copyOfRange(args, 1, args.length);
IrcCommand command = commands.get(commandName);
if (command == null)
{
sendMessage(user, String.format("Error: Command %s not found!", commandName));
return;
}
IrcCommandSender sender = new IrcCommandSender(user);
ircUserCache.put(sender.getUser(), sender);
try
{
command.processCommand(sender, args);
}
catch (CommandException e)
{
sendMessage(user, "Error: " + e.getMessage());
}
}
private void processMcCommand(User user, String cmdLine)
{
if (!admins.contains(user.getNick()))
{
sendMessage(user, "Permission denied. You are not an admin");
return;
}
String[] args = cmdLine.split(" ");
String commandName = args[0].substring(1);
args = Arrays.copyOfRange(args, 1, args.length);
ICommand command = (ICommand) MinecraftServer.getServer().getCommandManager().getCommands().get(commandName);
if (command == null)
{
sendMessage(user, String.format("Error: Command %s not found!", commandName));
return;
}
IrcCommandSender sender = new IrcCommandSender(user);
ircUserCache.put(sender.getUser(), sender);
try
{
command.processCommand(sender, args);
}
catch (CommandException e)
{
sendMessage(user, "Error: " + e.getMessage());
}
}
/* ------------------------------------------------------------ */
@SubscribeEvent(priority = EventPriority.LOW)
public void chatEvent(ServerChatEvent event)
{
if (isConnected() && sendMessages)
sendMessage(ChatOutputHandler.stripFormatting(event.component.getUnformattedText()));
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void playerLoginEvent(PlayerLoggedInEvent event)
{
if (showGameEvents)
sendMessage(Translator.format("%s joined the game", event.player.getCommandSenderName()));
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void playerLoginEvent(PlayerLoggedOutEvent event)
{
if (showGameEvents)
sendMessage(Translator.format("%s left the game", event.player.getCommandSenderName()));
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void playerDeathEvent(LivingDeathEvent event)
{
if (!(event.entityLiving instanceof EntityPlayer))
return;
if (showGameEvents)
sendMessage(Translator.format("%s died", event.entityLiving.getCommandSenderName()));
}
/* ------------------------------------------------------------ */
@Override
public void onPrivateMessage(PrivateMessageEvent<PircBotX> event)
{
String raw = event.getMessage().trim();
while (raw.startsWith(":"))
raw.replace(":", "");
// Check to see if it is a command
if (raw.startsWith(COMMAND_CHAR) && allowCommands)
{
processCommand(event.getUser(), raw);
}
else if (raw.startsWith(COMMAND_MC_CHAR) && allowMcCommands)
{
processMcCommand(event.getUser(), raw);
}
else
{
if (twitchMode && (event.getUser().getNick() == "jtv"))
return;
sendMessage(event.getUser(), String.format("Hello %s, use %%help for commands", event.getUser().getNick()));
}
}
@Override
public void onMessage(MessageEvent<PircBotX> event)
{
if (event.getUser().getNick().equalsIgnoreCase(bot.getNick()))
return;
String raw = event.getMessage().trim();
while (raw.startsWith(":"))
raw.replace(":", "");
if (raw.startsWith(COMMAND_CHAR) && allowCommands)
{
processCommand(event.getUser(), raw);
}
else if (raw.startsWith(COMMAND_MC_CHAR) && allowMcCommands)
{
processMcCommand(event.getUser(), raw);
}
else if (showMessages)
mcSendMessage(raw, event.getUser());
}
@Override
public void onKick(KickEvent<PircBotX> event)
{
if (event.getRecipient() != bot.getUserBot())
{
if (showEvents)
mcSendMessage(String.format("%s has been kicked from %s by %s: %s", event.getRecipient().getNick(), event.getChannel().getName(), event
.getSource().getNick(), event.getReason()));
}
else
{
LoggingHandler.felog.warn(String.format("The IRC bot was kicked from %s by %s: ", event.getChannel().getName(), event.getSource().getNick(),
event.getReason()));
}
}
@Override
public void onQuit(QuitEvent<PircBotX> event)
{
if (!showEvents || event.getUser() == bot.getUserBot())
return;
mcSendMessage(String.format("%s left the channel %s: %s", event.getUser().getNick(), event.getReason()));
}
@Override
public void onNickChange(NickChangeEvent<PircBotX> event)
{
if (!showEvents || event.getUser() == bot.getUserBot())
return;
mcSendMessage(Translator.format("%s changed his nick to %s", event.getOldNick(), event.getNewNick()));
}
@Override
public void onJoin(JoinEvent<PircBotX> event) throws Exception
{
if (!showEvents || event.getUser() == bot.getUserBot())
return;
mcSendMessage(Translator.format("%s joined the channel %s", event.getUser().getNick(), event.getChannel().getName()));
}
@Override
public void onPart(PartEvent<PircBotX> event) throws Exception
{
ircUserCache.remove(event.getUser());
if (!showEvents || event.getUser() == bot.getUserBot())
return;
mcSendMessage(Translator.format("%s left the channel %s: %s", event.getUser().getNick(), event.getChannel().getName(), event.getReason()));
}
@Override
public void onConnect(ConnectEvent<PircBotX> event) throws Exception
{
mcSendMessage("IRC bot connected to the network");
}
@Override
public void onDisconnect(DisconnectEvent<PircBotX> event) throws Exception
{
mcSendMessage("IRC bot disconnected from the network");
}
/* ------------------------------------------------------------ */
public boolean isSendMessages()
{
return sendMessages;
}
public void setSendMessages(boolean sendMessages)
{
this.sendMessages = sendMessages;
}
}