package tterrag.tppibot.reactions;
import static tterrag.tppibot.reactions.CharacterSpam.SpamReasons.*;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Value;
import org.pircbotx.Channel;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import org.pircbotx.hooks.events.DisconnectEvent;
import org.pircbotx.hooks.events.MessageEvent;
import tterrag.tppibot.Main;
import tterrag.tppibot.config.Config;
import tterrag.tppibot.interfaces.ICommand.PermLevel;
import tterrag.tppibot.interfaces.IReaction;
import tterrag.tppibot.runnables.MessageSender;
import tterrag.tppibot.util.IRCUtils;
import tterrag.tppibot.util.Logging;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
public class CharacterSpam implements IReaction {
public enum SpamReasons {
REPEATS("You had too many repeated characters."),
SYMBOLS("You had too many non-alphabetic symbols."),
CAPS("Too much caps!"),
FLOOD("Too many messages!"),
CURSE("Banned word.");
private String text;
SpamReasons(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
@Value
public static class Strike {
private SpamReasons reason;
private String message;
}
private Map<Character, Integer> repeated;
private ListMultimap<String, Strike> strikes;
private Config strikesConfig;
private static Set<String> blacklistChannels = Sets.newConcurrentHashSet();
private Config blacklistConfig;
private static List<Character> whitelistedChars = Arrays.asList(new Character[] { ' ', '.' });
public CharacterSpam() {
repeated = new HashMap<Character, Integer>();
strikesConfig = new Config("spamStrikes.json");
blacklistConfig = new Config("spamChannelBlacklist.json");
Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<List<Strike>>(){}.getType(), new JsonDeserializer<List<Strike>>() {
@Override
public List<Strike> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<Strike> ret = new ArrayList<>();
if (json.isJsonPrimitive()) {
for (int i = 0; i < json.getAsInt(); i++) {
ret.add(new Strike(null, "Unknown"));
}
} else {
JsonArray arr = json.getAsJsonArray();
for (int i = 0; i < arr.size(); i++) {
ret.add(context.deserialize(arr.get(i), Strike.class));
}
}
return ret;
}
}).registerTypeAdapter(new TypeToken<ListMultimap<String, Strike>>() {}.getType(), new InstanceCreator<ListMultimap<String, Strike>>() {
@Override
public ListMultimap<String, Strike> createInstance(Type type) {
return MultimapBuilder.hashKeys().arrayListValues().build();
}
}).create();
Map<String, List<Strike>> map = gson.fromJson(strikesConfig.getText(), new TypeToken<Map<String, List<Strike>>>() {
}.getType());
blacklistChannels = gson.fromJson(blacklistConfig.getText(), new TypeToken<Set<String>>() {
}.getType());
strikes = MultimapBuilder.hashKeys().arrayListValues().build();
if (map != null) {
map.forEach((s, l) -> strikes.putAll(s, l));
}
if (blacklistChannels == null)
blacklistChannels = Sets.newConcurrentHashSet();
}
@Override
public synchronized void onMessage(MessageEvent<?> event) {
int symbolCount = 0;
int caps = 0;
String msg = event.getMessage();
if (msg.length() < 12)
return;
if (blacklistChannels.contains(event.getChannel().getName().toLowerCase()))
return;
for (char c : msg.toCharArray()) {
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || whitelistedChars.contains(c))) {
symbolCount++;
}
if (c >= 'A' && c <= 'Z') {
caps++;
}
if (repeated.containsKey(c)) {
repeated.put(c, repeated.get(c) + 1);
} else {
repeated.put(c, 1);
}
}
for (char c : repeated.keySet()) {
if (repeated.get(c) > msg.length() / 2) {
Logging.log("too many repeated characters!");
finish(timeout(event, REPEATS) ? event.getUser() : null, REPEATS, msg);
return;
}
}
if (symbolCount > msg.length() / 2) {
Logging.log("too many symbols!");
finish(timeout(event, SYMBOLS) ? event.getUser() : null, SYMBOLS, msg);
return;
}
// No caps for now
// if (caps > (double) msg.length() / 1.75d)
// {
// Logging.log("caps!");
// finish(timeout(event, CAPS) ? event.getUser() : null);
// return;
// }
finish(null, null, null);
}
public void finish(User user, SpamReasons reason, String message) {
repeated.clear();
if (user == null)
return;
String hostmask = user.getHostmask();
strikes.put(hostmask, new Strike(reason, message));
}
public boolean timeout(MessageEvent<?> event, SpamReasons reason) {
return timeout(event.getBot(), event.getUser(), event.getChannel(), reason);
}
public boolean timeout(PircBotX bot, User user, Channel channel, SpamReasons reason) {
if (IRCUtils.userIsOp(channel, bot.getUserBot()) && !IRCUtils.isUserAboveOrEqualTo(channel, PermLevel.TRUSTED, user)) {
int strikeCount = 0;
if (strikes.containsKey(user.getHostmask())) {
strikeCount = strikes.get(user.getHostmask()).size();
}
if (reason == SpamReasons.CURSE) {
MessageSender.INSTANCE.enqueue(bot, channel.getName(), user.getNick() + ", please do not do that! This is strike " + (strikeCount + 1) + ", you will now be timed out for " + 10
+ " minutes. Reason: " + reason.getText());
IRCUtils.timeout(bot, user, channel, "" + 10);
return true;
}
if (strikeCount < 3) {
MessageSender.INSTANCE.enqueue(bot, channel.getName(), user.getNick() + ", please do not do that! This is strike " + (strikeCount + 1) + "! Reason: " + reason.getText());
} else {
MessageSender.INSTANCE.enqueue(bot, channel.getName(), user.getNick() + ", please do not do that! This is strike " + (strikeCount + 1) + ", you will now be timed out for "
+ (5 * (strikeCount - 2)) + " minutes. Reason: " + reason.getText());
IRCUtils.timeout(bot, user, channel, "" + 5 * (strikeCount - 2));
}
return true;
}
return false;
}
/**
* @return true if added, false if removed
*/
public static boolean toggleBlacklistChannel(String channelname) {
synchronized (blacklistChannels) {
channelname = channelname.toLowerCase();
if (blacklistChannels.contains(channelname)) {
blacklistChannels.remove(channelname);
return false;
} else {
blacklistChannels.add(channelname);
return true;
}
}
}
public boolean filtersEnabled(String channelname) {
synchronized (blacklistChannels) {
channelname = channelname.toLowerCase();
return !blacklistChannels.contains(channelname);
}
}
public int getStrikeCount(User user) {
return strikes.get(user.getHostmask()).size();
}
public int removeStrikes(User user, int amnt) {
String hostmask = user.getHostmask();
while (--amnt >= 0 && !strikes.get(hostmask).isEmpty()) {
strikes.get(hostmask).remove(0);
}
return getStrikeCount(user);
}
public List<Strike> getStrikes(User user) {
return ImmutableList.copyOf(strikes.get(user.getHostmask()));
}
@Subscribe
public void onDisconnect(DisconnectEvent<?> event) {
String text = Main.gson.toJson(strikes.asMap());
System.out.println(text);
System.out.println(strikes);
strikesConfig.writeTextToFile(text);
blacklistConfig.writeJsonToFile(blacklistChannels);
}
}