Fix many issues with the chat processing

#71
This commit is contained in:
Norbi Peti 2020-02-13 00:24:31 +01:00
parent 0dbfaa65a5
commit 4d0c7c170e
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
10 changed files with 109 additions and 39 deletions

View file

@ -50,26 +50,26 @@ public class ChatProcessing {
private static ArrayList<MatchProviderBase> commonFormatters = Lists.newArrayList( private static ArrayList<MatchProviderBase> commonFormatters = Lists.newArrayList(
new RangeMatchProvider("bold", "**", FormatSettings.builder().bold(true).build()), new RangeMatchProvider("bold", "**", FormatSettings.builder().bold(true).build()),
new RangeMatchProvider("italic", "*", FormatSettings.builder().italic(true).build()), new RangeMatchProvider("italic", "*", FormatSettings.builder().italic(true).build()),
new RangeMatchProvider("italic2", "_", FormatSettings.builder().italic(true).build()),
new RangeMatchProvider("underlined", "__", FormatSettings.builder().underlined(true).build()), new RangeMatchProvider("underlined", "__", FormatSettings.builder().underlined(true).build()),
new RangeMatchProvider("italic2", "_", FormatSettings.builder().italic(true).build()),
new RangeMatchProvider("strikethrough", "~~", FormatSettings.builder().strikethrough(true).build()), new RangeMatchProvider("strikethrough", "~~", FormatSettings.builder().strikethrough(true).build()),
new RangeMatchProvider("spoiler", "||", FormatSettings.builder().obfuscated(true) new RangeMatchProvider("spoiler", "||", FormatSettings.builder().obfuscated(true)
.onmatch((match, cf, fs) -> { .onmatch((match, cf, fs) -> {
cf.setHoverText(match); cf.setHoverText(match);
return match; return match;
}).build()), }).build()),
new StringMatchProvider("nullMention", FormatSettings.builder().color(Color.DarkRed).build(), "null"), // Properly added a bug as a feature new StringMatchProvider("nullMention", FormatSettings.builder().color(Color.DarkRed).build(), true, "null"), // Properly added a bug as a feature
new StringMatchProvider("consolePing", FormatSettings.builder().color(Color.Aqua) new StringMatchProvider("consolePing", FormatSettings.builder().color(Color.Aqua)
.onmatch((match, builder, section) -> { .onmatch((match, builder, section) -> {
if (!pingedconsole) { if (!pingedconsole) {
System.out.print("\007"); System.out.print("\007");
pingedconsole = true; // Will set it to false in ProcessChat pingedconsole = true; // Will set it to false in ProcessChat
} }
return match; return "@console";
}).build(), "@console"), }).build(), true, "@console"),
new RegexMatchProvider("hashtag", HASHTAG_PATTERN, FormatSettings.builder().color(Color.Blue).openlink("https://twitter.com/hashtag/$1").build()), new RegexMatchProvider("hashtag", HASHTAG_PATTERN, FormatSettings.builder().color(Color.Blue).openlink("https://twitter.com/hashtag/$1").build()),
new StringMatchProvider("cyan", FormatSettings.builder().color(Color.Aqua).build(), "cyan"), // #55 new StringMatchProvider("cyan", FormatSettings.builder().color(Color.Aqua).build(), true, "cyan"), // #55
new RangeMatchProvider("code", "`", FormatSettings.builder().color(Color.DarkGray).build()), new RangeMatchProvider("code", "`", FormatSettings.builder().color(Color.DarkGray).build()),
new RegexMatchProvider("maskedLink", MASKED_LINK_PATTERN, FormatSettings.builder().underlined(true) new RegexMatchProvider("maskedLink", MASKED_LINK_PATTERN, FormatSettings.builder().underlined(true)
.onmatch((match, builder, section) -> { .onmatch((match, builder, section) -> {
@ -88,7 +88,7 @@ public class ChatProcessing {
var player = players.get(playerC); var player = players.get(playerC);
playPingSound(player, ComponentManager.getIfEnabled(FormatterComponent.class)); playPingSound(player, ComponentManager.getIfEnabled(FormatterComponent.class));
return "@someone (" + player.getDisplayName() + "§r)"; return "@someone (" + player.getDisplayName() + "§r)";
}).build())); }).build(), true, "@someone"));
private static Gson gson = new GsonBuilder() private static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum()) .registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum())
.registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection()) .registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection())
@ -265,8 +265,8 @@ public class ChatProcessing {
System.out.println(message); System.out.println(message);
}; };
if (names.length > 0) if (names.length > 0) //Add as first so it handles special characters (_) - TODO: But after URLs
formatters.add(new StringMatchProvider("name", FormatSettings.builder().color(Color.Aqua) formatters.add(0, new StringMatchProvider("name", FormatSettings.builder().color(Color.Aqua)
.onmatch((match, builder, section) -> { .onmatch((match, builder, section) -> {
Player p = Bukkit.getPlayer(match); Player p = Bukkit.getPlayer(match);
Optional<String> pn = nottest ? Optional.empty() Optional<String> pn = nottest ? Optional.empty()
@ -281,10 +281,10 @@ public class ChatProcessing {
} }
String color = String.format("§%x", (mpp.GetFlairColor() == 0x00 ? 0xb : mpp.GetFlairColor())); String color = String.format("§%x", (mpp.GetFlairColor() == 0x00 ? 0xb : mpp.GetFlairColor()));
return color + (nottest ? p.getName() : pn.get()) + "§r"; //Fix name casing, except when testing return color + (nottest ? p.getName() : pn.get()) + "§r"; //Fix name casing, except when testing
}).build(), names)); }).build(), true, names));
if (nicknames.length > 0) if (nicknames.length > 0) //Add as first so it handles special characters
formatters.add(new StringMatchProvider("nickname", FormatSettings.builder().color(Color.Aqua) formatters.add(0, new StringMatchProvider("nickname", FormatSettings.builder().color(Color.Aqua)
.onmatch((match, builder, section) -> { .onmatch((match, builder, section) -> {
if (PlayerListener.nicknames.containsKey(match.toLowerCase())) { //Made a stream and all that but I can actually store it lowercased if (PlayerListener.nicknames.containsKey(match.toLowerCase())) { //Made a stream and all that but I can actually store it lowercased
Player p = Bukkit.getPlayer(PlayerListener.nicknames.get(match.toLowerCase())); Player p = Bukkit.getPlayer(PlayerListener.nicknames.get(match.toLowerCase()));
@ -299,7 +299,7 @@ public class ChatProcessing {
error.accept("Player nicknamed " + match.toLowerCase() error.accept("Player nicknamed " + match.toLowerCase()
+ " not found in nickname map but was reported as online."); + " not found in nickname map but was reported as online.");
return "§c" + match + "§r"; return "§c" + match + "§r";
}).build(), nicknames)); }).build(), true, nicknames));
} }
return formatters; return formatters;
} }

View file

@ -4,6 +4,7 @@ import buttondevteam.chat.commands.ucmds.admin.DebugCommand;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors;
public final class ChatFormatUtils { public final class ChatFormatUtils {
private ChatFormatUtils() {} private ChatFormatUtils() {}
@ -26,6 +27,9 @@ public final class ChatFormatUtils {
* Check if the given start and end position is inside any of the ranges * Check if the given start and end position is inside any of the ranges
*/ */
static boolean isInRange(int start, int end, ArrayList<int[]> ranges) { static boolean isInRange(int start, int end, ArrayList<int[]> ranges) {
return ranges.stream().anyMatch(range -> range[1] >= start && range[0] <= end - 1); System.out.println("Ranges: " + ranges.stream().map(x -> x[0] + "-" + x[1]).collect(Collectors.joining(", ")));
System.out.println("In range: " + start + " " + end + ": " +
ranges.stream().filter(range -> range[1] >= start && range[0] <= end).map(x -> x[0] + "-" + x[1]).findAny().orElse("none"));
return ranges.stream().anyMatch(range -> range[1] >= start && range[0] <= end);
} }
} }

View file

@ -10,7 +10,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart, IHaveConfig)} is used to turn it into a {@link TellrawPart}, combining * A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart, IHaveConfig, FormatSettings)}} is used to turn it into a {@link TellrawPart}, combining
* intersecting parts found, for example when {@code _abc*def*ghi_} is said in chat, it'll turn it into an underlined part, then an underlined <i>and italics</i> part, finally an underlined part * intersecting parts found, for example when {@code _abc*def*ghi_} is said in chat, it'll turn it into an underlined part, then an underlined <i>and italics</i> part, finally an underlined part
* again. * again.
* *
@ -40,6 +40,8 @@ public final class ChatFormatter {
*/ */
val remchars = new ArrayList<int[]>(); val remchars = new ArrayList<int[]>();
escapeThings(str, excluded, remchars);
createSections(formatters, str, sections, excluded, remchars, defaults); createSections(formatters, str, sections, excluded, remchars, defaults);
sortSections(sections); sortSections(sections);
@ -51,18 +53,32 @@ public final class ChatFormatter {
header("ChatFormatter.Combine done"); header("ChatFormatter.Combine done");
} }
private static void escapeThings(String str, ArrayList<int[]> ignoredAreas, ArrayList<int[]> remchars) {
boolean escaped = false;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == '\\') {
remchars.add(new int[]{i, i});
ignoredAreas.add(new int[]{i + 1, i + 1});
i++; //Ignore a potential second slash
}
}
}
private static void createSections(List<MatchProviderBase> formatters, String str, ArrayList<FormattedSection> sections, private static void createSections(List<MatchProviderBase> formatters, String str, ArrayList<FormattedSection> sections,
ArrayList<int[]> excludedAreas, ArrayList<int[]> removedCharacters, FormatSettings defaults) { ArrayList<int[]> excludedAreas, ArrayList<int[]> removedCharacters, FormatSettings defaults) {
sections.add(new FormattedSection(defaults, 0, str.length() - 1, Collections.emptyList())); //Add entire message sections.add(new FormattedSection(defaults, 0, str.length() - 1, Collections.emptyList())); //Add entire message
formatters.forEach(MatchProviderBase::reset); //Reset state information, as we aren't doing deep cloning
while (formatters.size() > 0) { while (formatters.size() > 0) {
for (var iterator = formatters.iterator(); iterator.hasNext(); ) { for (var iterator = formatters.iterator(); iterator.hasNext(); ) {
MatchProviderBase formatter = iterator.next(); MatchProviderBase formatter = iterator.next();
DebugCommand.SendDebugMessage("Checking provider: " + formatter); DebugCommand.SendDebugMessage("Checking provider: " + formatter);
var sect = formatter.getNextSection(str, excludedAreas, removedCharacters); var sect = formatter.getNextSection(str, excludedAreas, removedCharacters);
if (sect != null) if (sect != null) //Not excluding the area here because the range matcher shouldn't take it all
sections.add(sect); sections.add(sect);
if (formatter.isFinished()) if (formatter.isFinished()) {
DebugCommand.SendDebugMessage("Provider finished");
iterator.remove(); iterator.remove();
}
} }
} }
} }
@ -103,12 +119,8 @@ public final class ChatFormatter {
FormattedSection section = new FormattedSection(firstSection.Settings, lastSection.Start, origend, FormattedSection section = new FormattedSection(firstSection.Settings, lastSection.Start, origend,
firstSection.Matches); firstSection.Matches);
section.Settings.copyFrom(lastSection.Settings); section.Settings.copyFrom(lastSection.Settings);
section.Matches.addAll(lastSection.Matches); // TODO: Clean section.Matches.addAll(lastSection.Matches);
sections.add(i, section); sections.add(i, section);
// Use the properties of the first section not the second one
lastSection.Settings = firstSection.Settings;
lastSection.Matches.clear();
lastSection.Matches.addAll(firstSection.Matches);
lastSection.Start = origend + 1; lastSection.Start = origend + 1;
lastSection.End = origend2; lastSection.End = origend2;

View file

@ -19,11 +19,15 @@ public class FormatSettings {
public void copyFrom(FormatSettings settings) { public void copyFrom(FormatSettings settings) {
try { try {
for (var field : FormatSettings.class.getDeclaredFields()) for (var field : FormatSettings.class.getDeclaredFields()) {
if (field.getType() == boolean.class && field.getBoolean(settings)) if (field.getType() == boolean.class) {
field.setBoolean(this, true); //Set to true if either of them are true if (field.getBoolean(settings))
else if (field.get(this) == null) field.setBoolean(this, true); //Set to true if either of them are true
} else if (field.get(settings) != null) {
//System.out.println("Setting " + field.getType() + " " + field.getName() + " from " + field.get(this) + " to " + field.get(settings));
field.set(this, field.get(settings)); field.set(this, field.get(settings));
}
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -15,7 +15,8 @@ public class FormattedSection {
FormattedSection(FormatSettings settings, int start, int end, List<String> matches) { FormattedSection(FormatSettings settings, int start, int end, List<String> matches) {
Start = start; Start = start;
End = end; End = end;
Settings = settings; Settings = FormatSettings.builder().build();
Settings.copyFrom(settings);
Matches.addAll(matches); Matches.addAll(matches);
} }
} }

View file

@ -10,4 +10,9 @@ public interface MatchProvider {
boolean isFinished(); boolean isFinished();
String getName(); String getName();
@Override
String toString();
void reset();
} }

View file

@ -22,6 +22,13 @@ public abstract class MatchProviderBase implements MatchProvider {
@Override @Override
public abstract String toString(); public abstract String toString();
protected abstract void resetSubclass();
public void reset() {
finished = false;
resetSubclass();
}
ConfigData<Boolean> enabled(IHaveConfig config) { ConfigData<Boolean> enabled(IHaveConfig config) {
return config.getData(name + ".enabled", true); return config.getData(name + ".enabled", true);
} }

View file

@ -24,16 +24,18 @@ public class RangeMatchProvider extends MatchProviderBase {
@Override @Override
public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) { public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) {
int i, len; int i, len;
do { i = message.indexOf(pattern, nextIndex);
i = message.indexOf(pattern, nextIndex); len = pattern.length();
len = pattern.length(); nextIndex = i + len; //Set for the next method call
nextIndex = i + len; //Set for the next loop if it's escaped or for the next method call
} while (i > 0 && message.charAt(i - 1) == '\\');
if (i == -1) { if (i == -1) {
finished = true; //Won't find any more - unfinished sections will be garbage collected finished = true; //Won't find any more - unfinished sections will be garbage collected
return null; return null;
} }
removedCharacters.add(new int[]{i, i + len - 1}); if (ChatFormatUtils.isInRange(i, i + len - 1, ignoredAreas)) {
DebugCommand.SendDebugMessage("Range start is in ignored area, skipping");
return null; //Not setting finished to true, so it will go to the next match
}
ignoredAreas.add(new int[]{i, i + len - 1});
if (startedSection == null) { if (startedSection == null) {
DebugCommand.SendDebugMessage("Started range match from " + i + " to " + (i + len - 1)); DebugCommand.SendDebugMessage("Started range match from " + i + " to " + (i + len - 1));
DebugCommand.SendDebugMessage("With settings: " + settings); DebugCommand.SendDebugMessage("With settings: " + settings);
@ -41,11 +43,21 @@ public class RangeMatchProvider extends MatchProviderBase {
startedSection = new FormattedSection(settings, i, i + len - 1, Collections.emptyList()); startedSection = new FormattedSection(settings, i, i + len - 1, Collections.emptyList());
return null; return null;
} else { } else {
DebugCommand.SendDebugMessage("Finished range match from " + i + " to " + (i + len - 1)); var section = startedSection;
DebugCommand.SendDebugMessage("Finished range match from " + section.Start + " to " + (i + len - 1));
DebugCommand.SendDebugMessage("With settings: " + settings); DebugCommand.SendDebugMessage("With settings: " + settings);
ChatFormatUtils.sendMessageWithPointer(message, i, i + len - 1); ChatFormatUtils.sendMessageWithPointer(message, section.Start, i + len - 1);
startedSection.End = i + len - 1; section.End = i + len - 1;
return startedSection; removedCharacters.add(new int[]{section.Start, section.Start + len - 1});
removedCharacters.add(new int[]{i, i + len - 1});
startedSection = null; //Reset so next find creates a new one
return section;
} }
} }
@Override
public void resetSubclass() {
nextIndex = 0;
startedSection = null;
}
} }

View file

@ -35,7 +35,7 @@ public class RegexMatchProvider extends MatchProviderBase {
DebugCommand.SendDebugMessage("With settings: " + settings); DebugCommand.SendDebugMessage("With settings: " + settings);
ChatFormatUtils.sendMessageWithPointer(message, start, end); ChatFormatUtils.sendMessageWithPointer(message, start, end);
if (ChatFormatUtils.isInRange(start, end, ignoredAreas)) { if (ChatFormatUtils.isInRange(start, end, ignoredAreas)) {
DebugCommand.SendDebugMessage("Formatter is in ignored area, skipping"); DebugCommand.SendDebugMessage("Match is in ignored area, skipping");
return null; //Not setting finished to true, so it will go to the next match return null; //Not setting finished to true, so it will go to the next match
} }
ArrayList<String> groups = new ArrayList<>(); ArrayList<String> groups = new ArrayList<>();
@ -43,6 +43,12 @@ public class RegexMatchProvider extends MatchProviderBase {
groups.add(matcher.group(i + 1)); groups.add(matcher.group(i + 1));
if (groups.size() > 0) if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0)); DebugCommand.SendDebugMessage("First group: " + groups.get(0));
ignoredAreas.add(new int[]{start, end});
return new FormattedSection(settings, matcher.start(), matcher.end() - 1, groups); return new FormattedSection(settings, matcher.start(), matcher.end() - 1, groups);
} }
@Override
public void resetSubclass() {
matcher = null;
}
} }

View file

@ -13,6 +13,7 @@ public class StringMatchProvider extends MatchProviderBase {
@ToString.Exclude @ToString.Exclude
private final FormatSettings settings; private final FormatSettings settings;
private int nextIndex = 0; private int nextIndex = 0;
private boolean ignoreCase;
/** /**
* Matches the given strings in the order given * Matches the given strings in the order given
@ -20,15 +21,23 @@ public class StringMatchProvider extends MatchProviderBase {
* @param settings The format settings * @param settings The format settings
* @param strings The strings to match in the correct order * @param strings The strings to match in the correct order
*/ */
public StringMatchProvider(String name, FormatSettings settings, String... strings) { public StringMatchProvider(String name, FormatSettings settings, boolean ignoreCase, String... strings) {
super(name); super(name);
this.settings = settings; this.settings = settings;
this.strings = strings; this.strings = strings;
this.ignoreCase = ignoreCase;
if (ignoreCase) {
for (int i = 0; i < strings.length; i++) {
strings[i] = strings[i].toLowerCase();
}
}
} }
@Nullable @Nullable
@Override @Override
public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) { public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) {
if (ignoreCase)
message = message.toLowerCase();
int i = -1, len = 0; int i = -1, len = 0;
for (String string : strings) { for (String string : strings) {
i = message.indexOf(string, nextIndex); i = message.indexOf(string, nextIndex);
@ -40,9 +49,19 @@ public class StringMatchProvider extends MatchProviderBase {
return null; return null;
} }
nextIndex = i + len; nextIndex = i + len;
if (ChatFormatUtils.isInRange(i, i + len - 1, ignoredAreas)) {
DebugCommand.SendDebugMessage("String is in ignored area, skipping");
return null; //Not setting finished to true, so it will go to the next match
}
DebugCommand.SendDebugMessage("Found string match from " + i + " to " + (i + len - 1)); DebugCommand.SendDebugMessage("Found string match from " + i + " to " + (i + len - 1));
DebugCommand.SendDebugMessage("With settings: " + settings); DebugCommand.SendDebugMessage("With settings: " + settings);
ChatFormatUtils.sendMessageWithPointer(message, i, i + len - 1); ChatFormatUtils.sendMessageWithPointer(message, i, i + len - 1);
ignoredAreas.add(new int[]{i, i + len - 1});
return new FormattedSection(settings, i, i + len - 1, Collections.emptyList()); return new FormattedSection(settings, i, i + len - 1, Collections.emptyList());
} }
@Override
public void resetSubclass() {
nextIndex = 0;
}
} }