Fixed cast error, NPE, masked links

Fixed config cast error
Fixed mcchat NPE
Implemented masked links
Added a test for it
This commit is contained in:
Norbi Peti 2019-01-19 20:26:39 +01:00
parent 0802de4b6f
commit 3a29010042
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
7 changed files with 459 additions and 447 deletions

View file

@ -46,7 +46,7 @@ public class ChatProcessing {
private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*");
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*");
private static final Pattern CODE_PATTERN = Pattern.compile("`");
private static final Pattern MASKED_LINK_PATTERN = Pattern.compile("\\[([^\\[\\]])]\\(([^()])\\)");
private static final Pattern MASKED_LINK_PATTERN = Pattern.compile("\\[([^\\[\\]]+)]\\(([^()]+)\\)");
private static final Pattern SOMEONE_PATTERN = Pattern.compile("@someone"); //TODO
private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~");
private static final Color[] RainbowPresserColors = new Color[]{Color.Red, Color.Gold, Color.Yellow, Color.Green,
@ -63,9 +63,8 @@ public class ChatProcessing {
.build(),
ChatFormatter.builder().regex(STRIKETHROUGH_PATTERN).strikethrough(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range)
.build(),
ESCAPE_FORMATTER, ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").type(ChatFormatter.Type.Excluder).build(),
ChatFormatter.builder().regex(NULL_MENTION_PATTERN).color(Color.DarkRed).build(), // Properly added a bug as a feature
ChatFormatter.builder().regex(CONSOLE_PING_PATTERN).color(Color.Aqua).onmatch((match, builder) -> {
ESCAPE_FORMATTER, ChatFormatter.builder().regex(NULL_MENTION_PATTERN).color(Color.DarkRed).build(), // Properly added a bug as a feature
ChatFormatter.builder().regex(CONSOLE_PING_PATTERN).color(Color.Aqua).onmatch((match, builder, section) -> {
if (!pingedconsole) {
System.out.print("\007");
pingedconsole = true; // Will set it to false in ProcessChat
@ -78,9 +77,14 @@ public class ChatProcessing {
ChatFormatter.builder().regex(CYAN_PATTERN).color(Color.Aqua).build(), // #55
ChatFormatter.builder().regex(CODE_PATTERN).color(Color.DarkGray).removeCharCount((short) 1).type(ChatFormatter.Type.Range)
.build(),
ChatFormatter.builder().regex(MASKED_LINK_PATTERN).underlined(true).onmatch((match, builder) -> {
return match; // TODO!
}).build());
ChatFormatter.builder().regex(MASKED_LINK_PATTERN).underlined(true).onmatch((match, builder, section) -> {
String text, link;
if (section.Matches.size() < 2 || (text = section.Matches.get(0)).length() == 0 || (link = section.Matches.get(1)).length() == 0)
return "";
builder.setOpenlink(link);
return text;
}).type(ChatFormatter.Type.Excluder).build(),
ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").type(ChatFormatter.Type.Excluder).build());
private static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(TellrawSerializableEnum.class, new TellrawSerializer.TwEnum())
.registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection())
@ -124,7 +128,7 @@ public class ChatProcessing {
ArrayList<ChatFormatter> formatters = addFormatters(colormode);
if (colormode == channel.Color().get() && mp != null && mp.RainbowPresserColorMode) { // Only overwrite channel color
final AtomicInteger rpc = new AtomicInteger(0);
formatters.add(ChatFormatter.builder().color(colormode).onmatch((match, cf) -> {
formatters.add(ChatFormatter.builder().color(colormode).onmatch((match, cf, s) -> {
cf.setColor(RainbowPresserColors[rpc.getAndUpdate(i -> ++i < RainbowPresserColors.length ? i : 0)]);
return match;
}).build());
@ -275,7 +279,7 @@ public class ChatProcessing {
};
formatters.add(ChatFormatter.builder().regex(Pattern.compile(namesb.toString())).color(Color.Aqua)
.onmatch((match, builder) -> {
.onmatch((match, builder, section) -> {
Player p = Bukkit.getPlayer(match);
Optional<String> pn = nottest ? Optional.empty()
: Arrays.stream(testPlayers).filter(tp -> tp.equalsIgnoreCase(match)).findAny();
@ -297,7 +301,7 @@ public class ChatProcessing {
if (addNickFormatter)
formatters.add(ChatFormatter.builder().regex((Pattern.compile(nicksb.toString()))).color(Color.Aqua)
.onmatch((match, builder) -> {
.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()));
if (p == null) {

View file

@ -42,7 +42,7 @@ public class TownColorComponent extends Component {
public static Map<String, Color> NationColor = new HashMap<>();
public ConfigData<Byte> colorCount() {
return getConfig().getData("colorCount", (byte) 1, cc -> (byte) cc, cc -> (int) cc);
return getConfig().getData("colorCount", (byte) 1, cc -> ((Integer) cc).byteValue(), Byte::intValue);
}
public ConfigData<Boolean> useNationColors() { //TODO

View file

@ -1,394 +1,398 @@
package buttondevteam.chat.formatting;
import buttondevteam.chat.ChatProcessing;
import buttondevteam.chat.commands.ucmds.admin.DebugCommand;
import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.Priority;
import lombok.Builder;
import lombok.Data;
import lombok.val;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart)} 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.
*
* @author NorbiPeti
*
*/
@Data
@Builder
public final class ChatFormatter {
Pattern regex;
boolean italic;
boolean bold;
boolean underlined;
boolean strikethrough;
boolean obfuscated;
Color color;
BiFunction<String, ChatFormatter, String> onmatch;
String openlink;
@Builder.Default
Priority priority = Priority.Normal;
@Builder.Default
short removeCharCount = 0;
@Builder.Default
Type type = Type.Normal;
public enum Type {
Normal,
/**
* Matches a start and an end section which gets converted to one section (for example see italics)
*/
Range,
/**
* Exclude matching area from further processing (besides this formatter)
*/
Excluder
}
public static void Combine(List<ChatFormatter> formatters, String str, TellrawPart tp) {
/*
* This method assumes that there is always a global formatter
*/
header("ChatFormatter.Combine begin");
ArrayList<FormattedSection> sections = new ArrayList<FormattedSection>();
for (ChatFormatter formatter : formatters) {
if (formatter.type != Type.Excluder)
continue;
Matcher matcher = formatter.regex.matcher(str);
while (matcher.find()) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
DebugCommand.SendDebugMessage("With excluder formatter:" + formatter);
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
}
ArrayList<String> groups = new ArrayList<String>();
for (int i = 0; i < matcher.groupCount(); i++)
groups.add(matcher.group(i + 1));
if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0));
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
formatter.type);
sections.add(section);
}
}
header("Section creation (excluders done)");
for (ChatFormatter formatter : formatters) {
if (formatter.type == Type.Excluder)
continue;
Matcher matcher = formatter.regex.matcher(str);
while (matcher.find()) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
DebugCommand.SendDebugMessage("With formatter:" + formatter);
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
}
ArrayList<String> groups = new ArrayList<String>();
for (int i = 0; i < matcher.groupCount(); i++)
groups.add(matcher.group(i + 1));
if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0));
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
formatter.type);
sections.add(section);
}
}
sections.sort(
(s1, s2) -> s1.Start == s2.Start
? s1.End == s2.End ? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
s1.Formatters.get(0).priority.GetValue()) : Integer.compare(s2.End, s1.End)
: Integer.compare(s1.Start, s2.Start));
/**
* 0: Start - 1: End index
*/
val remchars = new ArrayList<int[]>();
header("Range section conversion");
ArrayList<FormattedSection> combined = new ArrayList<>();
Map<ChatFormatter, FormattedSection> nextSection = new HashMap<>();
boolean escaped = false;
int takenStart = -1, takenEnd = -1;
ChatFormatter takenFormatter = null;
for (int i = 0; i < sections.size(); i++) {
// Set ending to -1 until closed with another 1 long "section" - only do this if IsRange is true
final FormattedSection section = sections.get(i);
if (section.type!=Type.Range) {
escaped = section.Formatters.contains(ChatProcessing.ESCAPE_FORMATTER) && !escaped; // Enable escaping on first \, disable on second
if (escaped) {// Don't add the escape character
remchars.add(new int[]{section.Start, section.Start});
DebugCommand.SendDebugMessage("Found escaper section: " + section);
} else {
combined.add(section); // The above will delete the \
DebugCommand.SendDebugMessage("Added section: " + section);
}
sendMessageWithPointer(str, section.Start, section.End);
continue;
}
if (!escaped) {
if (combined.stream().anyMatch(s -> section.type != Type.Range && (s.Start == section.Start
|| (s.Start < section.Start ? s.End >= section.Start : s.Start <= section.End)))) {
DebugCommand.SendDebugMessage("Range " + section + " overlaps with a combined section, ignoring.");
sendMessageWithPointer(str, section.Start, section.End);
continue;
}
if (section.Start == takenStart || (section.Start > takenStart && section.Start < takenEnd)) {
/*
* if (nextSection.containsKey(section.Formatters.get(0)) ? section.RemCharFromStart <= takenEnd - takenStart : section.RemCharFromStart > takenEnd - takenStart) {
*/
if (section.Formatters.get(0).removeCharCount < takenEnd - takenStart) {
DebugCommand.SendDebugMessage("Lose: " + section);
sendMessageWithPointer(str, section.Start, section.End);
DebugCommand.SendDebugMessage("And win: " + takenFormatter);
continue; // The current section loses
}
nextSection.remove(takenFormatter); // The current section wins
DebugCommand.SendDebugMessage("Win: " + section);
sendMessageWithPointer(str, section.Start, section.End);
DebugCommand.SendDebugMessage("And lose: " + takenFormatter);
}
takenStart = section.Start;
takenEnd = section.Start + section.Formatters.get(0).removeCharCount;
takenFormatter = section.Formatters.get(0);
if (nextSection.containsKey(section.Formatters.get(0))) {
FormattedSection s = nextSection.remove(section.Formatters.get(0));
// section: the ending marker section - s: the to-be full section
s.End = takenEnd - 1; //Take the remCharCount into account as well
// s.IsRange = false; // IsRange means it's a 1 long section indicating a start or an end
combined.add(s);
DebugCommand.SendDebugMessage("Finished section: " + s);
sendMessageWithPointer(str, s.Start, s.End);
} else {
DebugCommand.SendDebugMessage("Adding next section: " + section);
sendMessageWithPointer(str, section.Start, section.End);
nextSection.put(section.Formatters.get(0), section);
}
DebugCommand
.SendDebugMessage("New area taken: (" + takenStart + "-" + takenEnd + ") " + takenFormatter);
sendMessageWithPointer(str, takenStart, takenEnd);
} else {
DebugCommand.SendDebugMessage("Skipping section: " + section); // This will keep the text (character)
sendMessageWithPointer(str, section.Start, section.End);
escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*'
}
}
//Do not finish unfinished sections, ignore them
sections = combined;
header("Adding remove chars (RC)"); // Important to add after the range section conversion
sections.stream()
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.Start, fs.Start + rcc - 1}))
.forEach(rc -> remchars.add(rc));
sections.stream()
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.End - rcc + 1, fs.End}))
.forEach(rc -> remchars.add(rc));
DebugCommand.SendDebugMessage("Added remchars:");
DebugCommand
.SendDebugMessage(remchars.stream().map(rc -> Arrays.toString(rc)).collect(Collectors.joining("; ")));
header("Section combining");
boolean cont = true;
boolean found = false;
for (int i = 1; cont;) {
int nextindex = i + 1;
if (sections.size() < 2)
break;
DebugCommand.SendDebugMessage("i: " + i);
FormattedSection firstSection = sections.get(i - 1);
DebugCommand.SendDebugMessage("Combining sections " + firstSection);
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
DebugCommand.SendDebugMessage(" and " + sections.get(i));
sendMessageWithPointer(str, sections.get(i).Start, sections.get(i).End);
if (firstSection.Start == sections.get(i).Start && firstSection.End == sections.get(i).End) {
firstSection.Formatters.addAll(sections.get(i).Formatters);
firstSection.Matches.addAll(sections.get(i).Matches);
DebugCommand.SendDebugMessage("To section " + firstSection);
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
sections.remove(i);
found = true;
} else if (firstSection.End > sections.get(i).Start && firstSection.Start < sections.get(i).End) {
int origend = firstSection.End;
firstSection.End = sections.get(i).Start - 1;
int origend2 = sections.get(i).End;
boolean switchends;
if (switchends = origend2 < origend) {
int tmp = origend;
origend = origend2;
origend2 = tmp;
}
FormattedSection section = new FormattedSection(firstSection.Formatters, sections.get(i).Start, origend,
firstSection.Matches, Type.Normal);
section.Formatters.addAll(sections.get(i).Formatters);
section.Matches.addAll(sections.get(i).Matches); // TODO: Clean
sections.add(i, section);
nextindex++;
FormattedSection thirdFormattedSection = sections.get(i + 1);
if (switchends) { // Use the properties of the first section not the second one
thirdFormattedSection.Formatters.clear();
thirdFormattedSection.Formatters.addAll(firstSection.Formatters);
thirdFormattedSection.Matches.clear();
thirdFormattedSection.Matches.addAll(firstSection.Matches);
}
thirdFormattedSection.Start = origend + 1;
thirdFormattedSection.End = origend2;
ArrayList<FormattedSection> sts = sections;
Predicate<FormattedSection> removeIfNeeded = s -> {
if (s.Start < 0 || s.End < 0 || s.Start > s.End) {
DebugCommand.SendDebugMessage("Removing section: " + s);
sendMessageWithPointer(str, s.Start, s.End);
sts.remove(s);
return true;
}
return false;
};
DebugCommand.SendDebugMessage("To sections");
if (!removeIfNeeded.test(firstSection)) {
DebugCommand.SendDebugMessage(" 1:" + firstSection + "");
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
}
if (!removeIfNeeded.test(section)) {
DebugCommand.SendDebugMessage(" 2:" + section + "");
sendMessageWithPointer(str, section.Start, section.End);
}
if (!removeIfNeeded.test(thirdFormattedSection)) {
DebugCommand.SendDebugMessage(" 3:" + thirdFormattedSection);
sendMessageWithPointer(str, thirdFormattedSection.Start, thirdFormattedSection.End);
}
found = true;
}
for (int j = i - 1; j <= i + 1; j++) {
if (j < sections.size() && sections.get(j).End < sections.get(j).Start) {
DebugCommand.SendDebugMessage("Removing section: " + sections.get(j));
sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End);
sections.remove(j);
j--;
found = true;
}
}
i = nextindex - 1;
i++;
if (i >= sections.size()) {
if (found) {
i = 1;
found = false;
sections.sort(
(s1, s2) -> s1.Start == s2.Start
? s1.End == s2.End
? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
s1.Formatters.get(0).priority.GetValue())
: Integer.compare(s2.End, s1.End)
: Integer.compare(s1.Start, s2.Start));
} else
cont = false;
}
}
header("Section applying");
TellrawPart lasttp = null; String lastlink = null;
for (FormattedSection section : sections) {
DebugCommand.SendDebugMessage("Applying section: " + section);
String originaltext;
int start = section.Start, end = section.End;
DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end);
sendMessageWithPointer(str, start, end);
val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny();
val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny();
val rci = remchars.stream().filter(rc -> start < rc[0] && rc[1] < end).toArray(int[][]::new);
int s = start, e = end;
if (rcs.isPresent())
s = rcs.get()[1] + 1;
if (rce.isPresent())
e = rce.get()[0] - 1;
DebugCommand.SendDebugMessage("After RC - Start: " + s + " - End: " + e);
if (e - s < 0) { //e-s==0 means the end char is the same as start char, so one char message
DebugCommand.SendDebugMessage("Skipping section because of remchars (length would be " + (e - s + 1) + ")");
continue;
}
originaltext = str.substring(s, e + 1);
val sb = new StringBuilder(originaltext);
for (int x = rci.length - 1; x >= 0; x--)
sb.delete(rci[x][0] - start - 1, rci[x][1] - start); //Delete going backwards
originaltext = sb.toString();
DebugCommand.SendDebugMessage("Section text: " + originaltext);
String openlink = null;
section.Formatters.sort(Comparator.comparing(cf2 -> cf2.priority.GetValue())); //Apply the highest last, to overwrite previous ones
TellrawPart newtp = new TellrawPart("");
for (ChatFormatter formatter : section.Formatters) {
DebugCommand.SendDebugMessage("Applying formatter: " + formatter);
if (formatter.onmatch != null)
originaltext = formatter.onmatch.apply(originaltext, formatter);
if (formatter.color != null)
newtp.setColor(formatter.color);
if (formatter.bold)
newtp.setBold(formatter.bold);
if (formatter.italic)
newtp.setItalic(formatter.italic);
if (formatter.underlined)
newtp.setUnderlined(formatter.underlined);
if (formatter.strikethrough)
newtp.setStrikethrough(formatter.strikethrough);
if (formatter.obfuscated)
newtp.setObfuscated(formatter.obfuscated);
if (formatter.openlink != null)
openlink = formatter.openlink;
}
if (lasttp != null && newtp.getColor() == lasttp.getColor()
&& newtp.isBold() == lasttp.isBold()
&& newtp.isItalic() == lasttp.isItalic()
&& newtp.isUnderlined() == lasttp.isUnderlined()
&& newtp.isStrikethrough() == lasttp.isStrikethrough()
&& newtp.isObfuscated() == lasttp.isObfuscated()
&& (openlink == null ? lastlink == null : openlink.equals(lastlink))) {
DebugCommand.SendDebugMessage("This part has the same properties as the previous one, combining.");
lasttp.setText(lasttp.getText() + originaltext);
continue; //Combine parts with the same properties
}
newtp.setText(originaltext);
if (openlink != null && openlink.length() > 0) {
newtp.setClickEvent(TellrawEvent.create(TellrawEvent.ClickAction.OPEN_URL,
(section.Matches.size() > 0 ? openlink.replace("$1", section.Matches.get(0)) : openlink)))
.setHoverEvent(TellrawEvent.create(TellrawEvent.HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)));
}
tp.addExtra(newtp);
lasttp = newtp;
}
header("ChatFormatter.Combine done");
}
private static void sendMessageWithPointer(String str, int... pointer) {
DebugCommand.SendDebugMessage(str);
StringBuilder sb = new StringBuilder(str.length());
Arrays.sort(pointer);
for (int i = 0; i < pointer.length; i++) {
for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++)
sb.append(' ');
if (pointer[i] == (i > 0 ? pointer[i - 1] : -1))
continue;
sb.append('^');
}
DebugCommand.SendDebugMessage(sb.toString());
}
private static void header(String message) {
DebugCommand.SendDebugMessage("\n--------\n" + message + "\n--------\n");
}
}
package buttondevteam.chat.formatting;
import buttondevteam.chat.ChatProcessing;
import buttondevteam.chat.commands.ucmds.admin.DebugCommand;
import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.Priority;
import lombok.Builder;
import lombok.Data;
import lombok.val;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* A {@link ChatFormatter} shows what formatting to use based on regular expressions. {@link ChatFormatter#Combine(List, String, TellrawPart)} 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.
*
* @author NorbiPeti
*
*/
@Data
@Builder
public final class ChatFormatter {
Pattern regex;
boolean italic;
boolean bold;
boolean underlined;
boolean strikethrough;
boolean obfuscated;
Color color;
TriFunc<String, ChatFormatter, FormattedSection, String> onmatch;
String openlink;
@Builder.Default
Priority priority = Priority.Normal;
@Builder.Default
short removeCharCount = 0;
@Builder.Default
Type type = Type.Normal;
public enum Type {
Normal,
/**
* Matches a start and an end section which gets converted to one section (for example see italics)
*/
Range,
/**
* Exclude matching area from further processing (besides this formatter)
*/
Excluder
}
@FunctionalInterface
public interface TriFunc<T1, T2, T3, R> {
R apply(T1 x1, T2 x2, T3 x3);
}
public static void Combine(List<ChatFormatter> formatters, String str, TellrawPart tp) {
/*
* This method assumes that there is always a global formatter
*/
header("ChatFormatter.Combine begin");
ArrayList<FormattedSection> sections = new ArrayList<FormattedSection>();
for (ChatFormatter formatter : formatters) {
if (formatter.type != Type.Excluder)
continue;
Matcher matcher = formatter.regex.matcher(str);
while (matcher.find()) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
DebugCommand.SendDebugMessage("With excluder formatter:" + formatter);
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
}
ArrayList<String> groups = new ArrayList<String>();
for (int i = 0; i < matcher.groupCount(); i++)
groups.add(matcher.group(i + 1));
if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0));
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
formatter.type);
sections.add(section);
}
}
header("Section creation (excluders done)");
for (ChatFormatter formatter : formatters) {
if (formatter.type == Type.Excluder)
continue;
Matcher matcher = formatter.regex.matcher(str);
while (matcher.find()) {
DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1));
DebugCommand.SendDebugMessage("With formatter:" + formatter);
sendMessageWithPointer(str, matcher.start(), matcher.end() - 1);
if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) {
DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder");
continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards
}
ArrayList<String> groups = new ArrayList<String>();
for (int i = 0; i < matcher.groupCount(); i++)
groups.add(matcher.group(i + 1));
if (groups.size() > 0)
DebugCommand.SendDebugMessage("First group: " + groups.get(0));
FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups,
formatter.type);
sections.add(section);
}
}
sections.sort(
(s1, s2) -> s1.Start == s2.Start
? s1.End == s2.End ? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
s1.Formatters.get(0).priority.GetValue()) : Integer.compare(s2.End, s1.End)
: Integer.compare(s1.Start, s2.Start));
/**
* 0: Start - 1: End index
*/
val remchars = new ArrayList<int[]>();
header("Range section conversion");
ArrayList<FormattedSection> combined = new ArrayList<>();
Map<ChatFormatter, FormattedSection> nextSection = new HashMap<>();
boolean escaped = false;
int takenStart = -1, takenEnd = -1;
ChatFormatter takenFormatter = null;
for (int i = 0; i < sections.size(); i++) {
// Set ending to -1 until closed with another 1 long "section" - only do this if IsRange is true
final FormattedSection section = sections.get(i);
if (section.type!=Type.Range) {
escaped = section.Formatters.contains(ChatProcessing.ESCAPE_FORMATTER) && !escaped; // Enable escaping on first \, disable on second
if (escaped) {// Don't add the escape character
remchars.add(new int[]{section.Start, section.Start});
DebugCommand.SendDebugMessage("Found escaper section: " + section);
} else {
combined.add(section); // The above will delete the \
DebugCommand.SendDebugMessage("Added section: " + section);
}
sendMessageWithPointer(str, section.Start, section.End);
continue;
}
if (!escaped) {
if (combined.stream().anyMatch(s -> section.type != Type.Range && (s.Start == section.Start
|| (s.Start < section.Start ? s.End >= section.Start : s.Start <= section.End)))) {
DebugCommand.SendDebugMessage("Range " + section + " overlaps with a combined section, ignoring.");
sendMessageWithPointer(str, section.Start, section.End);
continue;
}
if (section.Start == takenStart || (section.Start > takenStart && section.Start < takenEnd)) {
/*
* if (nextSection.containsKey(section.Formatters.get(0)) ? section.RemCharFromStart <= takenEnd - takenStart : section.RemCharFromStart > takenEnd - takenStart) {
*/
if (section.Formatters.get(0).removeCharCount < takenEnd - takenStart) {
DebugCommand.SendDebugMessage("Lose: " + section);
sendMessageWithPointer(str, section.Start, section.End);
DebugCommand.SendDebugMessage("And win: " + takenFormatter);
continue; // The current section loses
}
nextSection.remove(takenFormatter); // The current section wins
DebugCommand.SendDebugMessage("Win: " + section);
sendMessageWithPointer(str, section.Start, section.End);
DebugCommand.SendDebugMessage("And lose: " + takenFormatter);
}
takenStart = section.Start;
takenEnd = section.Start + section.Formatters.get(0).removeCharCount;
takenFormatter = section.Formatters.get(0);
if (nextSection.containsKey(section.Formatters.get(0))) {
FormattedSection s = nextSection.remove(section.Formatters.get(0));
// section: the ending marker section - s: the to-be full section
s.End = takenEnd - 1; //Take the remCharCount into account as well
// s.IsRange = false; // IsRange means it's a 1 long section indicating a start or an end
combined.add(s);
DebugCommand.SendDebugMessage("Finished section: " + s);
sendMessageWithPointer(str, s.Start, s.End);
} else {
DebugCommand.SendDebugMessage("Adding next section: " + section);
sendMessageWithPointer(str, section.Start, section.End);
nextSection.put(section.Formatters.get(0), section);
}
DebugCommand
.SendDebugMessage("New area taken: (" + takenStart + "-" + takenEnd + ") " + takenFormatter);
sendMessageWithPointer(str, takenStart, takenEnd);
} else {
DebugCommand.SendDebugMessage("Skipping section: " + section); // This will keep the text (character)
sendMessageWithPointer(str, section.Start, section.End);
escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*'
}
}
//Do not finish unfinished sections, ignore them
sections = combined;
header("Adding remove chars (RC)"); // Important to add after the range section conversion
sections.stream()
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.Start, fs.Start + rcc - 1}))
.forEach(rc -> remchars.add(rc));
sections.stream()
.flatMap(fs -> fs.Formatters.stream().filter(cf -> cf.removeCharCount > 0)
.mapToInt(cf -> cf.removeCharCount).mapToObj(rcc -> new int[]{fs.End - rcc + 1, fs.End}))
.forEach(rc -> remchars.add(rc));
DebugCommand.SendDebugMessage("Added remchars:");
DebugCommand
.SendDebugMessage(remchars.stream().map(rc -> Arrays.toString(rc)).collect(Collectors.joining("; ")));
header("Section combining");
boolean cont = true;
boolean found = false;
for (int i = 1; cont;) {
int nextindex = i + 1;
if (sections.size() < 2)
break;
DebugCommand.SendDebugMessage("i: " + i);
FormattedSection firstSection = sections.get(i - 1);
DebugCommand.SendDebugMessage("Combining sections " + firstSection);
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
DebugCommand.SendDebugMessage(" and " + sections.get(i));
sendMessageWithPointer(str, sections.get(i).Start, sections.get(i).End);
if (firstSection.Start == sections.get(i).Start && firstSection.End == sections.get(i).End) {
firstSection.Formatters.addAll(sections.get(i).Formatters);
firstSection.Matches.addAll(sections.get(i).Matches);
DebugCommand.SendDebugMessage("To section " + firstSection);
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
sections.remove(i);
found = true;
} else if (firstSection.End > sections.get(i).Start && firstSection.Start < sections.get(i).End) {
int origend = firstSection.End;
firstSection.End = sections.get(i).Start - 1;
int origend2 = sections.get(i).End;
boolean switchends;
if (switchends = origend2 < origend) {
int tmp = origend;
origend = origend2;
origend2 = tmp;
}
FormattedSection section = new FormattedSection(firstSection.Formatters, sections.get(i).Start, origend,
firstSection.Matches, Type.Normal);
section.Formatters.addAll(sections.get(i).Formatters);
section.Matches.addAll(sections.get(i).Matches); // TODO: Clean
sections.add(i, section);
nextindex++;
FormattedSection thirdFormattedSection = sections.get(i + 1);
if (switchends) { // Use the properties of the first section not the second one
thirdFormattedSection.Formatters.clear();
thirdFormattedSection.Formatters.addAll(firstSection.Formatters);
thirdFormattedSection.Matches.clear();
thirdFormattedSection.Matches.addAll(firstSection.Matches);
}
thirdFormattedSection.Start = origend + 1;
thirdFormattedSection.End = origend2;
ArrayList<FormattedSection> sts = sections;
Predicate<FormattedSection> removeIfNeeded = s -> {
if (s.Start < 0 || s.End < 0 || s.Start > s.End) {
DebugCommand.SendDebugMessage("Removing section: " + s);
sendMessageWithPointer(str, s.Start, s.End);
sts.remove(s);
return true;
}
return false;
};
DebugCommand.SendDebugMessage("To sections");
if (!removeIfNeeded.test(firstSection)) {
DebugCommand.SendDebugMessage(" 1:" + firstSection + "");
sendMessageWithPointer(str, firstSection.Start, firstSection.End);
}
if (!removeIfNeeded.test(section)) {
DebugCommand.SendDebugMessage(" 2:" + section + "");
sendMessageWithPointer(str, section.Start, section.End);
}
if (!removeIfNeeded.test(thirdFormattedSection)) {
DebugCommand.SendDebugMessage(" 3:" + thirdFormattedSection);
sendMessageWithPointer(str, thirdFormattedSection.Start, thirdFormattedSection.End);
}
found = true;
}
for (int j = i - 1; j <= i + 1; j++) {
if (j < sections.size() && sections.get(j).End < sections.get(j).Start) {
DebugCommand.SendDebugMessage("Removing section: " + sections.get(j));
sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End);
sections.remove(j);
j--;
found = true;
}
}
i = nextindex - 1;
i++;
if (i >= sections.size()) {
if (found) {
i = 1;
found = false;
sections.sort(
(s1, s2) -> s1.Start == s2.Start
? s1.End == s2.End
? Integer.compare(s2.Formatters.get(0).priority.GetValue(),
s1.Formatters.get(0).priority.GetValue())
: Integer.compare(s2.End, s1.End)
: Integer.compare(s1.Start, s2.Start));
} else
cont = false;
}
}
header("Section applying");
TellrawPart lasttp = null; String lastlink = null;
for (FormattedSection section : sections) {
DebugCommand.SendDebugMessage("Applying section: " + section);
String originaltext;
int start = section.Start, end = section.End;
DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end);
sendMessageWithPointer(str, start, end);
val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny();
val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny();
val rci = remchars.stream().filter(rc -> start < rc[0] && rc[1] < end).toArray(int[][]::new);
int s = start, e = end;
if (rcs.isPresent())
s = rcs.get()[1] + 1;
if (rce.isPresent())
e = rce.get()[0] - 1;
DebugCommand.SendDebugMessage("After RC - Start: " + s + " - End: " + e);
if (e - s < 0) { //e-s==0 means the end char is the same as start char, so one char message
DebugCommand.SendDebugMessage("Skipping section because of remchars (length would be " + (e - s + 1) + ")");
continue;
}
originaltext = str.substring(s, e + 1);
val sb = new StringBuilder(originaltext);
for (int x = rci.length - 1; x >= 0; x--)
sb.delete(rci[x][0] - start - 1, rci[x][1] - start); //Delete going backwards
originaltext = sb.toString();
DebugCommand.SendDebugMessage("Section text: " + originaltext);
String openlink = null;
section.Formatters.sort(Comparator.comparing(cf2 -> cf2.priority.GetValue())); //Apply the highest last, to overwrite previous ones
TellrawPart newtp = new TellrawPart("");
for (ChatFormatter formatter : section.Formatters) {
DebugCommand.SendDebugMessage("Applying formatter: " + formatter);
if (formatter.onmatch != null)
originaltext = formatter.onmatch.apply(originaltext, formatter, section);
if (formatter.color != null)
newtp.setColor(formatter.color);
if (formatter.bold)
newtp.setBold(formatter.bold);
if (formatter.italic)
newtp.setItalic(formatter.italic);
if (formatter.underlined)
newtp.setUnderlined(formatter.underlined);
if (formatter.strikethrough)
newtp.setStrikethrough(formatter.strikethrough);
if (formatter.obfuscated)
newtp.setObfuscated(formatter.obfuscated);
if (formatter.openlink != null)
openlink = formatter.openlink;
}
if (lasttp != null && newtp.getColor() == lasttp.getColor()
&& newtp.isBold() == lasttp.isBold()
&& newtp.isItalic() == lasttp.isItalic()
&& newtp.isUnderlined() == lasttp.isUnderlined()
&& newtp.isStrikethrough() == lasttp.isStrikethrough()
&& newtp.isObfuscated() == lasttp.isObfuscated()
&& Objects.equals(openlink, lastlink)) {
DebugCommand.SendDebugMessage("This part has the same properties as the previous one, combining.");
lasttp.setText(lasttp.getText() + originaltext);
continue; //Combine parts with the same properties
}
newtp.setText(originaltext);
if (openlink != null && openlink.length() > 0) {
newtp.setClickEvent(TellrawEvent.create(TellrawEvent.ClickAction.OPEN_URL,
(section.Matches.size() > 0 ? openlink.replace("$1", section.Matches.get(0)) : openlink)))
.setHoverEvent(TellrawEvent.create(TellrawEvent.HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)));
}
tp.addExtra(newtp);
lasttp = newtp;
}
header("ChatFormatter.Combine done");
}
private static void sendMessageWithPointer(String str, int... pointer) {
DebugCommand.SendDebugMessage(str);
StringBuilder sb = new StringBuilder(str.length());
Arrays.sort(pointer);
for (int i = 0; i < pointer.length; i++) {
for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++)
sb.append(' ');
if (pointer[i] == (i > 0 ? pointer[i - 1] : -1))
continue;
sb.append('^');
}
DebugCommand.SendDebugMessage(sb.toString());
}
private static void header(String message) {
DebugCommand.SendDebugMessage("\n--------\n" + message + "\n--------\n");
}
}

View file

@ -1,36 +1,36 @@
package buttondevteam.chat.formatting;
import java.util.ArrayList;
import java.util.Collection;
class FormattedSection {
int Start;
int End;
ArrayList<ChatFormatter> Formatters = new ArrayList<ChatFormatter>();
ArrayList<String> Matches = new ArrayList<String>();
ChatFormatter.Type type;
FormattedSection(ChatFormatter formatter, int start, int end, ArrayList<String> matches, ChatFormatter.Type type) {
Start = start;
End = end;
Formatters.add(formatter);
Matches.addAll(matches);
this.type = type;
}
FormattedSection(Collection<ChatFormatter> formatters, int start, int end, ArrayList<String> matches,
ChatFormatter.Type type) {
Start = start;
End = end;
Formatters.addAll(formatters);
Matches.addAll(matches);
this.type = type;
}
@Override
public String toString() {
return "Section(" + Start + ", " + End + ", formatters: " +
Formatters.toString() + ", matches: " + Matches.toString() + ", " +
type + ")";
}
package buttondevteam.chat.formatting;
import java.util.ArrayList;
import java.util.Collection;
public class FormattedSection {
public int Start;
public int End;
public ArrayList<ChatFormatter> Formatters = new ArrayList<ChatFormatter>();
public ArrayList<String> Matches = new ArrayList<String>();
public ChatFormatter.Type type;
FormattedSection(ChatFormatter formatter, int start, int end, ArrayList<String> matches, ChatFormatter.Type type) {
Start = start;
End = end;
Formatters.add(formatter);
Matches.addAll(matches);
this.type = type;
}
FormattedSection(Collection<ChatFormatter> formatters, int start, int end, ArrayList<String> matches,
ChatFormatter.Type type) {
Start = start;
End = end;
Formatters.addAll(formatters);
Matches.addAll(matches);
this.type = type;
}
@Override
public String toString() {
return "Section(" + Start + ", " + End + ", formatters: " +
Formatters.toString() + ", matches: " + Matches.toString() + ", " +
type + ")";
}
}

View file

@ -12,7 +12,6 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.lib.player.TBMCPlayerJoinEvent;
import buttondevteam.lib.player.TBMCPlayerLoadEvent;
import buttondevteam.lib.player.TBMCPlayerSaveEvent;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -32,7 +31,7 @@ public class PlayerJoinLeaveListener implements Listener {
@EventHandler
public void onPlayerTBMCJoin(TBMCPlayerJoinEvent e) {
ChatPlayer cp = e.GetPlayer().asPluginPlayer(ChatPlayer.class);
Player p = Bukkit.getPlayer(cp.getUUID());
Player p = e.getPlayer();
if (ComponentManager.isEnabled(FlairComponent.class)) {
if (!cp.FlairState().get().equals(FlairStates.NoComment)) {

View file

@ -37,9 +37,9 @@ depend:
- Votifier
- Vault
- ButtonCore
- Dynmap-Towny
soft-depend:
- Minigames
- Dynmap-Towny
permissions:
tbmc.admin:
description: Gives access to /un- commands and /u admin commands

View file

@ -66,10 +66,15 @@ public class ChatFormatIT {
list.add(new ChatFormatIT(sender, "*test _test_ test*", new TellrawPart("test ").setItalic(true).setColor(Color.White),
new TellrawPart("test").setItalic(true).setUnderlined(true).setColor(Color.White), new TellrawPart(" test").setItalic(true).setColor(Color.White)));
list.add(new ChatFormatIT(sender, "https://norbipeti.github.io/test?test&test#test", new TellrawPart("https://norbipeti.github.io/test?test&test#test")
.setColor(Color.White).setUnderlined(true)
.setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)))
.setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test?test&test#test"))));
.setColor(Color.White).setUnderlined(true)
.setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)))
.setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test?test&test#test"))));
list.add(new ChatFormatIT(sender, "[hmm](https://norbipeti.github.io/test)", new TellrawPart("hmm")
.setColor(Color.White).setUnderlined(true)
.setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT,
new TellrawPart("Click to open").setColor(Color.Blue)))
.setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/test"))));
return list;
}