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(
new RangeMatchProvider("bold", "**", FormatSettings.builder().bold(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("italic2", "_", FormatSettings.builder().italic(true).build()),
new RangeMatchProvider("strikethrough", "~~", FormatSettings.builder().strikethrough(true).build()),
new RangeMatchProvider("spoiler", "||", FormatSettings.builder().obfuscated(true)
.onmatch((match, cf, fs) -> {
cf.setHoverText(match);
return match;
}).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)
.onmatch((match, builder, section) -> {
if (!pingedconsole) {
System.out.print("\007");
pingedconsole = true; // Will set it to false in ProcessChat
}
return match;
}).build(), "@console"),
return "@console";
}).build(), true, "@console"),
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 RegexMatchProvider("maskedLink", MASKED_LINK_PATTERN, FormatSettings.builder().underlined(true)
.onmatch((match, builder, section) -> {
@ -88,7 +88,7 @@ public class ChatProcessing {
var player = players.get(playerC);
playPingSound(player, ComponentManager.getIfEnabled(FormatterComponent.class));
return "@someone (" + player.getDisplayName() + "§r)";
}).build()));
}).build(), true, "@someone"));
private static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum())
.registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection())
@ -265,8 +265,8 @@ public class ChatProcessing {
System.out.println(message);
};
if (names.length > 0)
formatters.add(new StringMatchProvider("name", FormatSettings.builder().color(Color.Aqua)
if (names.length > 0) //Add as first so it handles special characters (_) - TODO: But after URLs
formatters.add(0, new StringMatchProvider("name", FormatSettings.builder().color(Color.Aqua)
.onmatch((match, builder, section) -> {
Player p = Bukkit.getPlayer(match);
Optional<String> pn = nottest ? Optional.empty()
@ -281,10 +281,10 @@ public class ChatProcessing {
}
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
}).build(), names));
}).build(), true, names));
if (nicknames.length > 0)
formatters.add(new StringMatchProvider("nickname", FormatSettings.builder().color(Color.Aqua)
if (nicknames.length > 0) //Add as first so it handles special characters
formatters.add(0, new StringMatchProvider("nickname", FormatSettings.builder().color(Color.Aqua)
.onmatch((match, builder, section) -> {
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()));
@ -299,7 +299,7 @@ public class ChatProcessing {
error.accept("Player nicknamed " + match.toLowerCase()
+ " not found in nickname map but was reported as online.");
return "§c" + match + "§r";
}).build(), nicknames));
}).build(), true, nicknames));
}
return formatters;
}

View file

@ -4,6 +4,7 @@ import buttondevteam.chat.commands.ucmds.admin.DebugCommand;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
public final class 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
*/
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;
/**
* 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
* again.
*
@ -40,6 +40,8 @@ public final class ChatFormatter {
*/
val remchars = new ArrayList<int[]>();
escapeThings(str, excluded, remchars);
createSections(formatters, str, sections, excluded, remchars, defaults);
sortSections(sections);
@ -51,18 +53,32 @@ public final class ChatFormatter {
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,
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
formatters.forEach(MatchProviderBase::reset); //Reset state information, as we aren't doing deep cloning
while (formatters.size() > 0) {
for (var iterator = formatters.iterator(); iterator.hasNext(); ) {
MatchProviderBase formatter = iterator.next();
DebugCommand.SendDebugMessage("Checking provider: " + formatter);
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);
if (formatter.isFinished())
if (formatter.isFinished()) {
DebugCommand.SendDebugMessage("Provider finished");
iterator.remove();
}
}
}
}
@ -103,12 +119,8 @@ public final class ChatFormatter {
FormattedSection section = new FormattedSection(firstSection.Settings, lastSection.Start, origend,
firstSection.Matches);
section.Settings.copyFrom(lastSection.Settings);
section.Matches.addAll(lastSection.Matches); // TODO: Clean
section.Matches.addAll(lastSection.Matches);
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.End = origend2;

View file

@ -19,11 +19,15 @@ public class FormatSettings {
public void copyFrom(FormatSettings settings) {
try {
for (var field : FormatSettings.class.getDeclaredFields())
if (field.getType() == boolean.class && field.getBoolean(settings))
field.setBoolean(this, true); //Set to true if either of them are true
else if (field.get(this) == null)
for (var field : FormatSettings.class.getDeclaredFields()) {
if (field.getType() == boolean.class) {
if (field.getBoolean(settings))
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));
}
}
} catch (Exception e) {
e.printStackTrace();
}

View file

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

View file

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

View file

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

View file

@ -24,16 +24,18 @@ public class RangeMatchProvider extends MatchProviderBase {
@Override
public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) {
int i, len;
do {
i = message.indexOf(pattern, nextIndex);
len = pattern.length();
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) == '\\');
i = message.indexOf(pattern, nextIndex);
len = pattern.length();
nextIndex = i + len; //Set for the next method call
if (i == -1) {
finished = true; //Won't find any more - unfinished sections will be garbage collected
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) {
DebugCommand.SendDebugMessage("Started range match from " + i + " to " + (i + len - 1));
DebugCommand.SendDebugMessage("With settings: " + settings);
@ -41,11 +43,21 @@ public class RangeMatchProvider extends MatchProviderBase {
startedSection = new FormattedSection(settings, i, i + len - 1, Collections.emptyList());
return null;
} 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);
ChatFormatUtils.sendMessageWithPointer(message, i, i + len - 1);
startedSection.End = i + len - 1;
return startedSection;
ChatFormatUtils.sendMessageWithPointer(message, section.Start, i + len - 1);
section.End = i + len - 1;
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);
ChatFormatUtils.sendMessageWithPointer(message, start, end);
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
}
ArrayList<String> groups = new ArrayList<>();
@ -43,6 +43,12 @@ public class RegexMatchProvider extends MatchProviderBase {
groups.add(matcher.group(i + 1));
if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0));
ignoredAreas.add(new int[]{start, end});
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
private final FormatSettings settings;
private int nextIndex = 0;
private boolean ignoreCase;
/**
* Matches the given strings in the order given
@ -20,15 +21,23 @@ public class StringMatchProvider extends MatchProviderBase {
* @param settings The format settings
* @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);
this.settings = settings;
this.strings = strings;
this.ignoreCase = ignoreCase;
if (ignoreCase) {
for (int i = 0; i < strings.length; i++) {
strings[i] = strings[i].toLowerCase();
}
}
}
@Nullable
@Override
public FormattedSection getNextSection(String message, ArrayList<int[]> ignoredAreas, ArrayList<int[]> removedCharacters) {
if (ignoreCase)
message = message.toLowerCase();
int i = -1, len = 0;
for (String string : strings) {
i = message.indexOf(string, nextIndex);
@ -40,9 +49,19 @@ public class StringMatchProvider extends MatchProviderBase {
return null;
}
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("With settings: " + settings);
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());
}
@Override
public void resetSubclass() {
nextIndex = 0;
}
}