package com.forgeessentials.chat;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.server.CommandMessage;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.event.ClickEvent;
import net.minecraft.event.ClickEvent.Action;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.CommandEvent;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.permission.PermissionLevel;
import com.forgeessentials.api.APIRegistry;
import com.forgeessentials.api.UserIdent;
import com.forgeessentials.api.permissions.FEPermissions;
import com.forgeessentials.api.permissions.GroupEntry;
import com.forgeessentials.chat.command.CommandIrc;
import com.forgeessentials.chat.command.CommandIrcBot;
import com.forgeessentials.chat.command.CommandIrcPm;
import com.forgeessentials.chat.command.CommandMessageReplacement;
import com.forgeessentials.chat.command.CommandMute;
import com.forgeessentials.chat.command.CommandNickname;
import com.forgeessentials.chat.command.CommandPm;
import com.forgeessentials.chat.command.CommandReply;
import com.forgeessentials.chat.command.CommandTimedMessages;
import com.forgeessentials.chat.command.CommandUnmute;
import com.forgeessentials.chat.irc.IrcHandler;
import com.forgeessentials.commands.util.ModuleCommandsEventHandler;
import com.forgeessentials.commons.selections.WorldPoint;
import com.forgeessentials.core.ForgeEssentials;
import com.forgeessentials.core.misc.FECommandManager;
import com.forgeessentials.core.moduleLauncher.FEModule;
import com.forgeessentials.scripting.ScriptArguments;
import com.forgeessentials.util.ServerUtil;
import com.forgeessentials.util.events.FEModuleEvent.FEModuleInitEvent;
import com.forgeessentials.util.events.FEModuleEvent.FEModuleServerInitEvent;
import com.forgeessentials.util.events.FEModuleEvent.FEModuleServerPostInitEvent;
import com.forgeessentials.util.events.FEModuleEvent.FEModuleServerStopEvent;
import com.forgeessentials.util.events.FEPlayerEvent.NoPlayerInfoEvent;
import com.forgeessentials.util.output.ChatOutputHandler;
import com.forgeessentials.util.output.LoggingHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent;
@FEModule(name = "Chat", parentMod = ForgeEssentials.class)
public class ModuleChat
{
public static final String CONFIG_FILE = "Chat";
public static final String CONFIG_CATEGORY = "Chat";
public static final String PERM = "fe.chat";
public static final String PERM_CHAT = PERM + ".chat";
public static final String PERM_COLOR = PERM + ".usecolor";
private static final String PERM_TEXTFORMAT = PERM + ".textformat";
private static final String PERM_PLAYERFORMAT = PERM + ".playerformat";
public static final String PERM_RANGE = PERM + ".range";
// @formatter:off
static final Pattern URL_PATTERN = Pattern.compile(
// schema ipv4 OR namespace port path ends
// |-----------------| |-------------------------| |----------------------------| |---------| |--| |---------------|
"((?:(?:http|https):\\/\\/)?(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3}|(?:[-\\w_\\.]{1,}\\.[a-z]{2,}?))(?::[0-9]{1,5})?.*?(?=[!\"\u00A7 \n]|$))",
Pattern.CASE_INSENSITIVE);
// @formatter:on
public static final Map<String, String> chatConstReplacements = new HashMap<>();
@FEModule.Instance
public static ModuleChat instance;
private PrintWriter logWriter;
public Censor censor;
public Mailer mailer;
public IrcHandler ircHandler;
public TimedMessageHandler timedMessageHandler;
/* ------------------------------------------------------------ */
@SubscribeEvent
public void moduleLoad(FEModuleInitEvent e)
{
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
ForgeEssentials.getConfigManager().registerLoader(CONFIG_FILE, new ChatConfig());
timedMessageHandler = new TimedMessageHandler();
ircHandler = new IrcHandler();
censor = new Censor();
mailer = new Mailer();
setupChatReplacements();
}
public void setupChatReplacements()
{
chatConstReplacements.put("smile", "\u263A");
chatConstReplacements.put("copyrighted", "\u00A9");
chatConstReplacements.put("registered", "\u00AE");
chatConstReplacements.put("diamond", "\u2662");
chatConstReplacements.put("spade", "\u2664");
chatConstReplacements.put("club", "\u2667");
chatConstReplacements.put("heart", "\u2661");
chatConstReplacements.put("female", "\u2640");
chatConstReplacements.put("male", "\u2642");
// replace colors
chatConstReplacements.put("red", EnumChatFormatting.RED.toString());
chatConstReplacements.put("yellow", EnumChatFormatting.YELLOW.toString());
chatConstReplacements.put("black", EnumChatFormatting.BLACK.toString());
chatConstReplacements.put("darkblue", EnumChatFormatting.DARK_BLUE.toString());
chatConstReplacements.put("darkgreen", EnumChatFormatting.DARK_GREEN.toString());
chatConstReplacements.put("darkaqua", EnumChatFormatting.DARK_AQUA.toString());
chatConstReplacements.put("darkred", EnumChatFormatting.DARK_RED.toString());
chatConstReplacements.put("purple", EnumChatFormatting.DARK_PURPLE.toString());
chatConstReplacements.put("gold", EnumChatFormatting.GOLD.toString());
chatConstReplacements.put("grey", EnumChatFormatting.GRAY.toString());
chatConstReplacements.put("darkgrey", EnumChatFormatting.DARK_GRAY.toString());
chatConstReplacements.put("indigo", EnumChatFormatting.BLUE.toString());
chatConstReplacements.put("green", EnumChatFormatting.GREEN.toString());
chatConstReplacements.put("aqua", EnumChatFormatting.AQUA.toString());
chatConstReplacements.put("pink", EnumChatFormatting.LIGHT_PURPLE.toString());
chatConstReplacements.put("white", EnumChatFormatting.WHITE.toString());
// replace MC formating
chatConstReplacements.put("rnd", EnumChatFormatting.OBFUSCATED.toString());
chatConstReplacements.put("bold", EnumChatFormatting.BOLD.toString());
chatConstReplacements.put("strike", EnumChatFormatting.STRIKETHROUGH.toString());
chatConstReplacements.put("underline", EnumChatFormatting.UNDERLINE.toString());
chatConstReplacements.put("italics", EnumChatFormatting.ITALIC.toString());
chatConstReplacements.put("reset", EnumChatFormatting.RESET.toString());
}
@SubscribeEvent
public void serverStarting(FEModuleServerInitEvent e)
{
FECommandManager.registerCommand(new CommandMute());
FECommandManager.registerCommand(new CommandNickname());
FECommandManager.registerCommand(new CommandPm());
FECommandManager.registerCommand(new CommandReply());
FECommandManager.registerCommand(new CommandTimedMessages());
FECommandManager.registerCommand(new CommandUnmute());
FECommandManager.registerCommand(new CommandIrc());
FECommandManager.registerCommand(new CommandIrcPm());
FECommandManager.registerCommand(new CommandIrcBot());
APIRegistry.perms.registerPermissionDescription(PERM, "Chat permissions");
APIRegistry.perms.registerPermission(PERM_CHAT, PermissionLevel.TRUE, "Allow players to use the public chat");
APIRegistry.perms.registerPermission(PERM_COLOR, PermissionLevel.TRUE, "Allow players to use the public chat");
APIRegistry.perms.registerPermissionProperty(PERM_TEXTFORMAT, "", "Textformat colors. USE ONLY THE COLOR CHARACTERS AND NO &");
APIRegistry.perms.registerPermissionProperty(PERM_PLAYERFORMAT, "", "Text to show in front of the player name in chat messages");
APIRegistry.perms.registerPermissionProperty(PERM_RANGE, "", "Send chat messages only to players in this range of the sender");
}
@SubscribeEvent
public void serverStarted(FEModuleServerPostInitEvent e)
{
ServerUtil.replaceCommand(CommandMessage.class, new CommandMessageReplacement());
}
@SubscribeEvent
public void serverStopping(FEModuleServerStopEvent e)
{
closeLog();
ircHandler.disconnect();
}
/* ------------------------------------------------------------ */
@SubscribeEvent(priority = EventPriority.LOW)
public void chatEvent(ServerChatEvent event)
{
UserIdent ident = UserIdent.get(event.player);
if (!ident.checkPermission(PERM_CHAT))
{
ChatOutputHandler.chatWarning(event.player, "You don't have the permission to write in public chat.");
event.setCanceled(true);
return;
}
if (event.player.getEntityData().getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG).getBoolean("mute"))
{
ChatOutputHandler.chatWarning(event.player, "You are currently muted.");
event.setCanceled(true);
return;
}
if (CommandPm.getTarget(event.player) != null)
{
tell(event.player, event.component, CommandPm.getTarget(event.player));
event.setCanceled(true);
return;
}
// Log chat message
logChatMessage(event.player.getCommandSenderName(), event.message);
// Initialize parameters
String message = processChatReplacements(event.player, censor.filter(event.player, event.message));
String playerName = getPlayerNickname(event.player);
// Get player name formatting
String playerFormat = APIRegistry.perms.getUserPermissionProperty(ident, ModuleChat.PERM_PLAYERFORMAT);
if (playerFormat == null)
playerFormat = "";
// Initialize header
String playerCmd = "/msg " + event.player.getCommandSenderName() + " ";
IChatComponent groupPrefix = appendGroupPrefixSuffix(null, ident, false);
IChatComponent playerPrefix = clickChatComponent(getPlayerPrefixSuffix(ident, false), Action.SUGGEST_COMMAND, playerCmd);
IChatComponent playerText = clickChatComponent(playerFormat + playerName, Action.SUGGEST_COMMAND, playerCmd);
IChatComponent playerSuffix = clickChatComponent(getPlayerPrefixSuffix(ident, true), Action.SUGGEST_COMMAND, playerCmd);
IChatComponent groupSuffix = appendGroupPrefixSuffix(null, ident, true);
IChatComponent header = new ChatComponentTranslation(ChatOutputHandler.formatColors(ChatConfig.chatFormat), //
groupPrefix != null ? groupPrefix : "", //
playerPrefix != null ? playerPrefix : "", //
playerText, //
playerSuffix != null ? playerSuffix : "", //
groupSuffix != null ? groupSuffix : "");
// Apply colors
if (event.message.contains("&") && ident.checkPermission(PERM_COLOR))
message = ChatOutputHandler.formatColors(message);
// Build message part with links
IChatComponent messageComponent = filterChatLinks(message);
String textFormats = APIRegistry.perms.getUserPermissionProperty(ident, ModuleChat.PERM_TEXTFORMAT);
if (textFormats != null)
ChatOutputHandler.applyFormatting(messageComponent.getChatStyle(), ChatOutputHandler.enumChatFormattings(textFormats));
// Finish complete message
event.component = new ChatComponentTranslation("%s%s", header, messageComponent);
// Handle chat range
Double range = ServerUtil.tryParseDouble(ident.getPermissionProperty(PERM_RANGE));
if (range != null)
{
WorldPoint source = new WorldPoint(event.player);
for (EntityPlayerMP player : ServerUtil.getPlayerList())
{
if (player.dimension == source.getDimension() && source.distance(new WorldPoint(player)) <= range)
ChatOutputHandler.sendMessage(player, event.component);
}
event.setCanceled(true);
}
}
@SubscribeEvent(priority = EventPriority.LOW)
public void commandEvent(CommandEvent event)
{
if (!(event.sender instanceof EntityPlayerMP))
return;
EntityPlayerMP player = (EntityPlayerMP) event.sender;
if (!player.getEntityData().getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG).getBoolean("mute"))
return;
if (!ChatConfig.mutedCommands.contains(event.command.getCommandName()))
return;
ChatOutputHandler.chatWarning(event.sender, "You are currently muted.");
event.setCanceled(true);
}
public static String processChatReplacements(ICommandSender sender, String message)
{
message = ScriptArguments.processSafe(message, sender);
for (Entry<String, String> r : chatConstReplacements.entrySet())
message = message.replaceAll("%" + r.getKey(), r.getValue());
message = ChatOutputHandler.formatColors(message);
return message;
}
public static IChatComponent clickChatComponent(String text, Action action, String uri)
{
IChatComponent component = new ChatComponentText(ChatOutputHandler.formatColors(text));
component.getChatStyle().setChatClickEvent(new ClickEvent(Action.SUGGEST_COMMAND, uri));
return component;
}
public static String getPlayerPrefixSuffix(UserIdent player, boolean isSuffix)
{
String fix = APIRegistry.perms.getServerZone().getPlayerPermission(player, isSuffix ? FEPermissions.SUFFIX : FEPermissions.PREFIX);
if (fix == null)
return "";
return fix;
}
public static IChatComponent appendGroupPrefixSuffix(IChatComponent header, UserIdent ident, boolean isSuffix)
{
for (GroupEntry group : APIRegistry.perms.getPlayerGroups(ident))
{
String text = APIRegistry.perms.getServerZone().getGroupPermission(group.getGroup(), isSuffix ? FEPermissions.SUFFIX : FEPermissions.PREFIX);
if (text != null)
{
IChatComponent component = clickChatComponent(text, Action.SUGGEST_COMMAND, "/gmsg " + group.getGroup() + " ");
if (header == null)
header = component;
else
header.appendSibling(component);
}
}
return header;
}
public static IChatComponent filterChatLinks(String text)
{
// Includes ipv4 and domain pattern
// Matches an ip (xx.xxx.xx.xxx) or a domain (something.com) with or
// without a protocol or path.
IChatComponent ichat = new ChatComponentText("");
Matcher matcher = URL_PATTERN.matcher(text);
int lastEnd = 0;
// Find all urls
while (matcher.find())
{
int start = matcher.start();
int end = matcher.end();
// Append the previous left overs.
ichat.appendText(text.substring(lastEnd, start));
lastEnd = end;
String url = text.substring(start, end);
IChatComponent link = new ChatComponentText(url);
link.getChatStyle().setUnderlined(true);
try
{
// Add schema so client doesn't crash.
if ((new URI(url)).getScheme() == null)
url = "http://" + url;
}
catch (URISyntaxException e)
{
// Bad syntax bail out!
ichat.appendText(url);
continue;
}
// Set the click event and append the link.
ClickEvent click = new ClickEvent(ClickEvent.Action.OPEN_URL, url);
link.getChatStyle().setChatClickEvent(click);
ichat.appendSibling(link);
}
// Append the rest of the message.
ichat.appendText(text.substring(lastEnd));
return ichat;
}
/* ------------------------------------------------------------ */
@SubscribeEvent
public void onPlayerFirstJoin(NoPlayerInfoEvent event)
{
if (!ChatConfig.welcomeMessage.isEmpty())
{
String message = processChatReplacements(event.getPlayer(), ChatConfig.welcomeMessage);
ChatOutputHandler.broadcast(new ChatComponentText(message));
}
}
@SubscribeEvent
public void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent e)
{
if (e.player instanceof EntityPlayerMP)
sendMotd(e.player);
}
public static void sendMotd(ICommandSender sender)
{
for (String message : ChatConfig.loginMessage)
{
message = processChatReplacements(sender, message);
ChatOutputHandler.sendMessage(sender, message);
}
}
/* ------------------------------------------------------------ */
public void logChatMessage(String sender, String message)
{
if (logWriter == null)
return;
String logMessage = String.format("[%1$tY-%1$tm-%1$te %1$tH:%1$tM:%1$tS] %2$s: %3$s", new Date(), sender, message);
logWriter.write(logMessage + "\n");
}
public void setChatLogging(boolean enabled)
{
if (logWriter != null && enabled)
return;
closeLog();
if (enabled)
{
File logFile = new File(ForgeEssentials.getFEDirectory(), String.format("ChatLog/%1$tY-%1$tm-%1$te_%1$tH.%1$tM.log", new Date()));
try
{
File dir = logFile.getParentFile();
if (!dir.exists() && !dir.mkdirs())
{
LoggingHandler.felog.warn(String.format("Could not create chat log directory %s!", logFile.getPath()));
}
else
{
logWriter = new PrintWriter(logFile);
}
}
catch (FileNotFoundException e)
{
LoggingHandler.felog.error(String.format("Could not create chat log file %s.", logFile.getAbsolutePath()));
}
}
}
private void closeLog()
{
if (logWriter != null)
{
logWriter.close();
logWriter = null;
}
}
/* ------------------------------------------------------------ */
public static void setPlayerNickname(EntityPlayer player, String nickname)
{
if (nickname == null)
player.getEntityData().getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG).removeTag("nickname");
else
player.getEntityData().getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG).setString("nickname", nickname);
}
public static String getPlayerNickname(EntityPlayer player)
{
String nickname = player.getEntityData().getCompoundTag(EntityPlayer.PERSISTED_NBT_TAG).getString("nickname");
if (nickname == null || nickname.isEmpty())
nickname = player.getCommandSenderName();
return nickname;
}
/* ------------------------------------------------------------ */
public static void tell(ICommandSender sender, IChatComponent message, ICommandSender target)
{
ChatComponentTranslation sentMsg = new ChatComponentTranslation("commands.message.display.incoming", new Object[] { sender.func_145748_c_(),
message.createCopy() });
ChatComponentTranslation senderMsg = new ChatComponentTranslation("commands.message.display.outgoing",
new Object[] { target.func_145748_c_(), message });
sentMsg.getChatStyle().setColor(EnumChatFormatting.GRAY).setItalic(Boolean.valueOf(true));
senderMsg.getChatStyle().setColor(EnumChatFormatting.GRAY).setItalic(Boolean.valueOf(true));
ChatOutputHandler.sendMessage(target, sentMsg);
ChatOutputHandler.sendMessage(sender, senderMsg);
CommandReply.messageSent(sender, target);
ModuleCommandsEventHandler.checkAfkMessage(target, message);
}
}