diff --git a/src/chatty/Helper.java b/src/chatty/Helper.java index febfa2853..2852fc81c 100644 --- a/src/chatty/Helper.java +++ b/src/chatty/Helper.java @@ -541,6 +541,10 @@ public static boolean matchUserStatus(String id, User user) { || user.isModerator() || user.isStaff()) { return true; } + } else if (id.equals("$vip")) { + if (user.hasTwitchBadge("vip")) { + return true; + } } return false; } diff --git a/src/chatty/SettingsManager.java b/src/chatty/SettingsManager.java index b8688249b..f955c9b2f 100644 --- a/src/chatty/SettingsManager.java +++ b/src/chatty/SettingsManager.java @@ -365,6 +365,7 @@ public void defineSettings() { settings.addBoolean("closeToTray", false); settings.addBoolean("minimizeToTray", false); settings.addBoolean("trayIconAlways", false); + settings.addBoolean("singleClickTrayOpen", true); // Window State settings.addMap("windows", new HashMap<>(), Setting.STRING); @@ -515,6 +516,7 @@ public void defineSettings() { settings.addList("highlightBlacklist", new ArrayList(), Setting.STRING); settings.addBoolean("highlightMatches", true); settings.addBoolean("highlightMatchesAll", true); + settings.addBoolean("highlightByPoints", true); // Ignore settings.addList("ignore", new ArrayList(), Setting.STRING); diff --git a/src/chatty/TwitchClient.java b/src/chatty/TwitchClient.java index 98120921d..d78f190e8 100644 --- a/src/chatty/TwitchClient.java +++ b/src/chatty/TwitchClient.java @@ -792,7 +792,7 @@ else if (channel.startsWith("*")) { if (testUser.getRoom().equals(room)) { user = testUser; } - g.printMessage(user,text,false,null,1); + g.printMessage(user,text,false); } else { g.printLine("Not in a channel"); } @@ -815,9 +815,9 @@ private void sendMessage(String channel, String text) { private void sendMessage(String channel, String text, boolean allowCommandMessageLocally) { if (c.sendSpamProtectedMessage(channel, text, false)) { User user = c.localUserJoined(channel); - g.printMessage(user, text, false, null, 0); + g.printMessage(user, text, false); if (allowCommandMessageLocally) { - modCommandAddStreamHighlight(user, text); + modCommandAddStreamHighlight(user, text, MsgTags.EMPTY); } } else { g.printLine("# Message not sent to prevent ban: " + text); @@ -1754,7 +1754,7 @@ private void commandActionMessage(String channel, String message) { public void sendActionMessage(String channel, String message) { if (c.onChannel(channel, true)) { if (c.sendSpamProtectedMessage(channel, message, true)) { - g.printMessage(c.localUserJoined(channel), message, true, null, 0); + g.printMessage(c.localUserJoined(channel), message, true); } else { g.printLine("# Action Message not sent to prevent ban: " + message); } @@ -1865,9 +1865,9 @@ public void commandOpenStreamHighlights(Room room) { g.printLine(room, streamHighlights.openFile()); } - public void modCommandAddStreamHighlight(User user, String message) { + public void modCommandAddStreamHighlight(User user, String message, MsgTags tags) { // Stream Highlights - String result = streamHighlights.modCommand(user, message); + String result = streamHighlights.modCommand(user, message, tags); if (result != null) { result = user.getDisplayNick() + ": " + result; if (settings.getBoolean("streamHighlightChannelRespond")) { @@ -2752,12 +2752,11 @@ public void onUserUpdated(User user) { } @Override - public void onChannelMessage(User user, String message, boolean action, - String emotes, String id, int bits) { - g.printMessage(user, message, action, emotes, bits, id); + public void onChannelMessage(User user, String text, boolean action, MsgTags tags) { + g.printMessage(user, text, action, tags); if (!action) { - addressbookCommands(user.getChannel(), user, message); - modCommandAddStreamHighlight(user, message); + addressbookCommands(user.getChannel(), user, text); + modCommandAddStreamHighlight(user, text, tags); } } @@ -3050,7 +3049,7 @@ private class MyWhisperListener implements WhisperListener { @Override public void whisperReceived(User user, String message, String emotes) { - g.printMessage(user, message, false, emotes, 0); + g.printMessage(user, message, false, MsgTags.create("emotes", emotes)); g.updateUser(user); } @@ -3061,7 +3060,7 @@ public void info(String message) { @Override public void whisperSent(User to, String message) { - g.printMessage(to, message, true, null, 0); + g.printMessage(to, message, true); } } diff --git a/src/chatty/TwitchConnection.java b/src/chatty/TwitchConnection.java index 394256265..2e5c67a93 100644 --- a/src/chatty/TwitchConnection.java +++ b/src/chatty/TwitchConnection.java @@ -953,13 +953,10 @@ void onChannelMessage(String channel, String nick, String from, String text, } else { User user = userJoined(channel, nick); updateUserFromTags(user, tags); - String emotesTag = tags.get("emotes"); - String id = tags.get("id"); - int bits = tags.getInteger("bits", 0); if (!user.getName().equals(username) || !sentMessages.shouldHide(channel, text)) { // Don't show if own name and message was sent recently, // to prevent echo message from being shown in chatrooms - listener.onChannelMessage(user, text, action, emotesTag, id, bits); + listener.onChannelMessage(user, text, action, tags); } } } @@ -1480,7 +1477,7 @@ public interface ConnectionListener { void onUserUpdated(User user); - void onChannelMessage(User user, String message, boolean action, String emotes, String id, int bits); + void onChannelMessage(User user, String msg, boolean action, MsgTags tags); void onWhisper(User user, String message, String emotes); diff --git a/src/chatty/User.java b/src/chatty/User.java index 1725ac726..8b7b21c60 100644 --- a/src/chatty/User.java +++ b/src/chatty/User.java @@ -207,10 +207,14 @@ public synchronized boolean hasTwitchBadge(String id) { return twitchBadges != null && twitchBadges.containsKey(id); } - public List getBadges(boolean botBadgeEnabled) { + public synchronized boolean hasTwitchBadge(String id, String version) { + return twitchBadges != null && twitchBadges.containsKey(id) && twitchBadges.get(id).equals(version); + } + + public List getBadges(boolean botBadgeEnabled, boolean pointsHl) { Map badges = getTwitchBadges(); if (iconManager != null) { - return iconManager.getBadges(badges, this, botBadgeEnabled); + return iconManager.getBadges(badges, this, botBadgeEnabled, pointsHl); } return null; } diff --git a/src/chatty/gui/Highlighter.java b/src/chatty/gui/Highlighter.java index 54a480b2b..f4812c5ae 100644 --- a/src/chatty/gui/Highlighter.java +++ b/src/chatty/gui/Highlighter.java @@ -8,6 +8,10 @@ import chatty.User; import chatty.util.Debugging; import chatty.util.MiscUtil; +import chatty.util.Pair; +import chatty.util.StringUtil; +import chatty.util.api.usericons.BadgeType; +import chatty.util.irc.MsgTags; import java.awt.Color; import java.util.ArrayList; import java.util.Collection; @@ -18,6 +22,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -157,7 +162,7 @@ public String getLastReplacement() { * @see #check(HighlightItem.Type, String, String, Addressbook, User) */ public boolean check(User user, String text) { - return check(HighlightItem.Type.REGULAR, text, null, null, user); + return check(HighlightItem.Type.REGULAR, text, null, null, user, MsgTags.EMPTY); } /** @@ -181,20 +186,22 @@ public boolean check(User user, String text) { * @return true if the message matches, false otherwise */ public boolean check(HighlightItem.Type type, String text, String channel, - Addressbook ab, User user) { + Addressbook ab, User user, MsgTags tags) { Blacklist blacklist = null; if (!blacklistItems.isEmpty()) { - blacklist = new Blacklist(type, text, channel, ab, user, blacklistItems); + blacklist = new Blacklist(type, text, channel, ab, user, tags, blacklistItems); } - // Only reset matches, since the other variables are filled anyway, - // except for "follow-up", where they should stay the same + /** + * All last match variables filled in case of match, except for text + * matches, so reset here. + */ lastTextMatches = null; // Try to match own name first (if enabled) if (highlightUsername && usernameItem != null && usernameItem.matches(type, text, blacklist, - channel, ab, user)) { + channel, ab, user, tags)) { fillLastMatchVariables(usernameItem, text); addMatch(user, usernameItem); return true; @@ -202,7 +209,7 @@ public boolean check(HighlightItem.Type type, String text, String channel, // Then try to match against the items for (HighlightItem item : items) { - if (item.matches(type, text, blacklist, channel, ab, user)) { + if (item.matches(type, text, blacklist, channel, ab, user, tags)) { fillLastMatchVariables(item, text); addMatch(user, item); return true; @@ -228,6 +235,20 @@ private void fillLastMatchVariables(HighlightItem item, String text) { } } + /** + * This should not be necessary if the state is only checked after a match + * (since variables will be set correctly then), but it may be useful in + * some other situations. + */ + public void resetLastMatchVariables() { + lastMatchColor = null; + lastMatchBackgroundColor = null; + lastMatchNoNotification = false; + lastMatchNoSound = false; + lastReplacement = null; + lastTextMatches = null; + } + private void addMatch(User user, HighlightItem item) { if (highlightNextMessages && user != null) { String username = user.getName(); @@ -264,35 +285,94 @@ public enum Type { REGULAR, INFO, ANY } + private static abstract class Item { + + /** + * For info output. + */ + private final String info; + + /** + * Data mainly used in the matches() method for this item, for info + * output. + */ + private final Object infoData; + + private Item(String info, Object infoData) { + this.info = info; + this.infoData = infoData; + } + + @Override + public String toString() { + if (infoData != null) { + return info+": "+infoData; + } + return info; + } + + abstract public boolean matches(String channel, Addressbook ab, User user, MsgTags tags); + } + + private void addUserItem(String info, Object infoData, Function m) { + Item item = new Item(info, infoData) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + return user != null && m.apply(user); + } + }; + matchItems.add(item); + } + + private void addChanItem(String info, Object infoData, Function m) { + Item item = new Item(info, infoData) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + return channel != null && m.apply(channel); + } + }; + matchItems.add(item); + } + + private void addTagsItem(String info, Object infoData, Function m) { + Item item = new Item(info, infoData) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + return tags != null && m.apply(tags); + } + }; + matchItems.add(item); + } + /** * A regex that will never match. */ private static final Pattern NO_MATCH = Pattern.compile("(?!)"); + private static final Item NO_MATCH_ITEM = new Item("Never Match", null) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + return false; + } + }; - private String username; - private Pattern usernamePattern; + private final String raw; + private final List matchItems = new ArrayList<>(); private Pattern pattern; - private String category; - private final Set notChannels = new HashSet<>(); - private final Set channels = new HashSet<>(); - private String channelCategory; - private String channelCategoryNot; - private String categoryNot; private Color color; private Color backgroundColor; private boolean noNotification; private boolean noSound; private Type appliesToType = Type.REGULAR; - private boolean firstMsg; // Replacement string for filtering parts of a message private String replacement; private String error; private String textWithoutPrefix = ""; - private final Set statusReq = new HashSet<>(); - private final Set statusReqNot = new HashSet<>(); - private enum Status { MOD("m"), SUBSCRIBER("s"), BROADCASTER("b"), ADMIN("a"), STAFF("f"), TURBO("t"), ANY_MOD("M"), GLOBAL_MOD("g"), BOT("r"), VIP("v"); @@ -328,6 +408,7 @@ private enum Status { } public HighlightItem(String item) { + raw = item; prepare(item); } @@ -341,44 +422,246 @@ private void prepare(String item) { item = item.trim(); if (!findPatternPrefixAndCompile(item)) { // If not a text matching prefix, search for other prefixes + + //-------------------------- + // User prefixes + //-------------------------- if (item.startsWith("cat:")) { - category = parsePrefix(item, "cat:"); - } else if (item.startsWith("!cat:")) { - categoryNot = parsePrefix(item, "!cat:"); - } else if (item.startsWith("user:")) { - username = parsePrefix(item, "user:").toLowerCase(Locale.ENGLISH); - } else if (item.startsWith("reuser:")) { - String regex = parsePrefix(item, "reuser:").toLowerCase(Locale.ENGLISH); - compileUsernamePattern(regex); - } else if (item.startsWith("chan:")) { - parseListPrefix(item, "chan:"); - } else if (item.startsWith("!chan:")) { - parseListPrefix(item, "!chan:"); - } else if (item.startsWith("chanCat:")) { - channelCategory = parsePrefix(item, "chanCat:"); - } else if (item.startsWith("!chanCat:")) { - channelCategoryNot = parsePrefix(item, "!chanCat:"); - } else if (item.startsWith("color:")) { + List categories = parseStringListPrefix(item, "cat:", s -> s); + addUserItem("Any of Addressbook Categories", categories, user -> { + for (String category : categories) { + if (user.hasCategory(category)) { + return true; + } + } + return false; + }); + } + else if (item.startsWith("!cat:")) { + List categories = parseStringListPrefix(item, "!cat:", s -> s); + addUserItem("Not any of Addressbook Categories", categories, user -> { + for (String category : categories) { + if (!user.hasCategory(category)) { + return true; + } + } + return false; + }); + } + else if (item.startsWith("user:")) { + Pattern p = compilePattern(Pattern.quote(parsePrefix(item, "user:").toLowerCase(Locale.ENGLISH))); + addUserItem("Username", p, user -> { + return p.matcher(user.getName()).matches(); + }); + } + else if (item.startsWith("reuser:")) { + Pattern p = compilePattern(parsePrefix(item, "reuser:").toLowerCase(Locale.ENGLISH)); + addUserItem("Username (Regex)", p, user -> { + return p.matcher(user.getName()).matches(); + }); + } + else if (item.startsWith("status:")) { + Set s = parseStatus(parsePrefix(item, "status:")); + addUserItem("User Status", s, user -> { + return checkStatus(user, s, true); + }); + } + else if (item.startsWith("!status:")) { + Set s = parseStatus(parsePrefix(item, "!status:")); + addUserItem("Not User Status", s, user -> { + return checkStatus(user, s, false); + }); + } + //-------------------------- + // Channel Prefixes + //-------------------------- + else if (item.startsWith("chan:")) { + List chans = parseStringListPrefix(item, "chan:", + c -> Helper.toChannel(c)); + addChanItem("One of channels", chans, chan -> { + return chans.contains(chan); + }); + } + else if (item.startsWith("!chan:")) { + List chans = parseStringListPrefix(item, "!chan:", + c -> Helper.toChannel(c)); + addChanItem("Not one of channels", chans, chan -> { + return !chans.contains(chan); + }); + } + else if (item.startsWith("chanCat:")) { + List cats = parseStringListPrefix(item, "chanCat:", s -> s); + matchItems.add(new Item("Channel Addressbook Category", cats) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + if (channel == null || ab == null) { + return false; + } + for (String cat : cats) { + if (ab.hasCategory(channel, cat)) { + return true; + } + } + return false; + } + }); + } + else if (item.startsWith("!chanCat:")) { + List cats = parseStringListPrefix(item, "!chanCat:", s -> s); + matchItems.add(new Item("Not Channel Addressbook Category", cats) { + + @Override + public boolean matches(String channel, Addressbook ab, User user, MsgTags tags) { + if (channel == null || ab == null) { + return false; + } + for (String cat : cats) { + if (!ab.hasCategory(channel, cat)) { + return true; + } + } + return false; + } + }); + } + //-------------------------- + // Behaviour Prefixes + //-------------------------- + else if (item.startsWith("color:")) { color = HtmlColors.decode(parsePrefix(item, "color:")); - } else if (item.startsWith("bgcolor:")) { + } + else if (item.startsWith("bgcolor:")) { backgroundColor = HtmlColors.decode(parsePrefix(item, "bgcolor:")); - } else if (item.startsWith("status:")) { - String status = parsePrefix(item, "status:"); - parseStatus(status, true); - } else if (item.startsWith("!status:")) { - String status = parsePrefix(item, "!status:"); - parseStatus(status, false); - } else if (item.startsWith("config:")) { - parseListPrefix(item, "config:"); - } else if (item.startsWith("replacement:")) { + } + else if (item.startsWith("replacement:")) { replacement = parsePrefix(item, "replacement:"); - } else { + } + //-------------------------- + // Mixed Prefixes + //-------------------------- + else if (item.startsWith("config:")) { + List list = parseStringListPrefix(item, "config:", s -> s); + list.forEach(part -> { + if (part.equals("silent")) { + noSound = true; + } + else if (part.equals("!notify")) { + noNotification = true; + } + else if (part.equals("info")) { + appliesToType = Type.INFO; + } + else if (part.equals("any")) { + appliesToType = Type.ANY; + } + else if (part.equals("firstmsg")) { + addUserItem("First Message of User", null, user -> { + return user.getNumberOfMessages() == 0; + }); + } + else if (part.equals("hl")) { + addTagsItem("Highlighted by channel points", null, t -> { + return t.isHighlightedMessage(); + }); + } else if (part.equals(chatty.util.ForkUtil.FILTER_FORK_PREFIX)) { + // Do nothing. + } + }); + parseBadges(list); + parseTags(list); + } + //-------------------------- + // No prefix + //-------------------------- + else { textWithoutPrefix = item; - compilePattern("(?iu)" + Pattern.quote(item)); + pattern = compilePattern("(?iu)" + Pattern.quote(item)); } } } + /** + * Receives all list items from a single "config:" prefix, which may or + * may not contain badge items. Only one of the resulting badge items + * has to match in the resulting match item. + * + * @param list The "config:" prefix items + */ + private void parseBadges(List list) { + List badges = new ArrayList<>(); + list.forEach(part -> { + if (part.startsWith("b|") && part.length() > 2) { + badges.add(BadgeType.parse(part.substring(2))); + } + }); + if (!badges.isEmpty()) { + addUserItem("Any of Twitch Badge", badges, user -> { + for (BadgeType type : badges) { + if (type.version == null) { + if (user.hasTwitchBadge(type.id)) { + return true; + } + } + else { + if (user.hasTwitchBadge(type.id, type.version)) { + return true; + } + } + } + return false; + }); + } + } + + /** + * Receives all list items from a single "config:" prefix, which may or + * may not contain tag items. Only of of the resulting tag items has to + * match in the resulting match item. + * + * @param list The "config:" prefix items + */ + private void parseTags(List list) { + List> items = new ArrayList<>(); + list.forEach(part -> { + if (part.startsWith("t|") && part.length() > 2) { + String tag = part.substring(2); + String[] split = tag.split("=", 2); + if (split.length == 2) { + String value = split[1]; + Pattern p; + if (value.startsWith("reg:")) { + p = compilePattern(split[1].substring("reg:".length())); + } + else { + p = compilePattern(Pattern.quote(split[1])); + } + items.add(new Pair(split[0], p)); + } + else { + items.add(new Pair(split[0], null)); + } + } + }); + if (!items.isEmpty()) { + addTagsItem("Any of Message Tags", items, tags -> { + for (Pair item : items) { + if (tags.containsKey(item.key)) { + if (item.value != null) { + if (item.value.matcher(tags.get(item.key)).matches()) { + return true; + } + } + else { + return true; + } + } + } + return false; + }); + } + } + /** * Find status ids in the status: or !status: prefix and save the ones * that were found as requirement. Characters that do not represent a @@ -388,71 +671,80 @@ private void prepare(String item) { * @param shouldBe Whether this is a requirement where the status should * be there or should NOT be there (status:/!status:) */ - private void parseStatus(String status, boolean shouldBe) { + private Set parseStatus(String status) { + Set result = new HashSet<>(); for (Status s : Status.values()) { if (status.contains(s.id)) { - if (shouldBe) { - statusReq.add(s); - } else { - statusReqNot.add(s); - } + result.add(s); } } + return result; } /** * Parse a prefix with a parameter, also prepare() following items (if * present). * + * Uses StringUtil.split() to find the first non-quoted/escaped space, + * with the returned prefix value cleared of quote/escape characters + * (but the value handed to prepare() not). + * * @param item The input to parse the stuff from * @param prefix The name of the prefix, used to remove the prefix to * get the value * @return The value of the prefix */ private String parsePrefix(String item, String prefix) { - String[] split = item.split(" ", 2); - if (split.length == 2) { - // There is something after this prefix, so prepare that just - // like another item (but of course added to this object). - prepare(split[1]); + List split = StringUtil.split(item, ' ', 2); + if (split.size() == 2) { + prepare(split.get(1)); } - return split[0].substring(prefix.length()); + return split.get(0).substring(prefix.length()); } - private void parseListPrefix(String item, String prefix) { - parseList(parsePrefix(item, prefix), prefix); + private void parseListPrefixSingle(String item, String prefix, Consumer p) { + /** + * Don't clear quote/escape characters from prefix value, since the + * list parsing will still need them. It's a bit weird using the + * same "level" of quote/escape characters for different "levels" of + * parsing, but it should work well enough for this. + */ + List split = StringUtil.split(item, ' ', 2, 0); + if (split.size() == 2) { + prepare(split.get(1)); + } + parseList(split.get(0).substring(prefix.length()), p); + } + + /** + * Parses a string list from the prefix value (items separated by ","). + * + * @param item + * @param prefix + * @param c Applied to each list entries, for example to modify it + * @return + */ + private List parseStringListPrefix(String item, String prefix, Function c) { + List result = new ArrayList<>(); + parseListPrefixSingle(item, prefix, p -> result.add(c.apply(p))); + return result; } /** - * Parses a comma-separated list of a prefix. + * Split input by comma and send each non-empty item to the given + * consumer. * - * @param list The String containing the list - * @param prefix The prefix for this list, used to determine what to do - * with the found list items + * Since StringUtil.split() is used, quoting and escaping can be used + * for ignoring commas. + * + * @param list The String containing the comma-separated list + * @param p The consumer */ - private void parseList(String list, String prefix) { - String[] split2 = list.split(","); - for (String part : split2) { + private static void parseList(String list, Consumer p) { + List split = StringUtil.split(list, ',', 0); + for (String part : split) { if (!part.isEmpty()) { - if (prefix.equals("chan:")) { - channels.add(Helper.toChannel(part)); - } else if (prefix.equals("!chan:")) { - notChannels.add(Helper.toChannel(part)); - } else if (prefix.equals("config:")) { - if (part.equals("silent")) { - noSound = true; - } else if (part.equals("!notify")) { - noNotification = true; - } else if (part.equals("info")) { - appliesToType = Type.INFO; - } else if (part.equals("any")) { - appliesToType = Type.ANY; - } else if (part.equals("firstmsg")) { - firstMsg = true; - } else if (part.equals(chatty.util.ForkUtil.FILTER_FORK_PREFIX)) { - // Do nothing. - } - } + p.accept(part); } } } @@ -471,7 +763,7 @@ private boolean findPatternPrefixAndCompile(String input) { String withoutPrefix = input.substring(prefix.length()); String pattern = patternPrefixes.get(prefix).apply(withoutPrefix); textWithoutPrefix = withoutPrefix; - compilePattern(pattern); + this.pattern = compilePattern(pattern); return true; } } @@ -489,29 +781,14 @@ private static void addPatternPrefix(Function patternBuilder, St patternPrefixes.put(prefix, patternBuilder); } } - - /** - * Compiles a pattern (regex) and sets it as pattern. - * - * @param patternString - */ - private void compilePattern(String patternString) { + + private Pattern compilePattern(String patternString) { try { - pattern = Pattern.compile(patternString); + return Pattern.compile(patternString); } catch (PatternSyntaxException ex) { error = ex.getDescription(); - pattern = NO_MATCH; LOGGER.warning("Invalid regex: " + ex); - } - } - - private void compileUsernamePattern(String patternString) { - try { - usernamePattern = Pattern.compile(patternString); - } catch (PatternSyntaxException ex) { - error = ex.getDescription(); - pattern = NO_MATCH; - LOGGER.warning("Invalid username regex: " + ex); + return NO_MATCH; } } @@ -649,17 +926,17 @@ public boolean matchesAny(String text, Blacklist blacklist) { return matches(Type.ANY, text, blacklist, null); } - public boolean matches(Type type, String text, User user) { - return matches(type, text, null, null, null, user); + public boolean matches(Type type, String text, User user, MsgTags tags) { + return matches(type, text, null, null, null, user, tags); } public boolean matches(Type type, String text, Blacklist blacklist, User user) { - return matches(type, text, blacklist, null, null, user); + return matches(type, text, blacklist, null, null, user, MsgTags.EMPTY); } public boolean matches(Type type, String text, String channel, Addressbook ab) { - return matches(type, text, null, channel, ab, null); + return matches(type, text, null, channel, ab, null, MsgTags.EMPTY); } /** @@ -679,10 +956,11 @@ public boolean matches(Type type, String text, String channel, Addressbook ab) { * @param channel The channel, can be null * @param ab The Addressbook, can be null * @param user The User object, can be null + * @param tags MsgTags, can be null * @return true if it matches, false otherwise */ public boolean matches(Type type, String text, Blacklist blacklist, - String channel, Addressbook ab, User user) { + String channel, Addressbook ab, User user, MsgTags tags) { //------ // Type //------ @@ -698,9 +976,9 @@ public boolean matches(Type type, String text, Blacklist blacklist, return false; } - //--------- - // Channel - //--------- + //----------- + // Variables + //----------- if (user != null) { if (channel == null) { channel = user.getChannel(); @@ -709,53 +987,19 @@ public boolean matches(Type type, String text, Blacklist blacklist, ab = user.getAddressbook(); } } - if (!channels.isEmpty() && channel != null - && !channels.contains(channel)) { - return false; - } - if (!notChannels.isEmpty() && channel != null - && notChannels.contains(channel)) { - return false; - } - if (channelCategory != null && ab != null && channel != null - && !ab.hasCategory(channel, channelCategory)) { - return false; - } - if (channelCategoryNot != null && ab != null && channel != null - && ab.hasCategory(channel, channelCategoryNot)) { - return false; - } - - //------ - // User - //------ - if (username != null && user != null - && !username.equals(user.getName())) { - return false; - } - if (usernamePattern != null && user != null - && !usernamePattern.matcher(user.getName()).matches()) { - return false; - } - if (category != null && user != null - && !user.hasCategory(category)) { - return false; + if (tags == null) { + tags = MsgTags.EMPTY; } - if (categoryNot != null && user != null - && user.hasCategory(categoryNot)) { - return false; - } - if (!checkStatus(user, statusReq)) { - return false; - } - if (!checkStatus(user, statusReqNot)) { - return false; - } - // Message count is updated after printing message, so it checks 0 - if (firstMsg && user != null - && user.getNumberOfMessages() > 0) { - return false; + +// System.out.println(raw); + for (Item item : matchItems) { + boolean match = item.matches(channel, ab, user, tags); +// System.out.println(item); + if (!match) { + return false; + } } + // If all the requirements didn't make it fail, this matches return true; } @@ -772,13 +1016,13 @@ public boolean matches(Type type, String text, Blacklist blacklist, * on which set of requirements was given, statusReq or statusReqNot, * only one requirement has to match or all have to match) */ - private boolean checkStatus(User user, Set req) { + private static boolean checkStatus(User user, Set req, boolean positive) { // No requirement, so always matching if (req.isEmpty()) { return true; } if (user == null) { - return true; + return false; } /** * If this checks the requirements that SHOULD be there, then this @@ -790,7 +1034,7 @@ private boolean checkStatus(User user, Set req) { * requirements have to match, so it will return false at the first * requirement that doesn't match, true if all match). */ - boolean or = req == statusReq; + boolean or = positive; if (req.contains(Status.MOD) && user.isModerator()) { return or; } @@ -875,10 +1119,10 @@ public static class Blacklist { * @param items The HighlightItem objects that the Blacklist is based on */ public Blacklist(HighlightItem.Type type, String text, String channel, - Addressbook ab, User user, Collection items) { + Addressbook ab, User user, MsgTags tags, Collection items) { blacklisted = new ArrayList<>(); for (HighlightItem item : items) { - if (item.matches(type, text, null, channel, ab, user)) { + if (item.matches(type, text, null, channel, ab, user, tags)) { List matches = item.getTextMatches(text); if (matches != null) { blacklisted.addAll(matches); diff --git a/src/chatty/gui/MainGui.java b/src/chatty/gui/MainGui.java index a729845c8..da793bc89 100644 --- a/src/chatty/gui/MainGui.java +++ b/src/chatty/gui/MainGui.java @@ -1427,14 +1427,28 @@ public void clearHistory() { private class TrayMenuListener implements ActionListener { + private final ElapsedTime lastEvent = new ElapsedTime(); + @Override public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); - if (cmd == null || cmd.equals("show")) { - if (isMinimized()) { - makeVisible(); - } else { - minimizeToTray(); + if (cmd == null + || cmd.equals("show") + || cmd.equals("doubleClick") + || (cmd.equals("singleClick") && client.settings.getBoolean("singleClickTrayOpen"))) { + /** + * Prevent hiding/showing too quickly, for example when both the + * mouse listener and the action listener fire (could also be + * platform dependent). + */ + if (lastEvent.millisElapsed(80)) { + lastEvent.set(); + if (isMinimized()) { + makeVisible(); + } + else { + minimizeToTray(); + } } } else if (cmd.equals("exit")) { @@ -2047,13 +2061,13 @@ public void emoteMenuItemClicked(ActionEvent e, EmoticonImage emoteImage) { url = TwitchUrl.makeFFZUrl(); } else if (e.getActionCommand().equals("emoteId")) { if (emote.type == Emoticon.Type.FFZ) { - url = TwitchUrl.makeFFZUrl(emote.numericId); + url = TwitchUrl.makeFFZEmoteUrl(emote.stringId); } else if (emote.type == Emoticon.Type.TWITCH) { - url = TwitchUrl.makeTwitchemotesUrl(emote.numericId); + url = TwitchUrl.makeTwitchemotesUrl(emote.stringId); } } else if (e.getActionCommand().equals("emoteCreator")) { if (emote.type == Emoticon.Type.FFZ) { - url = TwitchUrl.makeFFZUrl(emote.creator); + url = TwitchUrl.makeFFZUserUrl(emote.creator); } else if (emote.creator.equals("Twemoji")) { url = "https://github.com/twitter/twemoji"; } @@ -2883,26 +2897,22 @@ public Window getActiveWindow() { */ //For timestamp - public void printMessage(User user, String text, boolean action, - String emotes, int bits, String id) { - printMessage(user, text, action, emotes, bits, id, null); + public void printMessage(User user, String text, boolean action) { + printMessage(user, text, action, MsgTags.EMPTY, null); } - //For timestamp - - public void printMessage(User user, String text, boolean action, - String emotes, int bits) { - printMessage(user, text, action, emotes, bits, null, null); + public void printMessage(User user, String text, boolean action, MsgTags tags) { + printMessage(user, text, action, tags, null); } - - public void printMessage(final User user, - final String text, final boolean action, final String emotes, - final int origBits, final String id, final String timestamp) { + //For timestamp + + public void printMessage(User user, String text, boolean action, MsgTags tags, final String timestamp) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Channel chan; String channel = user.getChannel(); boolean whisper = false; + int origBits = tags.getBits(); // Disable Cheer emotes altogether if disabled in the settings int bits = origBits; @@ -2936,7 +2946,7 @@ public void run() { boolean isOwnMessage = isOwnUsername(user.getName()) || (whisper && action); boolean ignoredUser = (userIgnored(user, whisper) && !isOwnMessage); - boolean ignored = checkMsg(ignoreList, "ignore", text, user, isOwnMessage) || ignoredUser; + boolean ignored = checkMsg(ignoreList, "ignore", text, user, tags, isOwnMessage) || ignoredUser; if (!ignored || client.settings.getBoolean("logIgnored")) { client.chatLog.message(chan.getFilename(), user, text, action); @@ -2946,10 +2956,15 @@ public void run() { List highlightMatches = null; if ((client.settings.getBoolean("highlightIgnored") || !ignored) && !client.settings.listContains("noHighlightUsers", user.getName())) { - highlighted = checkMsg(highlighter, "highlight", text, user, isOwnMessage); + highlighted = checkMsg(highlighter, "highlight", text, user, tags, isOwnMessage); + } + if (!highlighted) { + highlighted = tags.isHighlightedMessage() && client.settings.getBoolean("highlightByPoints"); + // Irregular highlight, so reset last match + highlighter.resetLastMatchVariables(); } - TagEmotes tagEmotes = Emoticons.parseEmotesTag(emotes); + TagEmotes tagEmotes = Emoticons.parseEmotesTag(tags.getRawEmotes()); // Do stuff if highlighted, without printing message if (highlighted) { @@ -2961,7 +2976,7 @@ public void run() { } else { channels.setChannelNewMessage(chan); } - notificationManager.highlight(user, text, + notificationManager.highlight(user, text, tags, highlighter.getLastMatchNoNotification(), highlighter.getLastMatchNoSound(), isOwnMessage, whisper, origBits > 0); @@ -2969,7 +2984,7 @@ public void run() { if (whisper) { notificationManager.whisper(user, text, isOwnMessage); } else { - notificationManager.message(user, text, isOwnMessage, + notificationManager.message(user, text, tags, isOwnMessage, origBits > 0); } if (!isOwnMessage) { @@ -3000,13 +3015,14 @@ public void run() { printInfo(chan, InfoMessage.createInfo("Own message ignored.")); } } else { - boolean hasReplacements = checkMsg(filter, "filter", text, user, isOwnMessage); + boolean hasReplacements = checkMsg(filter, "filter", text, user, tags, isOwnMessage); // Print message, but determine how exactly - UserMessage message = new UserMessage(user, text, tagEmotes, id, bits, + UserMessage message = new UserMessage(user, text, tagEmotes, tags.getId(), bits, highlightMatches, hasReplacements ? filter.getLastTextMatches() : null, hasReplacements ? filter.getLastReplacement() : null); + message.pointsHl = tags.isHighlightedMessage(); // Custom color if (highlighted) { @@ -3014,7 +3030,7 @@ public void run() { message.backgroundColor = highlighter.getLastMatchBackgroundColor(); } if (!highlighted || client.settings.getBoolean("msgColorsPrefer")) { - ColorItem colorItem = msgColorManager.getMsgColor(user, text); + ColorItem colorItem = msgColorManager.getMsgColor(user, text, tags); if (!colorItem.isEmpty()) { message.color = colorItem.getForegroundIfEnabled(); message.backgroundColor = colorItem.getBackgroundIfEnabled(); @@ -3038,10 +3054,11 @@ public void run() { // Stuff independent of highlight/ignore if (timestamp == null) { - user.addMessage(processMessage(text), action, id); + user.addMessage(processMessage(text), action, tags.getId()); } else { - user.addMessage(processMessage(text), action, id, timestamp); + user.addMessage(processMessage(text), action, tags.getId(), timestamp); } + // Update User if (highlighted) { user.setHighlighted(); } @@ -3108,27 +3125,27 @@ private String processMessage(String text) { } private boolean checkHighlight(HighlightItem.Type type, String text, - String channel, Addressbook ab, User user, Highlighter hl, + String channel, Addressbook ab, User user, MsgTags tags, Highlighter hl, String setting, boolean isOwnMessage) { if (client.settings.getBoolean(setting + "Enabled")) { if (client.settings.getBoolean(setting + "OwnText") || !isOwnMessage) { - return hl.check(type, text, channel, ab, user); + return hl.check(type, text, channel, ab, user, tags); } } return false; } private boolean checkMsg(Highlighter hl, String setting, String text, - User user, boolean isOwnMessage) { + User user, MsgTags tags, boolean isOwnMessage) { return checkHighlight(HighlightItem.Type.REGULAR, text, null, null, - user, hl, setting, isOwnMessage); + user, tags, hl, setting, isOwnMessage); } private boolean checkInfoMsg(Highlighter hl, String setting, String text, String channel, Addressbook ab) { return checkHighlight(HighlightItem.Type.INFO, text, channel, ab, null, - hl, setting, false); + null, hl, setting, false); } protected void ignoredMessagesCount(String channel, String message) { @@ -4659,9 +4676,6 @@ private boolean printOneRecentMessage(String channel, String data, boolean first User user = client.getUser(channel, tags.get("display-name")); ForkUtil.updateUserFromTags(user, tags); - String emotesTag = tags.get("emotes"); - String id = tags.get("id"); - int bits = tags.getInteger("bits", 0); String separator = "PRIVMSG " + channel + " :"; boolean action = false; @@ -4711,7 +4725,7 @@ private boolean printOneRecentMessage(String channel, String data, boolean first printLine(client.roomManager.getRoom(channel), "[Begin of recent messages.]"); } - this.printMessage(user, textMsg, action, emotesTag, bits, id, tmi); + this.printMessage(user, textMsg, action, tags, tmi); return true; } diff --git a/src/chatty/gui/TrayIconManager.java b/src/chatty/gui/TrayIconManager.java index 6e869779e..a72a5e3e3 100644 --- a/src/chatty/gui/TrayIconManager.java +++ b/src/chatty/gui/TrayIconManager.java @@ -90,7 +90,20 @@ public void addActionListener(ActionListener listener) { @Override public void mouseClicked(MouseEvent e) { - listener.actionPerformed(new ActionEvent(e, ActionEvent.ACTION_FIRST, "show")); + // Checking just isPopupTrigger() didn't seem to work + if (!e.isPopupTrigger() && e.getButton() == MouseEvent.BUTTON1) { + if (e.getClickCount() % 2 == 0) { + /** + * The action listener above may already trigger for + * double-clicks, but this allows for quicker toggle + * (at least on Windows 7). + */ + listener.actionPerformed(new ActionEvent(e, ActionEvent.ACTION_FIRST, "doubleClick")); + } + else { + listener.actionPerformed(new ActionEvent(e, ActionEvent.ACTION_FIRST, "singleClick")); + } + } } }); diff --git a/src/chatty/gui/TwitchUrl.java b/src/chatty/gui/TwitchUrl.java index 63c8adf43..798e94ec0 100644 --- a/src/chatty/gui/TwitchUrl.java +++ b/src/chatty/gui/TwitchUrl.java @@ -73,11 +73,11 @@ public static String makeFFZUrl() { return "https://frankerfacez.com"; } - public static String makeFFZUrl(int id) { + public static String makeFFZEmoteUrl(String id) { return "https://www.frankerfacez.com/emoticons/"+id; } - public static String makeFFZUrl(String user) { + public static String makeFFZUserUrl(String user) { return "https://www.frankerfacez.com/emoticons/user/"+StringUtil.toLowerCase(user); } @@ -85,7 +85,7 @@ public static String makeBttvUrl() { return "https://www.nightdev.com/betterttv/"; } - public static String makeTwitchemotesUrl(int id) { + public static String makeTwitchemotesUrl(String id) { return "https://twitchemotes.com/emote/"+id; } diff --git a/src/chatty/gui/colors/MsgColorItem.java b/src/chatty/gui/colors/MsgColorItem.java index c12e911ab..0f88c5e45 100644 --- a/src/chatty/gui/colors/MsgColorItem.java +++ b/src/chatty/gui/colors/MsgColorItem.java @@ -6,6 +6,7 @@ import chatty.gui.Highlighter; import chatty.gui.Highlighter.HighlightItem; import chatty.util.StringUtil; +import chatty.util.irc.MsgTags; import java.awt.Color; /** @@ -24,8 +25,8 @@ public MsgColorItem(String item, } public boolean matches(HighlightItem.Type type, String text, String channel, - Addressbook ab, User user) { - return search.matches(type, text, null, channel, ab, user); + Addressbook ab, User user, MsgTags tags) { + return search.matches(type, text, null, channel, ab, user, tags); } } diff --git a/src/chatty/gui/colors/MsgColorManager.java b/src/chatty/gui/colors/MsgColorManager.java index 58b16aacc..aca4e153d 100644 --- a/src/chatty/gui/colors/MsgColorManager.java +++ b/src/chatty/gui/colors/MsgColorManager.java @@ -5,6 +5,7 @@ import chatty.User; import chatty.gui.Highlighter.HighlightItem; import chatty.util.colors.HtmlColors; +import chatty.util.irc.MsgTags; import chatty.util.settings.Settings; import java.awt.Color; import java.util.ArrayList; @@ -122,24 +123,24 @@ public synchronized void setData(List newData) { * @return */ public synchronized ColorItem getColor(HighlightItem.Type type, User user, - String text, String channel, Addressbook ab) { + String text, String channel, MsgTags tags, Addressbook ab) { if (data == null || !settings.getBoolean(ENABLED_SETTING)) { return EMPTY; } for (MsgColorItem item : data) { - if (item.matches(type, text, channel, ab, user)) { + if (item.matches(type, text, channel, ab, user, tags)) { return item; } } return EMPTY; } - public synchronized ColorItem getMsgColor(User user, String text) { - return getColor(HighlightItem.Type.REGULAR, user, text, user.getChannel(), user.getAddressbook()); + public synchronized ColorItem getMsgColor(User user, String text, MsgTags tags) { + return getColor(HighlightItem.Type.REGULAR, user, text, user.getChannel(), tags, user.getAddressbook()); } public synchronized ColorItem getInfoColor(String text, String channel, Addressbook ab) { - return getColor(HighlightItem.Type.INFO, null, text, channel, ab); + return getColor(HighlightItem.Type.INFO, null, text, channel, MsgTags.EMPTY, ab); } } diff --git a/src/chatty/gui/colors/UsercolorItem.java b/src/chatty/gui/colors/UsercolorItem.java index 030743ad9..e6ac3a16c 100644 --- a/src/chatty/gui/colors/UsercolorItem.java +++ b/src/chatty/gui/colors/UsercolorItem.java @@ -17,7 +17,7 @@ public class UsercolorItem extends ColorItem { private static final Set statusDef = new HashSet<>(Arrays.asList( "$mod", "$sub", "$admin", "$staff", "$turbo", "$broadcaster", "$bot", - "$globalmod", "$anymod")); + "$globalmod", "$anymod", "$vip")); public static final int TYPE_NAME = 0; public static final int TYPE_COLOR = 1; diff --git a/src/chatty/gui/components/EmotesDialog.java b/src/chatty/gui/components/EmotesDialog.java index d99f8b9e6..0b31d78fe 100644 --- a/src/chatty/gui/components/EmotesDialog.java +++ b/src/chatty/gui/components/EmotesDialog.java @@ -864,6 +864,13 @@ private void updateEmotes2(Map emotesetInfo) { turboEmotes.add(emoteset); } else if (emoteset != Emoticon.SET_GLOBAL) { EmotesetInfo info = emotesetInfo.get(emoteset); + if (info == null) { + /** + * Get cached if already available (otherwise no info + * while waiting for request, if any emoteset changed). + */ + info = TwitchEmotesApi.api.getBySet(emoteset); + } if (info != null) { if (info.stream_name == null) { // No stream name, probably special emoteset @@ -1204,8 +1211,8 @@ private void updateEmotes2() { } String featured = emote.subType == Emoticon.SubType.EVENT ? " (Featured)" : ""; addInfo(panel2, Language.getString("emotesDialog.details.type"), emote.type.toString()+featured); - if (emote.numericId > Emoticon.ID_UNDEFINED) { - addInfo(panel2, Language.getString("emotesDialog.details.id"), ""+emote.numericId); + if (emote.type == Emoticon.Type.TWITCH || emote.type == Emoticon.Type.FFZ) { + addInfo(panel2, Language.getString("emotesDialog.details.id"), emote.stringId); } if (!emote.hasGlobalEmoteset()) { int emoteset = TwitchEmotesApi.getSet(emote, emotesetInfo); @@ -1217,7 +1224,8 @@ private void updateEmotes2() { if (emotesetInfo != null && emotesetInfo.product != null) { info += " ("+emotesetInfo.product+")"; } - addInfo(panel2, "Emoteset:", info); + String orig = TwitchEmotesApi.isModified(emote) ? "Orig. " : ""; + addInfo(panel2, orig+"Emoteset:", info); } else { addInfo(panel2, "Emoteset:", "unknown"); } diff --git a/src/chatty/gui/components/LivestreamerDialog.java b/src/chatty/gui/components/LivestreamerDialog.java index ffd6cd426..c7a2d6065 100644 --- a/src/chatty/gui/components/LivestreamerDialog.java +++ b/src/chatty/gui/components/LivestreamerDialog.java @@ -4,8 +4,10 @@ import chatty.Helper; import chatty.gui.GuiUtil; import chatty.gui.components.settings.EditorStringSetting; +import chatty.util.Debugging; import chatty.util.Livestreamer; import chatty.util.Livestreamer.LivestreamerListener; +import static chatty.util.Livestreamer.filterToken; import chatty.util.StringUtil; import chatty.util.settings.Settings; import java.awt.BorderLayout; @@ -384,7 +386,7 @@ private void addMessage(String message) { Document doc = messages.getDocument(); try { - doc.insertString(doc.getLength(), message+"\n", null); + doc.insertString(doc.getLength(), filterToken(message)+"\n", null); } catch (BadLocationException ex) { Logger.getLogger(LivestreamerDialog.class.getName()).log(Level.SEVERE, null, ex); } diff --git a/src/chatty/gui/components/help/help-releases.html b/src/chatty/gui/components/help/help-releases.html index 7e9b3643f..635d71ec7 100644 --- a/src/chatty/gui/components/help/help-releases.html +++ b/src/chatty/gui/components/help/help-releases.html @@ -61,7 +61,45 @@

Version 0.10 (This one!) (2019-??-??) [back to top]

-

Coming soon

+

Work in progress

+
+### Chat
+- Added various timestamp customization settings (font, color, format)
+- Added support for emotes modified by channel points
+- Added initial support for messages highlighted by channel points
+  - Automatically highlighted (can be turned off in the Highlights settings)
+  - Special badge added (can be turned off by adding a Custom Badge of the same
+    type with no image)
+  - Added matching prefix "config:hl"
+
+### Emotes
+- Added image to Emote tooltips
+- Implemented new Twitchemotes.com API for more up-to-date data and reduced
+  memory usage
+
+### Other
+- Changed matching behavior (Highlights, Ignore, etc.) for prefixes that make no
+  sense in the context to not match (e.g. "user:" on an info message)
+- TAB Completion: Always include current channel name
+- Change "/host" command to host current channel if no argument provided
+- Show favorited channels with star in Live Streams list (and optional sorted
+  first)
+- Added setting to show on single-click on Tray Icon (otherwise usually
+  double-click, platform dependent)
+- User Dialog: Allow smaller minimal size, more flexible sizing
+- User Dialog: Added shortcut setting for ban reasons
+- Changed notification border color for dark notification backgrounds
+- Added Custom Command function "$replace()"
+- Added "$vip" to Custom Usercolors/Badges
+- Improved debug output
+- Updated translations
+
+### Bugfixes
+- Always show Live Streams list scrollbar to avoid bug
+- Fixed TAB Completion not working in some cases
+- Fixed error related to TAB Completion
+- Various small bugfixes
+    

Version 0.9.7 (2019-07-15) diff --git a/src/chatty/gui/components/help/help-settings.html b/src/chatty/gui/components/help/help-settings.html index 01ca86640..9c9ccae67 100644 --- a/src/chatty/gui/components/help/help-settings.html +++ b/src/chatty/gui/components/help/help-settings.html @@ -10,7 +10,7 @@

Settings

| Chat Colors | Message Colors | Usercolors - | Usericons + | Badges | Emoticons | Fonts | Chat @@ -167,12 +167,12 @@

- Usericons + Badges [back to menu]

-

Usericon Settings

+

Badge Settings

    -
  • Show Usericons: Shows badges as images instead of +
  • Show Badges: Shows badges as images instead of just text
  • Enable Custom Usericons: Use the custom icons defined in the table below
  • @@ -802,11 +802,32 @@

    Text Matching Prefixes

Meta Prefixes (Matching)

-

The following prefix don't match on the text itself, but define other +

The following prefixes don't match on the text itself, but define other things that should or should not trigger a Highlight. You can place one or several of these prefixes before the search text (separated by spaces): - [meta-prefix:value] [..] [text-prefix:]<search text>

+ [meta-prefix:value] [..] [text-prefix:]<search text>

+ +

Notes:

+
    +
  • When specifying several prefixes, all of them have to match in order + for the Highlight to trigger.
  • +
  • The same prefix can appear several times with different values (e.g. + cat:category1 cat:category2).
  • +
  • Prefixes can normally not contain spaces (it wouldn't be clear + whether the space is part of the prefix value or a separator between + prefixes), however you can enclose the prefix value (or part of it) + in quotes or escape each space with a backlash. Example: + replacement:"text with space" or + replacement:text\ with\ space
  • +
  • Quotes and backslashes have to be escaped when intended to be used literally (\" or \\).
  • +
  • When a prefix can have several values, they must be comma-separated + (without spaces). Example: cat:category1,category2. + Commas in list values can be quoted or escaped as well for them to + be used literally instead of as a separator.
  • +
+ +

Possible matching prefixes:

  • user: to specify one exact username (case-insensitive) which should highlight only if this user send the message, doesn't @@ -815,20 +836,24 @@

    Meta Prefixes (Matching)

    Behaves like the regm:/re: prefix, in that it always tries to match the entire username. Example: reuser:(?i)a.* would match all names starting with "a".
  • -
  • cat: to specify a category the user who send the message - should be in (as defined in the Addressbook).
  • -
  • !cat: to specify a category the user who send the message - can NOT be in.
  • -
  • chan: to specify one or more channels the message has - to be send in to match (several channels are specified as comma-separated - list, without spaces).
  • -
  • !chan: to specify one or more channels the message must - NOT be send in to match.
  • -
  • chanCat: to specify one category the channel the message - was send in has to be in (as defined in the Addressbook - with the name of the channel, including leading #).
  • -
  • !chanCat: to specify one category the channel the message - was send in can NOT be in.
  • +
  • cat: - One or several (comma separated) Addressbook + categories. The user who send the message must be in at least on of them. + Example: cat:friends,family
  • +
  • !cat: - One or several Addressbook categories. + The user who send the message must not be in at least one of them.
  • +
  • chan: - One or several channels the message must + have been sent in. Example: chan:joshimuz,tirean
  • +
  • !chan: - One or several channels the message must + not have been sent in.
  • +
  • chanCat: - One or several Addressbook + categories. The channel the message was send in must be in at least + one of those categories (added to the Addressbook with the name of + the channel, including leading #).
  • +
  • !chanCat: - One or several Addressbook categories. The + channel the message was send in must not be in at least one + of them. Example: !chanCat:modding,important (if + channel has both, the match fails), !chanCat:modding !chanCat:important + (if channel has one or both, the match fails)
  • status: to specify that the user has to have one of the given status codes (case-sensitive): - For example: status:st matches - all subscriber and turbo users.
  • + For example: status:sv matches users that are either a + subscriber or a VIP (or both). status:s status:v + matches users that are both a subscriber and VIP at the + same time.
  • !status: to specify that the user must NOT have any of the given status codes (see status: for codes). For example: !status:stM matches all 'normal' users that have no @@ -860,6 +887,16 @@

    Meta Prefixes (Matching)

    messages instead of regular user messages
  • config:any - This item applies to both info messages and regular user messages
  • +
  • config:hl - Restrict matching to user messages + highlighted by using channel points
  • +
  • config:b|id/version - Match a Twitch Badge + (version optional, if several are specified in the same + config: prefix, only one has to match).
  • +
  • config:t|key=value or config:t|key=reg:value - + Match a message tag (value optional, if several are + specified in the same config: prefix, only one + has to match), prefix value with reg: to use + regex matching on the value.
@@ -870,6 +907,8 @@

Meta Prefixes (Behaviour)

prefixes before the search text (separated by spaces): [meta-prefix:value] [..] [text-prefix:]<search text>

+

See Meta Prefixes (Matching) for quoting/escaping.

+
  • color: to specify a color other than the default Highlight color for displaying this Highlight (HTML Color Codes as diff --git a/src/chatty/gui/components/help/help.html b/src/chatty/gui/components/help/help.html index 722e587f2..93a05af6b 100644 --- a/src/chatty/gui/components/help/help.html +++ b/src/chatty/gui/components/help/help.html @@ -5,7 +5,7 @@ -

    Chatty (Version: 0.10-b2)

    +

    Chatty (Version: 0.10-b3)

    diff --git a/src/chatty/gui/components/menus/EmoteContextMenu.java b/src/chatty/gui/components/menus/EmoteContextMenu.java index d7fea7444..f900d7e7b 100644 --- a/src/chatty/gui/components/menus/EmoteContextMenu.java +++ b/src/chatty/gui/components/menus/EmoteContextMenu.java @@ -45,8 +45,8 @@ public EmoteContextMenu(EmoticonImage emoteImage, ContextMenuListener listener) } } addItem("emoteImage", emoteImage.getSizeString(), ICON_IMAGE); - if (emote.numericId != Emoticon.ID_UNDEFINED) { - addItem("emoteId", "ID: "+emote.numericId, ICON_WEB); + if (emote.type == Emoticon.Type.TWITCH || emote.type == Emoticon.Type.FFZ) { + addItem("emoteId", "ID: "+emote.stringId, ICON_WEB); } // Non-Twitch Emote Information @@ -98,7 +98,8 @@ public EmoteContextMenu(EmoticonImage emoteImage, ContextMenuListener listener) addSeparator(); addItem("ignoreEmote", Language.getString("emoteCm.ignore")); - if (emote.subType != Emoticon.SubType.CHEER) { + if (emote.subType != Emoticon.SubType.CHEER + && (emote.emoteSet > -1 || emote.type != Emoticon.Type.TWITCH)) { if (!emote.hasStreamRestrictions()) { if (emoteManager.isFavorite(emote)) { addItem("unfavoriteEmote", Language.getString("emoteCm.unfavorite")); diff --git a/src/chatty/gui/components/settings/ColorSettings.java b/src/chatty/gui/components/settings/ColorSettings.java index ef5edeab5..9caa9d4ce 100644 --- a/src/chatty/gui/components/settings/ColorSettings.java +++ b/src/chatty/gui/components/settings/ColorSettings.java @@ -326,7 +326,7 @@ public ColorSettings(SettingsDialog d, Settings settings) { "#5C0000", // highlightBackgroundColor "#DFDFDF", // separatorColor "#C5C5C5", // timestampColor - "off", // timestampColorInherit + "40", // timestampColorInherit }, new Boolean[]{ true, // alternateBackground diff --git a/src/chatty/gui/components/settings/FilterSettings.java b/src/chatty/gui/components/settings/FilterSettings.java index 7ca46192b..4b99381d1 100644 --- a/src/chatty/gui/components/settings/FilterSettings.java +++ b/src/chatty/gui/components/settings/FilterSettings.java @@ -46,6 +46,7 @@ public FilterSettings(SettingsDialog d) { items.setInfo(INFO); HighlighterTester tester = new HighlighterTester(d, false); tester.setLinkLabelListener(d.getLinkLabelListener()); + items.setInfoLinkLabelListener(d.getLinkLabelListener()); items.setEditor(tester); items.setDataFormatter(input -> input.trim()); gbc.fill = GridBagConstraints.BOTH; diff --git a/src/chatty/gui/components/settings/FontChooser.java b/src/chatty/gui/components/settings/FontChooser.java index 517acefce..047b229a2 100644 --- a/src/chatty/gui/components/settings/FontChooser.java +++ b/src/chatty/gui/components/settings/FontChooser.java @@ -133,8 +133,8 @@ public FontChooser(Dialog owner, String[] fonts, boolean showStyles) { */ preview.setText(PREVIEW_TEXT); preview.setEditable(false); - preview.setForeground(getForeground()); - preview.setBackground(getBackground()); + // Transparent background + preview.setOpaque(false); preview.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); preview.setRows(6); preview.setLineWrap(true); diff --git a/src/chatty/gui/components/settings/HighlightSettings.java b/src/chatty/gui/components/settings/HighlightSettings.java index 6f0744240..798db7b5e 100644 --- a/src/chatty/gui/components/settings/HighlightSettings.java +++ b/src/chatty/gui/components/settings/HighlightSettings.java @@ -106,6 +106,11 @@ public HighlightSettings(SettingsDialog d) { JCheckBox highlightMatchesAll = d.addSimpleBooleanSetting("highlightMatchesAll"); base.add(highlightMatchesAll, gbc); + gbc = d.makeGbc(0, 4, 2, 1, GridBagConstraints.WEST); + gbc.insets = settingInsets; + JCheckBox highlightByPoints = d.addSimpleBooleanSetting("highlightByPoints"); + base.add(highlightByPoints, gbc); + gbc = d.makeGbc(0,5,2,1); gbc.insets = new Insets(5,10,5,5); ListSelector items = d.addListSetting("highlight", "Highlight", 220, 250, true, true); @@ -115,6 +120,7 @@ public HighlightSettings(SettingsDialog d) { highlightBlacklist.addItem(e.getActionCommand()); }); tester.setLinkLabelListener(d.getLinkLabelListener()); + items.setInfoLinkLabelListener(d.getLinkLabelListener()); items.setEditor(tester); items.setDataFormatter(input -> input.trim()); gbc.fill = GridBagConstraints.BOTH; @@ -238,6 +244,7 @@ public HighlightBlacklist(SettingsDialog d) { HighlighterTester tester = new HighlighterTester(d, true); tester.setEditingBlacklistItem(true); tester.setLinkLabelListener(d.getLinkLabelListener()); + setting.setInfoLinkLabelListener(d.getLinkLabelListener()); setting.setEditor(tester); add(setting, gbc); diff --git a/src/chatty/gui/components/settings/HighlighterTester.java b/src/chatty/gui/components/settings/HighlighterTester.java index b6ef6d952..c77d6c666 100644 --- a/src/chatty/gui/components/settings/HighlighterTester.java +++ b/src/chatty/gui/components/settings/HighlighterTester.java @@ -9,6 +9,7 @@ import chatty.gui.components.LinkLabelListener; import chatty.lang.Language; import chatty.util.StringUtil; +import chatty.util.irc.MsgTags; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; @@ -300,7 +301,7 @@ private void updateInfoText() { // Match ANY type, same as the other matching in this (ignoring // non-text prefixes) blacklist = new Highlighter.Blacklist(HighlightItem.Type.ANY, testInput.getText(), null, - null, null, Arrays.asList(new HighlightItem[]{blacklistItem})); + null, null, MsgTags.EMPTY, Arrays.asList(new HighlightItem[]{blacklistItem})); } if (highlightItem == null) { testResult.setText("No pattern."); diff --git a/src/chatty/gui/components/settings/IgnoreSettings.java b/src/chatty/gui/components/settings/IgnoreSettings.java index e387d5f3f..d1be0094f 100644 --- a/src/chatty/gui/components/settings/IgnoreSettings.java +++ b/src/chatty/gui/components/settings/IgnoreSettings.java @@ -108,6 +108,7 @@ public void actionPerformed(ActionEvent e) { items.setInfo(INFO_IGNORE); HighlighterTester tester = new HighlighterTester(d, false); tester.setLinkLabelListener(d.getLinkLabelListener()); + items.setInfoLinkLabelListener(d.getLinkLabelListener()); items.setEditor(tester); items.setDataFormatter(input -> input.trim()); base.add(items, gbc); diff --git a/src/chatty/gui/components/settings/MainSettings.java b/src/chatty/gui/components/settings/MainSettings.java index 577363f8b..bc701265e 100644 --- a/src/chatty/gui/components/settings/MainSettings.java +++ b/src/chatty/gui/components/settings/MainSettings.java @@ -80,10 +80,12 @@ public static Map getLanguageOptions() { Map languageOptions = new LinkedHashMap<>(); languageOptions.put("", Language.getString("settings.language.option.defaultLanguage")); languageOptions.put("zh_TW", "Chinese (traditional)"); + languageOptions.put("nl", "Dutch / Nederlands"); languageOptions.put("en_US", "English (US)"); languageOptions.put("en_GB", "English (UK)"); languageOptions.put("fr", "French / Français"); languageOptions.put("de", "German / Deutsch"); + languageOptions.put("it", "Italian / Italiano"); languageOptions.put("ja", "Japanese / 日本語"); languageOptions.put("ko", "Korean / 한국어"); languageOptions.put("pl", "Polish / Polski"); diff --git a/src/chatty/gui/components/settings/UsericonEditor.java b/src/chatty/gui/components/settings/UsericonEditor.java index d3ec854b8..003b6fdfe 100644 --- a/src/chatty/gui/components/settings/UsericonEditor.java +++ b/src/chatty/gui/components/settings/UsericonEditor.java @@ -68,16 +68,17 @@ public UsericonEditor(JDialog owner, LinkLabelListener linkLabelListener) { typeNames.put(Usericon.Type.MOD, "Moderator"); typeNames.put(Usericon.Type.VIP, "VIP"); typeNames.put(Usericon.Type.SUB, "Subscriber"); + typeNames.put(Usericon.Type.BROADCASTER, "Broadcaster"); + typeNames.put(Usericon.Type.BOT, "Bot"); + typeNames.put(Usericon.Type.TWITCH, "Other (Twitch)"); + typeNames.put(Usericon.Type.OTHER, "Other (Third-Party)"); + typeNames.put(Usericon.Type.HL, "Highlighted (by points)"); typeNames.put(Usericon.Type.TURBO, "Turbo"); typeNames.put(Usericon.Type.PRIME, "Prime"); typeNames.put(Usericon.Type.BITS, "Bits"); typeNames.put(Usericon.Type.ADMIN, "Admin"); typeNames.put(Usericon.Type.STAFF, "Staff"); - typeNames.put(Usericon.Type.BROADCASTER, "Broadcaster"); typeNames.put(Usericon.Type.GLOBAL_MOD, "Global Moderator"); - typeNames.put(Usericon.Type.BOT, "Bot"); - typeNames.put(Usericon.Type.TWITCH, "Other (Twitch)"); - typeNames.put(Usericon.Type.OTHER, "Other (Third-Party)"); } public void setTwitchBadgeTypes(Set types) { diff --git a/src/chatty/gui/components/settings/WindowSettings.java b/src/chatty/gui/components/settings/WindowSettings.java index a55cecddd..bd3a14802 100644 --- a/src/chatty/gui/components/settings/WindowSettings.java +++ b/src/chatty/gui/components/settings/WindowSettings.java @@ -53,6 +53,9 @@ public WindowSettings(final SettingsDialog d) { minimizing.add(d.addSimpleBooleanSetting("trayIconAlways"), d.makeGbc(2, 1, 1, 1, GridBagConstraints.WEST)); + minimizing.add(d.addSimpleBooleanSetting("singleClickTrayOpen"), + d.makeGbc(0, 2, 3, 1, GridBagConstraints.WEST)); + minimizing.add(d.addSimpleBooleanSetting("hideStreamsOnMinimize"), d.makeGbc(0, 3, 3, 1, GridBagConstraints.WEST)); diff --git a/src/chatty/gui/components/textpane/ChannelTextPane.java b/src/chatty/gui/components/textpane/ChannelTextPane.java index b1cd10764..91ceb32bd 100644 --- a/src/chatty/gui/components/textpane/ChannelTextPane.java +++ b/src/chatty/gui/components/textpane/ChannelTextPane.java @@ -541,7 +541,7 @@ private void printUserMessage(UserMessage message, String timestamp) { } else { printTimestamp(style, timestamp); } - printUser(user, action, message.whisper, message.id, background); + printUser(user, action, message.whisper, message.id, background, message.pointsHl); // Change style for text if /me and no highlight (if enabled) if (!highlighted && color == null && action && styles.isEnabled(Setting.ACTION_COLORED)) { @@ -1820,7 +1820,7 @@ private void clearSearchResult() { * @param msgId */ private void printUser(User user, boolean action, - boolean whisper, String msgId, Color background) { + boolean whisper, String msgId, Color background, boolean pointsHl) { // Decide on name based on settings and available names String userName; @@ -1840,7 +1840,7 @@ else if (styles.namesMode() != SettingsManager.DISPLAY_NAMES_MODE_CAPITALIZED // Badges or Status Symbols if (styles.isEnabled(Setting.USERICONS_ENABLED)) { - printUserIcons(user); + printUserIcons(user, pointsHl); } else { userName = user.getModeSymbol()+userName; @@ -1956,9 +1956,9 @@ private Color makeGoldColor(int i, int length) { * * @param user */ - private void printUserIcons(User user) { + private void printUserIcons(User user, boolean pointsHl) { boolean botBadgeEnabled = styles.isEnabled(Setting.BOT_BADGE_ENABLED); - java.util.List badges = user.getBadges(botBadgeEnabled); + java.util.List badges = user.getBadges(botBadgeEnabled, pointsHl); if (badges != null) { for (Usericon badge : badges) { if (badge.image != null && !badge.removeBadge) { @@ -2455,7 +2455,7 @@ private void findEmoticons(String text, User user, Map ranges, if (tagEmotes != null) { // Add emotes from tags - Map emoticonsById = main.emoticons.getEmoticonsById(); + Map emoticonsById = main.emoticons.getEmoticonsById(); addTwitchTagsEmoticons(user, emoticonsById, text, ranges, rangesStyle, tagEmotes); } @@ -2487,7 +2487,7 @@ private void findEmoticons(String text, User user, Map ranges, * @param rangesStyle The styles for this message * @param emotesDef The emotes definition from the IRCv3 tags */ - private void addTwitchTagsEmoticons(User user, Map emoticons, String text, + private void addTwitchTagsEmoticons(User user, Map emoticons, String text, Map ranges, Map rangesStyle, TagEmotes emotesDef) { if (emotesDef == null) { @@ -2513,7 +2513,7 @@ private void addTwitchTagsEmoticons(User user, Map emoticons, if (def.containsKey(i-offset)) { // An emote starts at the current position, so add it. Emoticons.TagEmote emoteData = def.get(i-offset); - int id = emoteData.id; + String id = emoteData.id; int start = i; int end = emoteData.end+offset; @@ -2534,15 +2534,8 @@ private void addTwitchTagsEmoticons(User user, Map emoticons, String url = Emoticon.getTwitchEmoteUrlById(id, 1); Emoticon.Builder b = new Emoticon.Builder( Emoticon.Type.TWITCH, code, url); - b.setNumericId(id); - Emoteset emotesetInfo = main.emoticons.getInfoByEmoteId(id); - if (emotesetInfo != null) { - b.setEmoteset(emotesetInfo.emoteset_id); - b.setStream(emotesetInfo.stream); - b.setEmotesetInfo(emotesetInfo.product); - } else { - b.setEmoteset(Emoticon.SET_UNKNOWN); - } + b.setStringId(id); + b.setEmoteset(Emoticon.SET_UNKNOWN); emoticon = b.build(); main.emoticons.addTempEmoticon(emoticon); } diff --git a/src/chatty/gui/components/textpane/LinkController.java b/src/chatty/gui/components/textpane/LinkController.java index 65d463d5c..d99bacc43 100644 --- a/src/chatty/gui/components/textpane/LinkController.java +++ b/src/chatty/gui/components/textpane/LinkController.java @@ -753,6 +753,9 @@ private static void makeUsericonPopupText(Usericon usericon, MyPopup p) { String info; if (!usericon.metaTitle.isEmpty()) { info = POPUP_HTML_PREFIX+"Badge: "+usericon.metaTitle; + } else if (usericon.type == Usericon.Type.HL) { + // Customize text since not really a badge + info = POPUP_HTML_PREFIX+usericon.type.label; } else { info = POPUP_HTML_PREFIX+"Badge: "+usericon.type.label; } diff --git a/src/chatty/gui/components/textpane/UserMessage.java b/src/chatty/gui/components/textpane/UserMessage.java index 1b74a83ca..fe635bd64 100644 --- a/src/chatty/gui/components/textpane/UserMessage.java +++ b/src/chatty/gui/components/textpane/UserMessage.java @@ -23,6 +23,7 @@ public class UserMessage extends Message { public boolean highlighted; public boolean ignored_compact; public boolean action; + public boolean pointsHl; public UserMessage(User user, String text, Emoticons.TagEmotes emotes, String id, int bits, List highlightMatches, diff --git a/src/chatty/gui/icon_hl.png b/src/chatty/gui/icon_hl.png new file mode 100644 index 000000000..5145c80fa Binary files /dev/null and b/src/chatty/gui/icon_hl.png differ diff --git a/src/chatty/gui/notifications/Notification.java b/src/chatty/gui/notifications/Notification.java index 812d8ff9b..2998d5c5f 100644 --- a/src/chatty/gui/notifications/Notification.java +++ b/src/chatty/gui/notifications/Notification.java @@ -7,6 +7,7 @@ import chatty.gui.Highlighter; import chatty.util.colors.HtmlColors; import chatty.util.StringUtil; +import chatty.util.irc.MsgTags; import java.awt.Color; import java.util.ArrayList; import java.util.Collection; @@ -267,11 +268,11 @@ public boolean matchesChannel(String channel) { return channel.equalsIgnoreCase(this.channel); } - public boolean matches(String text, String channel, Addressbook ab, User user) { + public boolean matches(String text, String channel, Addressbook ab, User user, MsgTags tags) { if (matcherItem == null || text == null) { return true; } - return matcherItem.matches(Highlighter.HighlightItem.Type.ANY, text, null, channel, ab, user); + return matcherItem.matches(Highlighter.HighlightItem.Type.ANY, text, null, channel, ab, user, tags); } public boolean hasChannel() { diff --git a/src/chatty/gui/notifications/NotificationManager.java b/src/chatty/gui/notifications/NotificationManager.java index a50f648ba..ec9d5697b 100644 --- a/src/chatty/gui/notifications/NotificationManager.java +++ b/src/chatty/gui/notifications/NotificationManager.java @@ -19,6 +19,7 @@ import chatty.util.api.Follower; import chatty.util.api.FollowerInfo; import chatty.util.api.StreamInfo; +import chatty.util.irc.MsgTags; import chatty.util.settings.Settings; import java.nio.file.Path; import java.nio.file.Paths; @@ -92,10 +93,10 @@ public void streamInfoChanged(String channel, StreamInfo info) { }); } - public void highlight(User user, String message, boolean noNotify, + public void highlight(User user, String message, MsgTags tags, boolean noNotify, boolean noSound, boolean isOwnMessage, boolean isWhisper, boolean hasBits) { - check(null, user.getChannel(), user, message, noNotify, noSound, n -> { + check(null, user.getChannel(), user, message, tags, noNotify, noSound, n -> { if (isOwnMessage && !n.hasOption("own")) { return null; } @@ -130,7 +131,7 @@ public void highlight(User user, String message, boolean noNotify, public void infoHighlight(Room room, String message, boolean noNotify, boolean noSound) { String channel = room != null ? room.getChannel() : null; - check(null, channel, null, message, noNotify, noSound, n -> { + check(null, channel, null, message, MsgTags.EMPTY, noNotify, noSound, n -> { boolean hasChannel = channel != null && !channel.isEmpty(); if (n.type == Type.HIGHLIGHT) { String title; @@ -174,9 +175,9 @@ public void info(Room room, String text) { }); } - public void message(User user, String message, boolean isOwnMessage, - boolean hasBits) { - check(Type.MESSAGE, user.getChannel(), user, message, n -> { + public void message(User user, String message, MsgTags tags, + boolean isOwnMessage, boolean hasBits) { + check(Type.MESSAGE, user.getChannel(), user, message, tags, n -> { if (isOwnMessage && !n.hasOption("own")) { return null; } @@ -272,16 +273,21 @@ public NotificationData(String title, String message) { } private void check(Type type, String channel, NotificationChecker c) { - check(type, channel, null, null, false, false, c); + check(type, channel, null, null, MsgTags.EMPTY, false, false, c); } private void check(Type type, String channel, User user, String message, NotificationChecker c) { - check(type, channel, user, message, false, false, c); + check(type, channel, user, message, MsgTags.EMPTY, false, false, c); + } + + private void check(Type type, String channel, User user, String message, + MsgTags tags, NotificationChecker c) { + check(type, channel, user, message, tags, false, false, c); } private void check(Type type, String channel, User user, - String message, boolean noNotify, boolean noSound, + String message, MsgTags tags, boolean noNotify, boolean noSound, NotificationChecker c) { boolean shown = false; boolean played = false; @@ -289,7 +295,7 @@ private void check(Type type, String channel, User user, if (n.hasEnabled() && (type == n.type || type == null) && n.matchesChannel(channel) - && n.matches(message, channel, ab, user)) { + && n.matches(message, channel, ab, user, tags)) { NotificationData d = c.check(n); if (d != null) { diff --git a/src/chatty/lang/Strings.properties b/src/chatty/lang/Strings.properties index 98de126aa..1d6c71e77 100644 --- a/src/chatty/lang/Strings.properties +++ b/src/chatty/lang/Strings.properties @@ -506,7 +506,7 @@ settings.page.look = Look settings.page.chatColors = Chat Colors settings.page.msgColors = Msg. Colors settings.page.usercolors = Usercolors -settings.page.usericons = Usericons +settings.page.usericons = Badges settings.page.emoticons = Emoticons settings.page.fonts = Fonts settings.page.chat = Chat @@ -685,8 +685,8 @@ settings.long.nickColorBackground.option.2 = Strong settings.label.displayColoredNamesInUserlist = Display colored usernames in userlist !-- Usericons --! -settings.section.usericons = Usericon Settings (Badges) -settings.boolean.usericonsEnabled = Show Usericons +settings.section.usericons = Badge Settings +settings.boolean.usericonsEnabled = Show Badges settings.boolean.botBadgeEnabled = Show Bot Badge !-- Emoticons --! @@ -816,6 +816,8 @@ settings.boolean.highlightMatches = Mark Highlight/Ignore matches settings.boolean.highlightMatches.tip = Surrounds sections of text that caused the Highlight with a rectangle (and in the Highlighted/Ignored Messages windows). settings.boolean.highlightMatchesAll = Mark all occurences settings.boolean.highlightMatchesAll.tip = Also mark Highlight matches in e.g. links and mentions +settings.boolean.highlightByPoints = Highlight by channel points +settings.boolean.highlightByPoints.tip = Highlight messages that are "highlighted" by using channel points !-- Ignore --! settings.section.ignoreMessages = Ignore Messages @@ -932,6 +934,8 @@ settings.boolean.closeToTray = Close to tray settings.boolean.trayIconAlways = Always show tray icon settings.boolean.hideStreamsOnMinimize = Hide Live Streams window on minimize settings.boolean.hideStreamsOnMinimize.tip = Automatically hide Live Streams window when minimizing Main window +settings.boolean.singleClickTrayOpen = Single-click on tray icon to show/hide +settings.boolean.singleClickTrayOpen.tip = With this unchecked, a double-click may be required (platform dependent) !-- Other Window Settings --! settings.section.otherWindow = Other diff --git a/src/chatty/lang/Strings_de.properties b/src/chatty/lang/Strings_de.properties index a665dc4d5..0707fb8bf 100644 --- a/src/chatty/lang/Strings_de.properties +++ b/src/chatty/lang/Strings_de.properties @@ -75,6 +75,8 @@ channelCm.copyStreamname = Streamnamen kopieren channelCm.dialog.chatRules = Chatregeln anzeigen channelCm.follow = Kanal folgen channelCm.unfollow = Kanal entfolgen +channelCm.favorite = Favorisieren +channelCm.unfavorite = Entfavorisieren channelCm.speedruncom = Speedrun.com öffnen channelCm.closeChannel = Kanal schließen # Uses plural form and shows number when joining more than one channel @@ -113,7 +115,7 @@ emoteCm.showEmotes = Emotes anzeigen !==================! # {0} = Number of Live Streams, {1} = Current sorting streams.title = Live Streams ({0}) [Sortierung\: {1}] -streams.sorting.recent = Kürzlich aktualisiert +streams.sorting.recent = Als letztes geändert streams.sorting.recent.tip = Stream Dauer oder vor kurzem Titel/Spiel geändert streams.sorting.uptime = Laufzeit (Neueste) streams.sorting.uptime.tip = Stream-Start @@ -123,6 +125,8 @@ streams.sorting.game = Spiel streams.sorting.game.tip = Spiel Name (alphabetisch) streams.sorting.viewers = Zuschauer streams.sorting.viewers.tip = Höchste Zuschauerzahl +streams.sortingOption.fav = Favoriten zuerst +streams.sortingOption.fav.tip = Kanäle die in "Kanäle - Favoriten / Chronik" als Favoriten hinzugefügt sind befinden sich oben in der Liste streams.cm.menu.sortBy = Sotieren nach.. streams.cm.removedStreams = Entfernte Streams.. streams.cm.refresh = Aktualisieren @@ -171,6 +175,12 @@ dialog.button.ok = OK dialog.button.test = Testen # Open a help of sorts dialog.button.help = Hilfe +# Edit something +dialog.button.edit = Bearbeiten +# Indiviualize something +dialog.button.customize = Anpassen +# Change some setting +dialog.button.change = Ändern !====================! !== General Status ==! @@ -209,7 +219,7 @@ login.access.user.tip = Genutzt um Livestreams denen du folgst anzuzeigen login.access.editor = Editor Zugriff login.access.editor.tip = Zum Ändern von Streameinstellungen in der Kanaladministration login.access.broadcast = Übertragung bearbeiten -login.access.broadcast.tip = Wird zur Erstellung von Stream-Markierungen benötigt +login.access.broadcast.tip = Wird zur Erstellung von Stream-Markierungen/Tags setzen benötigt # Commercials as in Advertisements login.access.commercials = Werbung starten login.access.commercials.tip = Um Werbung auf deinem Kanal zu schalten @@ -283,7 +293,7 @@ admin.infoUpdated = Info aktualisiert. !-- Status History --! admin.presets.title = Status Vorlagen (Aktuelles Spiel\: {0}) -admin.presets.button.useStatus = Status verwenden (Titel/Spiel) +admin.presets.button.useStatus = Status verwenden (Titel/Spiel/Tags) admin.presets.button.useTitleOnly = Nur Titel verwenden admin.presets.column.fav = Fav admin.presets.column.title = Titel @@ -469,10 +479,11 @@ settings.chooseFolder.button.default = Standard settings.chooseFolder.button.open = Öffnen !-- Chat Font --! -settings.section.chatFont = Chat Schriftart -settings.chatFont.button.selectFont = Schriftart wählen -settings.chatFont.fontName = Schriftart\: -settings.chatFont.fontSize = Schriftgröße\: +settings.section.chatFonts = Chat Schriftarten +settings.chatFont.chatFont = Nachrichten\: +settings.boolean.timestampFontEnabled = Angepasste Zeitstempel Schriftart +settings.chatFont.timestampFont = Zeitstempel\: +settings.chatFont.timestampFont.info = Der Zeitstempel kann unter "Chatfarben" und "Nachrichten" weiter angepasst werden. settings.chatFont.lineSpacing = Zeilenabstand\: settings.chatFont.option.smallest = Am Kleinsten settings.chatFont.option.smaller = Kleiner @@ -485,16 +496,19 @@ settings.chatFont.messageSpacing = Nachrichtenabstand\: settings.chatFont.bottomMargin = Unterer Rand\: !-- Input / Userlist Font --! -settings.section.otherFonts = Schriftart für Eingabefeld / Benutzerliste +settings.section.chatSpacings = Chat Abstände settings.otherFonts.inputFont = Eingabefeld\: settings.otherFonts.userlistFont = Benutzerliste\: settings.otherFonts.info = Hinweis\: Die allgemeine Schriftgröße des Programms kann unter 'Aussehen' eingestellt werden (bei Nutzung mancher Look & Feel). +settings.otherFonts.restrictedInfo = Die Auswahl der Schriftarten ist begrenzt um einen Bug zu vermeiden. !-- Choose font --! settings.chooseFont.title = Schriftart auswählen settings.chooseFont.selectFont = Schriftart und Größe auswählen settings.chooseFont.preview = Vorschau settings.chooseFont.enterText = Zusätzlichen Text zum Testen eingeben +settings.chooseFont.bold = Fett +settings.chooseFont.italic = Kursiv !-- Startup --! settings.section.startup = Starten @@ -548,6 +562,10 @@ settings.boolean.highlightBackground = Hintergrund für Hervorgehobene Nachricht settings.colors.highlightBackground = Hintergrund (Hervorgehobene Nachricht) settings.colors.inputBackground = Eingabe Hintergrund settings.colors.inputForeground = Eingabe Text +settings.boolean.timestampColorEnabled = Individualisierte Zeitstempel-Farbe +settings.label.timestampColorInherit = Besondere Farben erben\: +settings.label.timestampColorInherit.tip = Ist dies aktiviert erbt der Zeitstempel die Farbe von z.B. Info oder Hervorgehobenen Nachrichten (anstatt die individualisierte Zeitstempel-Farbe zu verwenden). Bei niedrigeren Prozenten wird die Helligkeit der geerbten Farbe mehr an die individualisierte Zeitstempel-Farbe angepasst. +settings.colors.timestamp = Zeitstempel settings.colors.searchResult = Suchergebnis settings.colors.searchResult2 = Hervorgeh. Suchergebnis settings.colors.lookandfeel = Hinweis\: Das allgemeine Aussehen des Programms wird durch das Look & Feel unter 'Aussehen' festgelegt. @@ -568,6 +586,7 @@ settings.section.msgColors = Eigene Nachrichtenfarben settings.boolean.msgColorsEnabled = Eigene Nachrichtenfarben aktivieren settings.boolean.msgColorsEnabled.tip = Wenn aktiviert können die Einträge in der Tabelle die Farben von Chatnachrichten beeinflussen settings.section.msgColorsOther = Weitere Einstellungen +settings.boolean.msgColorsPrefer = Eigene Nachrichtenfarben gegenüber Hervorheben-Farben bevorzugen settings.boolean.actionColored = Aktionsnachrichten (/me) in Benutzerfarbe anzeigen !-- Usercolors --! @@ -578,6 +597,11 @@ settings.string.nickColorCorrection.option.normal = Normal settings.string.nickColorCorrection.option.strong = Stark settings.string.nickColorCorrection.option.old = Alt settings.string.nickColorCorrection.option.gray = Graustufen +settings.label.nickColorBackground = Hintergrund ändern um Lesbarkeit zu verbessern +settings.label.nickColorBackground.tip = Wenn ein Benutzername auf geändertem Hintergrund schlecht lesbar ist, wird die Haupthintergrundfarbe genutzt (da Benutzerfarben für die Haupthintergrundfarbe korrigiert werden) +settings.long.nickColorBackground.option.0 = Aus +settings.long.nickColorBackground.option.1 = Normal +settings.long.nickColorBackground.option.2 = Stark !-- Usericons --! settings.section.usericons = Benutzericon Einstellungen (Abzeichen) @@ -621,6 +645,10 @@ settings.ignoredEmotes.title = Ignorierte Emoticons settings.ignoredEmotes.info1 = Für ignorierte Emoticons wird nur der Code angezeigt, d.h. sie werden nicht in ein Bild umgesetzt. settings.ignoredEmotes.info2 = Das Emoticon-Kontextmenu (rechtsklick auf Emoticon im Chat) kann zum Ignorieren genutzt werden. +!-- Chat --! +settings.boolean.showImageTooltips = Emoticon / Abzeichen Tooltips anzeigen +settings.boolean.showTooltipImages = Bild in Tooltips anzeigen + !-- Messages --! settings.section.deletedMessages = Gelöschte Nachrichten (Timeouts/Bans) settings.option.deletedMessagesMode.delete = Nachricht löschen @@ -642,6 +670,8 @@ settings.boolean.combineBanMessages.tip = Kombiniert ähnliche Ban-Nachrichten, settings.boolean.clearChatOnChannelCleared = Chat leeren wenn durch Moderator geleert settings.section.otherMessageSettings = Weitere Einstellungen settings.otherMessageSettings.timestamp = Zeitstempel\: +settings.otherMessageSettings.customizeTimestamp = Zeitstempel anpassen +settings.otherMessageSettings.customizeTimestamp.info = Der Zeitstempel kann unter "Chatfarben" und "Schriftarten" weiter angepasst werden. settings.boolean.showModMessages = Mod/Unmod anzeigen (unzuverlässig) settings.boolean.showModMessages.tip = MOD/UNMOD anzeigen wenn jemand gemoddet/entmoddet wurde oder ein Moderator dem Kanal beigetreten oder ihn verlassen hat settings.boolean.showJoinsParts = Joins/Parts anzeigen (unzuverlässig) @@ -661,17 +691,21 @@ settings.boolean.closeUserDialogOnAction = Benutzerdialogfenster nach Button-Akt settings.boolean.closeUserDialogOnAction.tip = Nach dem Klick auf einen Button wird das Benutzerdialogfenster automatisch geschlossen. settings.boolean.openUserDialogByMouse = Benutzerdialogfenster neben Mauszeiger öffnen settings.boolean.reuseUserDialog = Bereits offenenes Benutzerfenster gleichen Benutzers verwenden -settings.boolean.reuseUserDialog.tip = Beim Öffnen eines Benutzerfensters für einen Benutzer der bereits eines offen hat, nicht zum einem Neuen/Anderen wechseln +settings.boolean.reuseUserDialog.tip = Beim Öffnen eines Benutzerfensters für einen Benutzer der bereits eines offen hat, nicht zu einem Neuen/Anderen wechseln. settings.long.clearUserMessages.option.-1 = Nie settings.long.clearUserMessages.option.3 = 3 Stunden settings.long.clearUserMessages.option.6 = 6 Stunden settings.long.clearUserMessages.option.12 = 12 Stunden settings.long.clearUserMessages.option.24 = 24 Stunden settings.long.clearUserMessages.label = Nachrichtenverlauf von Benutzern löschen die inaktiv waren für\: +settings.label.banReasonsHotkey = Tastenkombination um Liste der Bangründe zu öffnen\: +settings.label.banReasonsInfo = Die Bangründe können direkt im Benutzerdialogfenster bearbeitet werden !-- Names --! settings.label.mentions = Anklickbare Erwähnungen\: settings.label.mentions.tip = Namen von Benutzern die vor kurzem etwas geschrieben haben werden in Chat-Nachrichten hevorgehoben und klickbar. +settings.label.mentionsInfo = Anlickbare Erw. (Info)\: +settings.label.mentionsInfo.tip = So wie obige Einstellung, allerdings für Info Nachrichten (z.B. Antwort auf Befehle, AutoMod) settings.label.mentionsBold = Fett settings.label.mentionsUnderline = Unterstrichen settings.label.mentionsColored = Farbig @@ -766,7 +800,14 @@ settings.completion.option.emotes = Emoticons settings.completion.option.namesEmotes = Namen, dann Emoticons settings.completion.option.emotesNames = Emoticons, dann Namen settings.completion.option.custom = Eigene Vervollständigung -settings.completion.info = Tipp\: Unabhängig von diesen Einstellungen kann immer @ vorangestellt werden um Namen zu verfollständigen, . (Punkt) für Custom Completion und \: für Emoji.

    Beispiel\: \:think + TAB > \:thinking\: +settings.completion.info = Tipp\: @ voranstellen um immer Namen zu vervollständigen, \: für Emoji und die Einstellung unten für Emotes. (Beispiel\: \:think + TAB > \:thinking\:) +settings.label.completionEmotePrefix = Twitch Emote Präfix\: +settings.label.completionEmotePrefix.tip = Nutze diesen Präfix um immer Emoticons zu vervollständigen (Emoji nutzen immer '\:'). +settings.completionEmotePrefix.option.none = Keins +settings.long.completionMixed.option.0 = Beste Übereinstimmung +settings.long.completionMixed.option.1 = Emoji zuerst +settings.long.completionMixed.option.2 = Emotes zuerst +settings.long.completionMixed.tip = Wie sortieren wenn Emotes und Emoji im Ergebnis vorkommen settings.string.completionSearch = Suche Emotes / Befehle\: # Example: Emote "joshWithIt" would match only when entering the beginning of "joshWithIt" settings.string.completionSearch.option.start = Beginn des Namens @@ -779,20 +820,23 @@ settings.boolean.completionPreferUsernames = Normale Namen für Benutzernamen-ba settings.boolean.completionAllNameTypes = Alle Namenstypen im Ergebnis (Normal/Lokalisiert/Custom) settings.boolean.completionAllNameTypesRestriction = Nur bei höchstens zwei Ergebnissen settings.section.completionAppearance = Aussehen / Verhalten -settings.boolean.completionShowPopup = Infofenster anzeigen -settings.completion.itemsShown = Max. Anzahl Einträge\: +settings.boolean.completionShowPopup = Infofenster anzeigen, mit +settings.boolean.completionShowPopup.tip = Zeige die aktuellen Vervollständigungsergebnisse in einem Popup-Fenster an +settings.label.searchResults = Suchergebnisse +settings.boolean.completionAuto = Für manche Arten automatisch anzeigen (z.B. Emoji) settings.boolean.completionCommonPrefix = Zu gemeinsamen Präfix vervollständigen (wenn mehrere Ergebnisse) settings.completion.nameSorting = Namenssortierung\: settings.completion.option.predictive = Voraussagend settings.completion.option.alphabetical = Alphabetisch settings.completion.option.userlist = Wie Benutzerliste +settings.boolean.completionSpace = Leerzeichen anhängen !-- Dialogs Settings --! settings.section.dialogs = Dialogfenster Position/Größe settings.dialogs.restore = Dialogfenster öffnen\: settings.dialogs.option.openDefault = Immer an Standardposition settings.dialogs.option.keepSession = Position während Sitzung beibehalten -settings.dialogs.option.restore = Position von letzter Sitzung beibehalten +settings.dialogs.option.restore = Position / Größe von letzter Sitzung beibehalten settings.dialogs.option.reopen = Wieder öffnen was letzte Sitzung offen war settings.boolean.restoreOnlyIfOnScreen = Position nur beibehalten wenn auf dem Bildschirm sichtbar settings.boolean.attachedWindows = Dialogfenster zusammen mit Hauptfenster bewegen @@ -837,12 +881,12 @@ settings.boolean.tabsMwheelScrollingAnywhere = Über Eingabefeld scrollen um ebe !-- Stream Settings --! settings.section.streamHighlights = Stream Höhepunkte settings.section.streamHighlightsCommand = Chatbefehl -settings.streamHighlights.info = Stream Höhepunkte (Highlights, Schreiben von Streamzeit/Notiz in Datei) können mit dem Befehl /addStreamHighlight, per Tastenkombination (Hotkeys Einstellungen) oder Moderator Befehl (hier einzustellen) hinzugefügt werden. +settings.streamHighlights.info = Stream Höhepunkte (Highlights, Schreiben von Streamzeit/Notiz in Datei) können mit dem Befehl /addStreamHighlight, per Tastenkombination (Hotkeys Einstellungen) oder Chatbefehl (hier einzustellen) hinzugefügt werden. Siehe Hilfe für mehr Informationen. settings.streamHighlights.matchInfo = Es ist sehr empfehlenswert den Chatbefehl nur vertrauenswürdigen Nutzern zugänglich zu machen, wie z.B. Moderatoren. -settings.streamHighlights.channel = Moderator Befehl Kanal\: -settings.streamHighlights.command = Moderator Befehl\: +settings.streamHighlights.channel = Befehl-Kanal\: +settings.streamHighlights.command = Befehl-Name\: settings.streamHighlights.match = Befehl Zugriff\: -settings.boolean.streamHighlightChannelRespond = Auf Moderator befehl mit Chatnachricht antworten +settings.boolean.streamHighlightChannelRespond = Auf Befehl mit Chatnachricht antworten settings.boolean.streamHighlightChannelRespond.tip = Eine Nachricht in den Chat schicken, damit Moderatoren sehen können dass der Befehl erfolgreich war settings.boolean.streamHighlightMarker = Zusätzlich eine Streammarkierung setzen settings.boolean.streamHighlightMarker.tip = Zusätzlich zum Schreiben der Infos in die Datei eine Streammarkierung setzen @@ -868,6 +912,8 @@ emotesDialog.noChannel = Kein Kanal. emotesDialog.noChannelEmotes = Keine Emotes für \#{0} gefunden emotesDialog.noChannelEmotes2 = Keine FFZ oder BTTV Emotes gefunden. emotesDialog.channelEmotes = Emotes für \#{0} +# {0} = Channel name (without leading #) +emotesDialog.backToChannel = Zurück zu \#{0} emotesDialog.subemotes = Abo-Emoticons {0} emotesDialog.subscriptionRequired2 = (Abo zur Nutzung erforderlich.) emotesDialog.globalTwitch = Globale Twitch Emotes diff --git a/src/chatty/lang/Strings_en_GB.properties b/src/chatty/lang/Strings_en_GB.properties index ad69f869f..6d4d4ba1d 100644 --- a/src/chatty/lang/Strings_en_GB.properties +++ b/src/chatty/lang/Strings_en_GB.properties @@ -24,6 +24,12 @@ emoteCm.favorite = Favourite # Remove emote from favorites emoteCm.unfavorite = Unfavourite +!====================! +!== General Dialog ==! +!====================! +# Indiviualize something +dialog.button.customize = Customise + !====================! !== Connect Dialog ==! !====================! @@ -67,6 +73,9 @@ settings.page.chatColors = Chat Colours settings.page.msgColors = Msg. Colours settings.page.usercolors = Usercolours +!-- Chat Font --! +settings.chatFont.timestampFont.info = The timestamp can be further cutsomized under "Chat Colours" and "Messages". + !-- Startup --! settings.startup.option.connectJoinFavorites = Connect and join favourited channels @@ -79,6 +88,9 @@ settings.colors.general.backgroundColor = Background Colour settings.colors.general.foregroundColor = Foreground Colour settings.colors.heading.misc = Miscellaneous Colours settings.colors.button.switchBackgrounds.tip = Switch Background and Background 2 colours, e.g. to test readability of foreground colours. +settings.boolean.timestampColorEnabled = Use custom timestamp colour +settings.label.timestampColorInherit = Inherit non-standard colour\: +settings.label.timestampColorInherit.tip = If enabled, the timestamp inherits the colour from e.g. info or highlighted messages (instead of using the custom timestamp colour). At lower percentages, the inherited colour's brightness gets adjusted to match the custom timestamp colour more. settings.colorChooser.title = Change Colour\: {0} settings.colorChooser.button.useSelected = Use selected colour settings.colorPresets.colorPresets = Colour Presets @@ -87,22 +99,43 @@ settings.colorPresets.colorPresets = Colour Presets settings.section.msgColors = Custom Message Colours settings.boolean.msgColorsEnabled = Enable custom message colours settings.boolean.msgColorsEnabled.tip = If enabled, entries in the table below can affect the colours of chat messages +settings.boolean.msgColorsPrefer = Prefer Custom Message Colours over Highlight Colours settings.boolean.actionColored = Colour action messages (/me) with Usercolour !-- Usercolors --! settings.string.nickColorCorrection = Usercolour Readability Correction\: settings.string.nickColorCorrection.option.gray = Greyscale +settings.label.nickColorBackground.tip = If a username on a custom background line is badly readable, use the main background colour instead (since the usercolours get corrected for the main background colour) + +!-- Messages --! +settings.otherMessageSettings.customizeTimestamp = Customise timestamp +settings.otherMessageSettings.customizeTimestamp.info = The timestamp can be further customised under "Chat Colours" and "Fonts". !-- Names --! +settings.label.mentions.tip = Usernames of users that recently chatted are made clickable and emphasised in chat messages. settings.label.mentionsColored = Coloured settings.label.mentionsColored.tip = Clickable mentions in user colour +settings.label.markHoveredUser.tip = Emphasise other occurances of a username you hover over with your mouse in chat. !-- Highlight --! settings.boolean.highlightEnabled.tip = Messages matching the criteria will be shown in a different colour, added to the Highlighted Messages window and trigger a notification (by default). +!-- Log to file --! +settings.boolean.logSubdirectories.tip = Organise logs into channel subdirectories + !-- TAB Completion --! +# Example: Emote "joshWithIt" would match when entering the beginning of "joshWithIt", "With" or "It" +settings.string.completionSearch.option.words = At start, and capitalised words +settings.section.completionNames = Localised Names +settings.boolean.completionAllNameTypes = Include all name types in result (Regular/Localised/Custom) settings.section.completionAppearance = Appearance / Behaviour +!-- Minimizing Settings --! +settings.section.minimizing = Minimising / Tray +settings.boolean.minimizeToTray = Minimise to tray +settings.boolean.hideStreamsOnMinimize = Hide Live Streams window on minimise +settings.boolean.hideStreamsOnMinimize.tip = Automatically hide Live Streams window when minimising Main window + !===================! !== Emotes Dialog ==! !===================! @@ -111,3 +144,4 @@ emotesDialog.noFavorites = You haven't added any favourite emotes emotesDialog.noFavorites.hint = Right-click on emote and choose 'Favourite') emotesDialog.notFoundFavorites = Favourites not yet found (metadata not loaded) emotesDialog.favoriteCmInfo = (Right-click to view info and unfavourite, if necessary.) +emotesDialog.subEmotesJoinChannel = (Must join a channel for them to be recognised.) diff --git a/src/chatty/lang/Strings_es.properties b/src/chatty/lang/Strings_es.properties index 63b53d616..f2c753bfa 100644 --- a/src/chatty/lang/Strings_es.properties +++ b/src/chatty/lang/Strings_es.properties @@ -63,9 +63,9 @@ menubar.about = Sobre / Ayuda !===================! chat.joining = Uniendose {0} chat.joined = Te has unido {0} -chat.joinError.notConnected = No puedes unirte '{0}' (no está conectado) -chat.joinError.alreadyJoined = No puedes unirte '{0}' (ya te uniste) -chat.joinError.invalid = No puedes unirte '{0}' (nombre de canal inválido) +chat.joinError.notConnected = No puedes unirte ''{0}'' (no está conectado) +chat.joinError.alreadyJoined = No puedes unirte ''{0}'' (ya te uniste) +chat.joinError.invalid = No puedes unirte ''{0}'' (nombre de canal inválido) chat.disconnected = Desconectado chat.error.unknownHost = Host desconocido chat.error.connectionTimeout = El tiempo de conexión expiró\n diff --git a/src/chatty/lang/Strings_fr.properties b/src/chatty/lang/Strings_fr.properties index a32070bd5..888ed5561 100644 --- a/src/chatty/lang/Strings_fr.properties +++ b/src/chatty/lang/Strings_fr.properties @@ -55,6 +55,7 @@ menubar.dialog.moderationLog = Logs de modération menubar.dialog.chatRules = Règles du chat # This is in context of the "StreamChat" submenu menubar.dialog.streamchat = Ouvrir la fenêtre +menubar.stream.addhighlight = Ajouter un temps fort !-- Help Menu --! menubar.menu.help = Aide @@ -112,10 +113,16 @@ emoteCm.showEmotes = Afficher les emotes !==================! # {0} = Number of Live Streams, {1} = Current sorting streams.title = Streams en direct ({0}) [Classement\: {1}] -streams.sorting.recent = Plus récent +streams.sorting.recent = Actualisation (Plus récente) +streams.sorting.recent.tip = Stream lancé ou tire/jeu changé le plus récemment +streams.sorting.uptime = Durée (Plus récent) +streams.sorting.uptime.tip = Stream lancé le plus récemment streams.sorting.name = Nom +streams.sorting.name.tip = Nom de la chaîne (alphabétiquement) streams.sorting.game = Jeux +streams.sorting.game.tip = Nom du jeu (alphabétiquement) streams.sorting.viewers = Spectateurs +streams.sorting.viewers.tip = Plus grand nombre de spectateurs streams.cm.menu.sortBy = Classer par.. streams.cm.removedStreams = Streams supprimés.. streams.cm.refresh = Rafraichir @@ -165,6 +172,11 @@ dialog.button.test = Test # Open a help of sorts dialog.button.help = Aide +!====================! +!== General Status ==! +!====================! +status.loading = Chargement.. + !====================! !== Connect Dialog ==! !====================! @@ -249,7 +261,7 @@ ignoredDialog.info = Ignorés sur {0}\: !==========================! !== Channel Admin Dialog ==! !==========================! -admin.title = Administration de la chaîne +admin.title = Administration de la chaîne - {0} admin.tab.status = Informations # Commercial as in Advertisement admin.tab.commercial = Publicités @@ -258,6 +270,8 @@ admin.button.presets = Présélections admin.button.fav = Fav admin.button.selectGame = Choisir le jeu admin.button.removeGame = Supprimer +admin.button.selectTags = Choisir les tags +admin.button.removeTags = Supprimer admin.button.update = Mettre à jour # {0} = Duration since last update (e.g. 5m) admin.infoLoaded = Infos chargées il y a {0} @@ -274,6 +288,7 @@ admin.presets.button.useTitleOnly = Utiliser le titre uniquement admin.presets.column.fav = Fav admin.presets.column.title = Titre admin.presets.column.game = Jeu +admin.presets.column.tags = Tags admin.presets.column.lastActivity = Dernière activité admin.presets.column.usage = Utilisation admin.presets.setting.currentGame = Jeu actuel @@ -283,6 +298,8 @@ admin.presets.cm.toggleFavorite = Ajouter/retirer des Favoris admin.presets.cm.remove = Supprimer admin.presets.cm.useStatus = Utiliser la présélection admin.presets.cm.useTitleOnly = Utiliser le titre uniquement +admin.presets.cm.useGameOnly = Utiliser le jeu uniquement +admin.presets.cm.useTagsOnly = Utiliser les tags uniquement # HTML admin.presets.info = Sélectionnez et appuyez sur F pour ajouter/retirer des Favoris, Supp pour supprimer, ou click droit pour ouvrir le menu contextuel. @@ -298,6 +315,21 @@ admin.game.searching = Recherche.. # {0} = Number of search results, {1} = Number of favorites admin.game.listInfo = Recherche\: {0} / Favoris\: {1} +!-- Select Tags --! +admin.tags.title = Choisissez jusqu''à {0} tags +admin.tags.listInfo = Tags\: {0}/{1} - Favoris\: {2}/{3} +admin.tags.info = Double-cliquer sur un tag de la liste pour ajouter/supprimer. Décocher "Afficher seulement Actif / Favoris" pour afficher tous les tags. Taper du texte pour chercher dans la liste. +admin.tags.button.showAll = Afficher seulement Actif / Favoris +admin.tags.button.clearFilter = Effacer la recherche +# Favorite as an action, to add to favorites +admin.tags.button.favorite = Favoris +# Unfavorite as an action, to remove from favorites +admin.tags.button.unfavorite = Retirer +admin.tags.button.addSelected = Ajouter la sélection +admin.tags.button.remove.tip = Supprimer ''{0}'' +admin.tags.cm.replaceAll = Remplacer tous +admin.tags.cm.replace = Remplacer ''{0}'' + !=========================! !== Channel Info Dialog ==! !=========================! @@ -335,6 +367,7 @@ channelInfo.viewers.cm.verticalZoom = Zoom vertical !=================! userDialog.title = Utilisateur\: userDialog.setting.pin = Épingler +userDialog.setting.pin.tip = La fenêtre reste ouverte sur le même utilisateur jusqu'à la fermeture manuelle userDialog.editBanReasons = Éditer les préréglages de ban (un par ligne) userDialog.selectBanReason = Sélectionner une raison de ban (optionnel) userDialog.customReason = Raison personnalisée\: @@ -345,6 +378,14 @@ userDialog.registered = Enregistré\: il y a {0} userDialog.registered.tip = Compte créé\: {0} # {0} = Number of followers userDialog.followers = Followers\: {0} +# {0} = User ID +userDialog.id = ID\: {0} +# {0} = Follow age (example: "1.5 years") +userDialog.followed = Suit\: depuis {0} +# {0} = Follow age (example: "1 year 277 days"), {1} = Follow date/time +userDialog.followed.tip = Suit\: depuis {0} ({1}) +userDialog.notFollowing = Ne suit pas +userDialog.error = Erreur de la requête !=======================! !== Chat Rules Dialog ==! @@ -428,10 +469,6 @@ settings.chooseFolder.button.default = Par défaut settings.chooseFolder.button.open = Ouvrir !-- Chat Font --! -settings.section.chatFont = Police du Chat -settings.chatFont.button.selectFont = Sélectionner la police -settings.chatFont.fontName = Nom de la police\: -settings.chatFont.fontSize = Taille de la police\: settings.chatFont.lineSpacing = Interligne\: settings.chatFont.option.smallest = Très petit settings.chatFont.option.smaller = Plus petit @@ -444,7 +481,6 @@ settings.chatFont.messageSpacing = Inter-message\: settings.chatFont.bottomMargin = Marge du bas\: !-- Input / Userlist Font --! -settings.section.otherFonts = Police des champ de saisie/liste d’utilisateurs settings.otherFonts.inputFont = Champ de saisie\: settings.otherFonts.userlistFont = Liste d’utilisateurs\: settings.otherFonts.info = Note\: La taille globale de la Police du programme peut-être modifiée dans la partie 'Apparence'. @@ -458,7 +494,7 @@ settings.chooseFont.enterText = Tapez du texte afin de tester !-- Startup --! settings.section.startup = Démarrage settings.boolean.splash = Afficher l'écran de lancement -settings.boolean.splash.tip = Une petite fenêtre apparait afin d'indiquer que Chatty est en train de se lancer. +settings.boolean.splash.tip = Une petite fenêtre apparait afin d'indiquer que Chatty est en train de se lancer settings.startup.onStart = Au lancement\: settings.startup.channels = Chaînes\: settings.startup.option.doNothing = Ne rien faire @@ -511,7 +547,7 @@ settings.colors.searchResult = Résultat de recherche settings.colors.searchResult2 = Surlignage du résultat de la recherche settings.colors.lookandfeel = Note\: L'apparence générale du programme est affectée par l’apparence sur la page des paramètres "Rechercher". settings.colors.button.choose = Choisir -settings.colorChooser.title = Changer la couleur +settings.colorChooser.title = Changer la couleur\: {0} settings.colorChooser.button.useSelected = Utiliser la couleur choisie settings.colorPresets.colorPresets = Présélection de couleurs settings.colorPresets.option.default = Par défaut @@ -615,10 +651,33 @@ settings.boolean.showModActionsRestrict = Masquer si l'action associée est déj settings.boolean.showModActionsRestrict.tip = Si cette action de modération est déjà accompagnée du @Modérateur dans un message d'information affiché dans le chat, il n'y aura pas d'autre message spécifique affiché pour cette action de modération. settings.boolean.showActionBy = Préciser quel @Modérateur à effectué l'action (ex\: ban) settings.boolean.showActionBy.tip = Si plusieurs modérateurs réalisent la même action à peu de temps d'intervalle, cette information peut être faussée. -settings.section.userDialog = Boite de dialogue -settings.boolean.closeUserDialogOnAction = Fermer la boite de dialogue lorsque vous faites une action (Bouton) -settings.boolean.closeUserDialogOnAction.tip = Après avoir cliqué sur un bouton, la boite de dialogue utilisateur se fermera automatiquement. -settings.boolean.openUserDialogByMouse = Toujours ouvrir la boite de dialogue près du pointeur de souris +settings.section.userDialog = Fenêtre d'utilisateur +settings.boolean.closeUserDialogOnAction = Fermer la fenêtre d'utilisateur lorsque vous interagissez avec (Bouton) +settings.boolean.closeUserDialogOnAction.tip = Après avoir cliqué sur un bouton, la fenêtre des informations de l'utilisateur se fermera automatiquement. +settings.boolean.openUserDialogByMouse = Toujours ouvrir la fenêtre de l'utilisateur près du pointeur de la souris +settings.boolean.reuseUserDialog = Réutiliser la fenêtre d'un utilisateur déjà ouverte +settings.boolean.reuseUserDialog.tip = Lorsque la fenêtre d'un utilisateur est déjà ouverte, ne pas en ouvrir une nouvelle mais se servir de l'existante. +settings.long.clearUserMessages.option.-1 = Jamais +settings.long.clearUserMessages.option.3 = 3 heures +settings.long.clearUserMessages.option.6 = 6 heures +settings.long.clearUserMessages.option.12 = 12 heures +settings.long.clearUserMessages.option.24 = 24 heures +settings.long.clearUserMessages.label = Effacer l'historique des messages des utilisateurs inactifs depuis\: + +!-- Names --! +settings.label.mentions = Mentions cliquables\: +settings.label.mentions.tip = Les pseudos des utilisateurs qui ont récemment parlé sont rendu cliquables et leurs messages mis en évidence dans le chat. +settings.label.mentionsBold = Gras +settings.label.mentionsUnderline = Souligné +settings.label.mentionsColored = Colorisé +settings.label.mentionsColored.tip = Mentions cliquables de la couleur de l'utilisateur +settings.long.markHoveredUser.option.0 = Désactivé +settings.long.markHoveredUser.option.1 = Tous / Toujours +settings.long.markHoveredUser.option.2 = Seulement les mentions +settings.long.markHoveredUser.option.3 = Seulement les mentions, toutes avec Ctrl enfoncé +settings.long.markHoveredUser.option.4 = Seulement avec Ctrl enfoncé +settings.label.markHoveredUser = Encadre le pseudo survolé +settings.label.markHoveredUser.tip = Met en évidence les autres occurrences d'un utilisateur dont vous survolez le pseudo dans le chat !-- Highlight --! settings.section.highlightMessages = Messages mis en évidence @@ -634,6 +693,8 @@ settings.boolean.highlightIgnored = Inclure les messages ignorés settings.boolean.highlightIgnored.tip = Permet aux messages ignorés d'être mis en évidence, sinon les messages ignorés ne seront jamais mis en évidence. settings.boolean.highlightMatches = Encadrer la correspondance settings.boolean.highlightMatches.tip = Entourer d'un rectangle la section du texte qui a causé la mise en évidence (ainsi que dans la fenêtre des messages mis en évidence/ignorés). +settings.boolean.highlightMatchesAll = Encadrer toutes les occurrences +settings.boolean.highlightMatchesAll.tip = Encadrer aussi les mises en évidence correspondantes par exemple dans les liens et les mentions !-- Ignore --! settings.section.ignoreMessages = Messages ignorés @@ -714,7 +775,6 @@ settings.boolean.completionAllNameTypes = Inclure tous les types de pseudos dans settings.boolean.completionAllNameTypesRestriction = Seulement quand il n’y a pas plus de 2 correspondances settings.section.completionAppearance = Apparence / Comportement settings.boolean.completionShowPopup = Afficher les résultats en pop-up -settings.completion.itemsShown = Résultats par page\: settings.boolean.completionCommonPrefix = Compléter avec des préfixes courants (s’il y a plus d'une réponse) settings.completion.nameSorting = Tri des pseudos\: settings.completion.option.predictive = Prédictif @@ -726,14 +786,18 @@ settings.section.dialogs = Position/Taille des fenêtres annexes settings.dialogs.restore = Restaurer les fenêtres annexes\: settings.dialogs.option.openDefault = Toujours utiliser la position par défaut settings.dialogs.option.keepSession = Conserver la position pendant la session -settings.dialogs.option.restore = Restaurer la position de la session précédente -settings.dialogs.option.reopen = Rouvrir et restaurer la position de la session précédente +settings.dialogs.option.restore = Conserver la position / taille de la session précédente +settings.dialogs.option.reopen = Rouvrir de la session précédente settings.boolean.restoreOnlyIfOnScreen = Restaurer la position seulement si elle est à l’écran settings.boolean.attachedWindows = Déplacer les fenêtres annexes lors du déplacement de la fenêtre principale !-- Minimizing Settings --! -settings.boolean.minimizeToTray = Réduire dans la zone de notifications -settings.boolean.closeToTray = Fermer dans la zone de notifications +settings.section.minimizing = Minimiser / Barre des tâches +settings.boolean.minimizeToTray = Réduire dans la barre des tâches +settings.boolean.closeToTray = Fermer dans la barre des tâches +settings.boolean.trayIconAlways = Toujours afficher l’icône de la barre des tâches +settings.boolean.hideStreamsOnMinimize = Masque aussi la fenêtre des streams en direct +settings.boolean.hideStreamsOnMinimize.tip = Masque automatiquement la fenêtre des streams en direct lorsque la fenêtre principale est minimisée !-- Other Window Settings --! settings.section.otherWindow = Autres @@ -766,12 +830,15 @@ settings.boolean.tabsMwheelScrollingAnywhere = Permettre aussi de le faire sur l !-- Stream Settings --! settings.section.streamHighlights = Temps forts du stream -settings.streamHighlights.info = Les temps forts du stream (inscription de heure/note dans un fichier) peuvent être ajouter avec la commande /addStreamHighlight, avec un raccourci (Paramètres des Raccourcis) ou avec une commande de modération (que vous pouvez configurer ici même). Consulter l'Aide pour de plus amples informations. -settings.streamHighlights.channel = Commande de modération de la chaîne\: -settings.streamHighlights.command = Commande de modération\: +settings.section.streamHighlightsCommand = Commande dans le chat +settings.streamHighlights.info = Les temps forts du stream (enregistrement de l'heure et d'un commentaire dans un fichier) peuvent être ajoutés avec la commande /addStreamHighlight, avec un raccourci (section Raccourcis des Paramètres) ou avec une commande dans le chat (que vous pouvez configurer ici même). Consulter l'Aide pour de plus amples informations. +settings.streamHighlights.matchInfo = Il est fortement recommandé de restreindre la commande dans le chat à des utilisateurs de confiances, comme des Modérateurs. +settings.streamHighlights.channel = Chaîne de la commande\: +settings.streamHighlights.command = Nom de la commande\: +settings.streamHighlights.match = Accès à la commande\: settings.boolean.streamHighlightChannelRespond = Confirmer la commande de modération avec un message dans le chat settings.boolean.streamHighlightChannelRespond.tip = Envoyer un message dans le chat afin que les modérateurs puissent voir que la commande s'est correctement effectuée. -settings.boolean.streamHighlightMarker = Créer aussi un Repère sur le stream +settings.boolean.streamHighlightMarker = Créer un Repère sur le stream lors de l'ajout d'un Temps fort settings.boolean.streamHighlightMarker.tip = Crée un Repère sur le stream en plus d'écrire le temps fort dans un fichier !===================! @@ -796,7 +863,7 @@ emotesDialog.noChannelEmotes = Aucunes emotes trouvées pour \#{0} emotesDialog.noChannelEmotes2 = Aucunes emotes FFZ ou BTTV trouvées emotesDialog.channelEmotes = Emotes spécifiques à \#{0} emotesDialog.subemotes = Emotes abonnés de {0} -emotesDialog.subscriptionRequired2 = (Nécessaire d’être abonné à cette chaîne pour pouvoir les utiliser) +emotesDialog.subscriptionRequired2 = (Nécessite d’être abonné à cette chaîne pour pouvoir les utiliser) emotesDialog.globalTwitch = Emotes Twitch basiques emotesDialog.globalFFZ = Emotes FFZ basiques emotesDialog.globalBTTV = Emotes BTTV basiques diff --git a/src/chatty/lang/Strings_it.properties b/src/chatty/lang/Strings_it.properties new file mode 100644 index 000000000..eb5548521 --- /dev/null +++ b/src/chatty/lang/Strings_it.properties @@ -0,0 +1,887 @@ +## +## Contributors: Wally_team +## +## This file is UTF-8 encoded. +## + +!==================! +!== Main Menubar ==! +!==================! + +!-- Main Menu --! +menubar.menu.main = Menù +menubar.dialog.connect = Connesso +menubar.action.disconnect = Disconnesso +menubar.dialog.settings = Impostazioni +menubar.dialog.login = Login.. +menubar.dialog.save = Salva.. +menubar.application.exit = Esci + +!-- View Menu --! +menubar.menu.view = Visualizza +menubar.setting.ontop = Sempre in primo piano +menubar.menu.options = Opzioni +menubar.menu.titlebar = Barra del titolo +menubar.setting.titleShowUptime = Tempo di attività dello Stream +menubar.setting.titleLongerUptime = Maggiori dettagli sullo stato di attività +menubar.setting.titleShowChannelState = Stato del canale +menubar.setting.titleShowViewerCount = Numero [spettatori] e [chatters] +menubar.setting.simpleTitle = Titolo semplice +menubar.setting.showJoinsParts = Mostra Entrate/Uscite +menubar.setting.showModMessages = Mostra mod/unmode +menubar.setting.attachedWindows = Finestre di dialogo allegate +menubar.setting.mainResizable = Finestra ridimensionabile +menubar.dialog.channelInfo = Informazioni canale +menubar.dialog.channelAdmin = Amministratore del canale +menubar.dialog.highlightedMessages = Messaggi in evidenza (Highlights) +menubar.dialog.ignoredMessages = Messaggi ignorati +menubar.dialog.search = Trova testo.. + +!-- Channels Menu --! +menubar.menu.channels = Canali +menubar.dialog.favorites = Preferiti +menubar.dialog.streams = Cronologia +menubar.dialog.addressbook = Rubrica (Addressbook) +menubar.dialog.joinChannel = Entra in un canale +menubar.rooms.none = Nessuna stanza trovata +menubar.rooms.reload = Ricarica stanze + +!-- Extra Menu --! +menubar.menu.extra = Extra +menubar.dialog.toggleEmotes = Emoticons +menubar.dialog.followers = Followers (Seguaci) +menubar.dialog.subscribers = Subscribers (Iscritti) +menubar.dialog.moderationLog = Registro di moderazione (Moderaion Log) +menubar.dialog.chatRules = Regole della Chat +# This is in context of the "StreamChat" submenu +menubar.dialog.streamchat = Apri la finestra di dialogo +menubar.stream.addhighlight = Aggiungi Stream in evidenza + +!-- Help Menu --! +menubar.menu.help = Aiuto +menubar.action.openWebsite = Sito web +menubar.dialog.updates = Cerca aggiornamenti +menubar.about = Informazioni / Aiuto + +!========================! +!== Main Context Menus ==! +!========================! + +!-- Channel/Streams Context Menu Entries --! +channelCm.menu.misc = Altro +channelCm.hostChannel = Canale ospite (Host Channel) +channelCm.joinHosted = Entra nel canale ospitato +channelCm.copyStreamname = Copia nome dello Stream +channelCm.dialog.chatRules = Mostra le Regole di Chat +channelCm.follow = Segui il canale +channelCm.unfollow = Non seguire più il canale +channelCm.speedruncom = Apri Speedrun.com +channelCm.closeChannel = Chiudi il canale +# Uses plural form and shows number when joining more than one channel +channelCm.join = Entra {0,choice,1\#canale|1<{0} canali} + +!-- User Context Menu Entries --! +# {0} = User Display Name +userCm.user = Utente\: {0} +# {0} = Username +userCm.join = Entra \#{0} +userCm.menu.misc = Varie +userCm.copyName = Copia nome +userCm.copyDisplayName = Copia nome visualizzato +userCm.follow = Segui +userCm.unfollow = Non seguire più +userCm.ignoreChat = Ignora (chat) +userCm.ignoreWhisper = Ignora (chat) +userCm.setColor = Imposta colori +userCm.setName = Imposta nome + +!-- Emote Context Menu Entries --! +# {0} = Creator +emoteCm.emoteBy = Emote da\: {0} +emoteCm.subEmote = Emoticon degli Iscritti +emoteCm.showDetails = Mostra dettagli +# Add emote to ignored emotes +emoteCm.ignore = Ignora +# Add emote to favorites +emoteCm.favorite = Preferiti +# Remove emote from favorites +emoteCm.unfavorite = Rimuovi dai Preferiti +emoteCm.showEmotes = Mostra Emotes + +!==================! +!== Live Streams ==! +!==================! +# {0} = Number of Live Streams, {1} = Current sorting +streams.title = Live Streams ({0}) [Riordina per\: {1} +streams.sorting.recent = Modificati (più recenti) +streams.sorting.recent.tip = Lo stream è stato avviato di recente o il Titolo/Gioco è cambiato +streams.sorting.uptime = Uptime (più recente) +streams.sorting.uptime.tip = Stream avviati più di recente +streams.sorting.name = Nome +streams.sorting.name.tip = Nome del Canale (alfabeticamente) +streams.sorting.game = Gioco +streams.sorting.game.tip = Nome del Gioco (alfabeticamente) +streams.sorting.viewers = Spettatori +streams.sorting.viewers.tip = Quantità massima di spettatori +streams.cm.menu.sortBy = Ordina per.. +streams.cm.removedStreams = Rimuovi Streams.. +streams.cm.refresh = Aggiorna +streams.removed.title = Offline/Left Streams +streams.removed.button.back = Ritorna ai Live Streams + +!===================! +!== Chat Messages ==! +!===================! +chat.connecting = Connessione in corso al server {0} +chat.secured = assicurato - secured +chat.joining = Stò entrando in {0} +chat.joined = Sei entrato in {0} +chat.joinError.notConnected = Non posso entrare in ''{0}'' (non sei connesso) +chat.joinError.alreadyJoined = Non posso in entrare in ''{0}'' (sei già dentro) +chat.joinError.invalid = Non posso entrare in' '{0}'' (il nome del canale non è valido) +chat.disconnected = Disconnesso +chat.error.unknownHost = Host sconosciuto +chat.error.connectionTimeout = Tempo di connessione scaduto +chat.error.loginFailed = Impossibile completare il login +# {0} = Channel name +chat.error.joinFailed = Accesso fallito per {0}\: Per favore controlla che il nome del canale sia corretto. Inoltre, se hai lo stesso problema in tutti i canali (forse di recente hai cambiato il tuo nome utente), prova a seguire questa procedura e riavvia Chatty. +chat.topic = Tema +# {0} = Name of the stream +chat.rooms.none = Nessuna stanza disponibile per {0} +# {0} = Number of rooms, {1} = Name of the stream they belong to +chat.rooms.available = {0} {0,choice,1\#stanza|1 + +!==================! +!== Login Dialog ==! +!==================! +login.title = Configurazione del Login.. +login.accountName = Nome Account\: +login.access = Accesso\: +login.button.removeLogin = Rimuovi Login +login.button.verifyLogin = Verifica Login +login.verifyingLogin = Verifica Login in corso.. +login.createLogin = +login.button.requestLogin = Richiesta dati di Login +login.access.chat = Accesso alle chat +login.access.chat.tip = Usato per connettersi alla chat e leggere/inviare messaggi +login.access.user = Leggere informazioni utenti +login.access.user.tip = Usato per richiedere streaming live che segui +login.access.editor = Accesso all'editor +login.access.editor.tip = Per modificare lo stato del tuo stream nella finestra di dialogo Amministratore +login.access.broadcast = Modifica trasmissione (broadcast) +login.access.broadcast.tip = Utilizzato per creare indicatori dello stream/impostare i tag dello stream +# Commercials as in Advertisements +login.access.commercials = Avvia spot pubblicitari +login.access.commercials.tip = Per avviare annunci pubblicitari sul tuo stream +login.access.subscribers = Mostra Iscritti/Abbonati +login.access.subscribers.tip = Utilizzato per mostrare l'elenco dei tuoi iscritti +login.access.follow = Segui i canali +login.access.follow.tip = Usato per seguire i canali attraverso il menù contestuale del canale o il comando /follow +login.removeLogin.title = Rimuovi login +login.removeLogin = Ciò rimuove il token di accesso da Chatty. +login.removeLogin.revoke = Revoca del Token\: rimuove tutti gli accessi per questo token (di solito consigliato) +login.removeLogin.remove = Rimuovi token\: rimuove il Token da Chatty, ma rimane valido +login.removeLogin.note = Nota\: per rimuovere l'accesso per tutti i token associati a Chatty, vai a twitch.tv/settings/connections e disconnetti l'app. +login.removeLogin.button.revoke = Revoca il Token +login.removeLogin.button.remove = Rimuovi Token + +!=================! +!== Join Dialog ==! +!=================! +join.title = Entra nei canali +# Uses plural form when joining more than one channel +join.button.join = Entra {0,choice,1\#canale|1F per Aggiungere o Rimuovere dai Preferiti, Del per Eliminare, o click destro per aprire il menù contestuale. + +!-- Select Game --! +admin.game.title = Seleziona Gioco +# HTML +admin.game.info =

    Digita parte del nome del gioco e premi Invio o clicca 'Cerca', quindi seleziona il gioco dai risultati ottenuti (usando i nomi di Twitch).

    +admin.game.button.search = Cerca +admin.game.button.clearSearch = Vuota la ricerca +admin.game.button.favorite = Aggiungi ai Preferiti +admin.game.button.unfavorite = Rimuovi dai Preferiti +admin.game.searching = Ricerca in corso.. +# {0} = Number of search results, {1} = Number of favorites +admin.game.listInfo = Trovati\: {0} / Preferiti\: {1} + +!-- Select Tags --! +admin.tags.title = Scegli fino {0} tags +admin.tags.listInfo = Tags\: {0}/{1} - Preferiti\: {2}/{3} +admin.tags.info = Fai doppio click sui Tag in lista per aggiungerli o rimuoverli. Disattiva "Mostra solo Attivi/Preferiti" per vedere tutti i Tag. Inserisci del testo per filtrare l'elenco corrente. +admin.tags.button.showAll = Mostra solo Attivi/Preferiti +admin.tags.button.clearFilter = Azzera filtro +# Favorite as an action, to add to favorites +admin.tags.button.favorite = Preferiti +# Unfavorite as an action, to remove from favorites +admin.tags.button.unfavorite = Rimuovi Preferiti +admin.tags.button.addSelected = Aggiungi selezionati +admin.tags.button.remove.tip = Rimuovi ''{0}'' +admin.tags.cm.replaceAll = Sostituisci tutto +admin.tags.cm.replace = Sostituisci ''{0}'' + +!=========================! +!== Channel Info Dialog ==! +!=========================! +channelInfo.title = Canale\: {0} +channelInfo.title.followed = seguito +channelInfo.status = Stato +channelInfo.history = Stato (Cronologia) +channelInfo.playing = Giocando a\: +channelInfo.viewers = Spettatori +channelInfo.streamOffline = Stream offline +channelInfo.noInfo = [Nessuna Informazione dello Stream] +channelInfo.cm.copyAllCommunities = Copia tutto +channelInfo.offline = Offline +channelInfo.offline.tip = Durata dell''ultima trasmissione (probabilmente approssimativa)\: {0} +# "With PICNIC" refers to the uptime that disregards small stream downtimes (so it's longer) +channelInfo.offline.tip.picnic = Durata dell''ultima trasmissione (probabilmente approssimativa)\: {0} (con PICNIC\: {1}) +channelInfo.uptime = Live\: {0} +channelInfo.uptime.tip = Stream avviato\: {0} +channelInfo.uptime.picnic = Live\: {0} ({1}) +# "With PICNIC" refers to the uptime that disregards small stream downtimes (so it's longer) +channelInfo.uptime.tip.picnic = Stream avviato\: {0} (con PICNIC\: {1}) +channelInfo.viewers.latest = ultimo\: {0} +channelInfo.viewers.now = ora\: {0} +channelInfo.viewers.hover = Spettatori\: {0} +channelInfo.viewers.min = minimo\: {0} +channelInfo.viewers.max = massimo {0}; +channelInfo.viewers.noHistory = Nessuna cronologia degli spettatori ancora +channelInfo.viewers.cm.timeRange = Tempo di Iintervallo +channelInfo.viewers.cm.timeRange.option = {0} {0,choice,1\#Ora|1Nota\: La dimensione generale del carattere di Chatty può essere regolata dalla pagina delle impostazioni 'Aspetto'. + +!-- Choose font --! +settings.chooseFont.title = Scegli carattere +settings.chooseFont.selectFont = Seleziona la famiglia e la dimensione del carattere +settings.chooseFont.preview = Anteprima +settings.chooseFont.enterText = Inserisci del testo addizionale per fare un test + +!-- Startup --! +settings.section.startup = All'avvio di Chatty +settings.boolean.splash = Mostra schermata iniziale +settings.boolean.splash.tip = Quando attivata viene mostrata una piccola finestra per indicare che Chatty si stà avviando +settings.startup.onStart = All'avvio\: +settings.startup.channels = Canali\: +settings.startup.option.doNothing = Non fare niente +settings.startup.option.openConnect = Apri finestra di connessione +settings.startup.option.connectJoinSpecified = Connettiti ed entra nei canali specificati +settings.startup.option.connectJoinPrevious = Connetti ed entra nei canali aperti precedentemente +settings.startup.option.connectJoinFavorites = Connetti ed entra nei canali Preferiti + +!-- Look & Feel --! +settings.section.lookandfeel = Look & Feel +settings.laf.lookandfeel = Look & Feel +settings.laf.font = Carattere\: +settings.laf.option.defaultFont = Prepdefinito +settings.laf.option.smallFont = Piccolo +settings.laf.option.largeFont = Grande +settings.laf.option.giantFont = Gigante +settings.laf.restartRequired = Si rende necessario il riavvio di Chatty per rendere effettive queste modifiche) +# HTML +settings.laf.info = L'aspetto del "Sistema" dipende dal tuo sistema operativo. Rispetto a "Predefinito" o "Sistema" le altre voci consentono di regolare ulteriormente la dimensione del carattere globale (ma potrebbero bloccare alcune funzionalità come lo snap di Windows). +settings.laf.info2 = Alcuni colori e tipi di carattere specifici possono essere impostati in altre pagine delle Impostazioni. + +!-- Language --! +settings.section.language = Linguaggio +settings.language.language = Lingua\: +settings.language.option.defaultLanguage = Predefinito +settings.language.info = AVVISO\: Mantenere aggiornato Chatty in ogni lingua è veramente un duro lavoro. Per questo motivo la guida ed altre parti del programma non sono state tradotte. + +!-- Chat Colors --! +settings.section.colors = Personalizzazioni colori delle chat +settings.colors.general.backgroundColor = Colore di sfondo +settings.colors.general.foregroundColor = Colore in primo piano +settings.colors.heading.misc = Colori vari +settings.colors.heading.highlights = Messaggi in evidenza (Highlighted) +settings.colors.heading.searchResult = Risultati di ricerca +settings.colors.background = Sfondo +settings.boolean.alternateBackground = Usa sfondo alternato +settings.colors.background2 = Sfondo 2 +settings.colors.button.switchBackgrounds = Inverti con lo sfondo +settings.colors.button.switchBackgrounds.tip = Inverti i colori di sfondo con sfondo 2, per testare la leggibilità dei colori in primo piano. +settings.boolean.messageSeparator = Mostra linee di separazione messaggi +settings.colors.foreground = Messaggi della chat +settings.colors.info = Messaggi di Informazione +settings.colors.compact = Compatto (es. MOD/UNMOD) +settings.colors.highlight = Testo (Highlighted) +settings.boolean.highlightBackground = Usa lo sfondo del messaggio evidenziato +settings.colors.highlightBackground = Sfondo (Messaggio evidenziato) +settings.colors.inputBackground = Sfondo di Input +settings.colors.inputForeground = Testo di Input +settings.colors.searchResult = Risultati trovati +settings.colors.searchResult2 = Risultati trovati evidenziati +settings.colors.lookandfeel = Note\: L'estetica generale del programma è influenzata anche dalle scelte effettuate nelle impostazioni relative all'Aspetto +settings.colors.button.choose = Scegli +settings.colorChooser.title = Cambia colore\: {0} +settings.colorChooser.button.useSelected = Usa il colore selezionato +settings.colorPresets.colorPresets = Colori preimpostati +settings.colorPresets.option.default = Predefinito +settings.colorPresets.option.dark = Dark +settings.colorPresets.option.dark2 = Dark 2 +settings.colorPresets.button.save = Salva +settings.colorPresets.button.saveAs = Salva come.. +settings.colorPresets.button.delete = Elimina +settings.colorPresets.info = (Le preimpostazioni vengono salvate direttamente nelle impostazioni) + +!-- Message Colors --! +settings.section.msgColors = Colore dei messaggi personalizzati () +settings.boolean.msgColorsEnabled = Abilita i colori dei messaggi personalizzati +settings.boolean.msgColorsEnabled.tip = Se abilitato, le voci nella tabella sottostante possono influire sui colori dei messaggi di chat +settings.section.msgColorsOther = Altre impostazioni +settings.boolean.actionColored = Colore dei messaggi in azione (/me) con coloreutente + +!-- Usercolors --! +settings.section.usercolorsOther = Altre impostazioni +settings.string.nickColorCorrection = Correzione della leggibilità dei colori utente\: +settings.string.nickColorCorrection.option.off = Disattivato +settings.string.nickColorCorrection.option.normal = Normale +settings.string.nickColorCorrection.option.strong = Forte +settings.string.nickColorCorrection.option.old = Vecchio +settings.string.nickColorCorrection.option.gray = Scala di grigi + +!-- Usericons --! +settings.section.usericons = Impostazioni icona utente +settings.boolean.usericonsEnabled = Mostra le icone dell'utente +settings.boolean.botBadgeEnabled = Mostra il badge del Bot + +!-- Emoticons --! +settings.section.emoticons = Impostazioni generali delle Emoticon +settings.boolean.emoticonsEnabled = Mostra emoticons +settings.emoticons.button.editIgnored = Modifica Emotes ignorate +settings.emoticons.chatScale = Scala (Chat)\: +settings.emoticons.dialogScale = Finestra Emotes\: +settings.emoticons.maxHeight = Altezza massima\: +settings.emoticons.maxHeightPixels = pixels +settings.boolean.closeEmoteDialogOnDoubleClick = Doppio click sulla emote chiude la finestra delle Emote +settings.emoticons.cheers = Saluti (Bits)\: +settings.emoticons.cheers.option.text = Solo testo +settings.emoticons.cheers.option.static = Immagini statiche +settings.emoticons.cheers.option.animated = Animate + +!-- Third-Party Emoticons --! +settings.section.3rdPartyEmotes = Emoticons di terze parti +settings.boolean.bttvEmotes = Abilita le migliori TTV Emotes +settings.boolean.showAnimatedEmotes = Mostra le emotes animate +settings.boolean.showAnimatedEmotes.tip = Attualmente solo BTTV ha GIF emotes +settings.boolean.ffz = Abilita le FrankerFaceZ (FFZ) +settings.boolean.ffzModIcon = Abilita le icone FFZ Mod +settings.boolean.ffzModIcon.tip = Mostra l'icona mod in alcuni canale +settings.boolean.ffzEvent = Abilita le FFZ emotes in primo piano +settings.boolean.ffzEvent.tip = Le emotes in primo piano sono disponibili tra gli eventi di alcuni canali (come le maratone di speedrunning) + +!-- Emoji --! +settings.section.emoji = Emoji +settings.emoji.set = Impostazioni\: +settings.emoji.option.none = Nessuna +settings.boolean.emojiReplace = Trasforma il codice in emoji nel testo inserito +settings.boolean.emojiReplace.tip = Codici come \:joy\: inseriti nella casella di input vengono trasformati nelle Emoji corrispondenti (Suggerimento\: utilizzare il completameno del TAB) + +!-- Ignored Emotes --! +settings.ignoredEmotes.title = Emotes ignorate +settings.ignoredEmotes.info1 = Le emotes ignorate vengono visualizzate solo col codice dell'emote, non vengono trasformate in un'immagine. +settings.ignoredEmotes.info2 = Puoi usare il menù contestuale (click destro su una emote in chat) per aggiungerla a questa lista. + +!-- Messages --! +settings.section.deletedMessages = Elimina messaggi (Timeouts/Bans) +settings.option.deletedMessagesMode.delete = Messaggio eliminato +settings.option.deletedMessagesMode.strikeThrough = Colpire +settings.option.deletedMessagesMode.strikeThroughShorten = Colpire, accorciare +settings.deletedMessages.max = massimo. +settings.deletedMessages.characters = caratteri +settings.boolean.banDurationAppended = Mostra la durata del ban +settings.boolean.banDurationAppended.tip = Mostra la durata in secondi per i timeout dietro l'ultimo messaggio eliminato +settings.boolean.banReasonAppended = Mostra il motivo del ban (solo moderatori) +settings.boolean.banReasonAppended.tip = Mostra il motivo di un ban dietro l'ultimo messaggio eliminato (solo moderatori, ad eccezione dei tuoi ban) +settings.boolean.showBanMessages = Mostra i messaggi dei ban separati, con le seguenti opzioni\: +settings.boolean.banDurationMessage = Mostra la durata del ban +settings.boolean.banDurationMessags.tip = Mostra la durata in secondi per i timeout in messaggi di ban separati +settings.boolean.banReasonMessage = Mostra il motivo del ban (solo moderatori) +settings.boolean.banReasonMessage.tip = Mostra il motivo di un ban in messaggi di ban separati (solo moderatori, ad eccezione dei tuoi ban) +settings.boolean.combineBanMessages = Combina i messaggi di ban +settings.boolean.combineBanMessages.tip = Combina i messaggi di ban simili in uno, aggiungendo il numero di ban +settings.boolean.clearChatOnChannelCleared = Cancella chat quando cancellata da un moderatore +settings.section.otherMessageSettings = Altro +settings.otherMessageSettings.timestamp = Data e ora\: +settings.boolean.showModMessages = Mostra mod/unmod (inaffidabile) +settings.boolean.showModMessages.tip = Mostra MOD/UNMOD quando viene promosso o rimosso un moderatore, oppure un moderatore si è unito o ha lasciato il canale. +settings.boolean.showJoinsParts = Mostra chi ENTRA e chi ESCE (inaffidabile) +settings.boolean.printStreamStatus = Mostra lo stato dello Strem (es. Titolo/Gioco) in chat +settings.boolean.printStreamStatus.tip = Mostra quando entra e quando cambia + +!-- Moderation --! +settings.section.modInfos = Informazioni solo sul moderatore +settings.boolean.showModActions = Mostra le azioni di moderazione nella chat (simile a ) +settings.boolean.showModActions.tip = Mostra quali comandi eseguono i moderatori, eccetto i tuoi (puoi anche aprire il menù principale Extra - Registro di moderazione). +settings.boolean.showModActionsRestrict = Nascondi se l'azione associata è già visibile nella chat (esempio\: ban) +settings.boolean.showModActionsRestrict.tip = Se questa azione di moderazione viene mostrata aggiungendo @Mod a un messaggio informativo esistente in chat, non mostrerà un messaggio Mod Action separato. +settings.boolean.showActionBy = Aggiungi quale @Mod ha causato un'azione (esempio\: Ban) +settings.boolean.showActionBy.tip = Se diversi moderatori eseguono la stessa azione in un breve lasso di tempo, questa visualizzazione potrebbe essere inaccurata. +settings.section.userDialog = Finestra di dialogo utente +settings.boolean.closeUserDialogOnAction = Chiudi la Finestra di dialogo utente quando esegui un'azione (Pulsante) +settings.boolean.closeUserDialogOnAction.tip = Dopo aver cliccato un pulsante, la Finestra di dialogo informazioni utente verrà chiusa automaticamente. +settings.boolean.openUserDialogByMouse = Apri la Finestra di dialogo utente sempre vicino al puntatore del mouse +settings.boolean.reuseUserDialog = Riutilizzare la dialog utente già aperta dello stesso utente +settings.boolean.reuseUserDialog.tip = Quando si apre la finestra di dialogo utente su un utente che ne ha già una aperta, non aprire/cambiare un altra +settings.long.clearUserMessages.option.-1 = Mai +settings.long.clearUserMessages.option.3 = 3 ore +settings.long.clearUserMessages.option.6 = 6 ore +settings.long.clearUserMessages.option.12 = 12 ore +settings.long.clearUserMessages.option.24 = 24 ore +settings.long.clearUserMessages.label = Cancella la cronologia dei messaggi degli utenti inattivi per\: + +!-- Names --! +settings.label.mentions = Menzioni cliccabili +settings.label.mentions.tip = I nomi utente degli utenti che hanno chattato di recente sono resi cliccabili e enfatizzati nei messaggi di chat +settings.label.mentionsBold = Grassetto +settings.label.mentionsUnderline = Sottolineato +settings.label.mentionsColored = Colorato +settings.label.mentionsColored.tip = Menzioni cliccabili nel colore dell'utente +settings.long.markHoveredUser.option.0 = Off +settings.long.markHoveredUser.option.1 = Tutto / Sempre +settings.long.markHoveredUser.option.2 = Solo menzioni +settings.long.markHoveredUser.option.3 = Solo menzioni, tutto quando si tiene premuto CTRL +settings.long.markHoveredUser.option.4 = Solo tenendo premuto CTRL +settings.label.markHoveredUser = Contrassegna il nome utente con il mouse\: +settings.label.markHoveredUser.tip = Enfatizza altre occorrenze di un nome utente che tieni nascosto nella chat + +!-- Highlight --! +settings.section.highlightMessages = Messaggi in evidenza (Highlight) +settings.boolean.highlightEnabled = Abilita gli highlight +settings.boolean.highlightEnabled.tip = I messaggi che corrispondono ai criteri verranno visualizzati in un colore diverso, aggiunti alla finestra Messaggi evidenziati e attivati una notifica (per impostazione predefinita). +settings.boolean.highlightUsername = Evidenzia il mio nome +settings.boolean.highlightUsername.tip = Metti in evidenza i messaggi contenenti il mio nome utente corrente, anche se non l'hai aggiunti all'elenco. +settings.boolean.highlightNextMessages = Evidenzia il seguito +settings.boolean.highlightNextMessages.tip = Mette in evidenza i messaggi dello stesso utente che vengono scritti poco dopo l'ultimo evidenziato. +settings.boolean.highlightOwnText = Controlla il tuo testo per i punti in evidenza +settings.boolean.highlightOwnText.tip = Consente di evidenziare i propri messaggi, altrimenti i propri messaggi non saranno mai evidenziati. Buono per i test. +settings.boolean.highlightIgnored = Controlla i messaggi ignorati +settings.boolean.highlightIgnored.tip = Consente di evidenziare i messaggi ignorati, altrimenti i messaggi ignorati non vengono mai evidenziati. +settings.boolean.highlightMatches = Segna evidenzia/ignora le partite/corrispondenze +settings.boolean.highlightMatches.tip = Circonda le sezioni di testo che hanno causato l'evidenziazione con un rettangolo (e nelle finestre dei messaggi evidenziati/ignorati). +settings.boolean.highlightMatchesAll = Segna tutte le occorrenze +settings.boolean.highlightMatchesAll.tip = Contrassegna anche le corrispondenze in evidenza nei collegamenti e menzioni + +!-- Ignore --! +settings.section.ignoreMessages = Ignora messaggi +settings.boolean.ignoreEnabled = Abilita ignora + +!-- Filter --! +settings.section.filterMessages = Filtra le parti dei messaggi +settings.boolean.filterEnabled = Abilita filtro + +!-- Log to file --! +settings.log.section.channels = Canali da loggare al file +settings.log.loggingMode = Modalità del Logging\: +settings.option.logMode.always = Sempre +settings.option.logMode.blacklist = Lista nera +settings.option.logMode.whitelist = Lista bianca +settings.option.logMode.off = Mai +settings.log.noList = +settings.log.alwaysInfo = Tutti i canali sono loggati +settings.log.blacklistInfo = Tutti i canali, ma quelli nella lista sono loggati. +settings.log.whitelistInfo = Solo i canali nella lista sono loggati. +settings.log.offInfo = Niente è loggato. +settings.boolean.logMessage = Messaggi di chat +settings.boolean.logMessage.tip = Logga i messaggi di chat regolari +settings.boolean.logInfo = Informazioni Chat +settings.boolean.logInfo.tip = Informazioni sui Log come titolo dello stream, messaggi da contrazione, connessione, disconnessione +settings.boolean.logBan = Bans/Tempo scaduto +settings.boolean.logBan.tip = Logga Bans/Timeouts come messaggi di BAN +settings.boolean.logDeleted = Elimina messaggio +settings.boolean.logDeleted.tip = Logga messaggi eliminati come messaggi ELIMINATI +settings.boolean.logMod = Mod/Unmod +# MOD/UNMOD should not be translated since it refers to how it appears in the log +settings.boolean.logMod.tip = Logga messaggi di MOD/UNMOD +settings.boolean.logJoinPart = Entrate/Uscite +# JOIN/PART should not be translated since it refers to how it appears in the log +settings.boolean.logJoinPart.tip = Logga ENTRATE/USCITE +settings.boolean.logSystem = Informazioni sistema +settings.boolean.logSystem.tip = Logga i messaggi del sistema Chatty come il controllo della versione, la risposta ai comandi di impostazione e così via.. +settings.boolean.logViewerstats = Statistiche +settings.boolean.logViewerstats.tip = Logga le statistiche del visualizzatore (una sorta di riepilogo della vista rilevata) in un intervallo semi-regolare +settings.boolean.logViewercount = Visualizzatore +settings.boolean.logViewercount.tip = Logga il visualizzatore ogni volta che riceve nuovi dati +settings.boolean.logModAction = Azioni di moderazione +settings.boolean.logModAction.tip = Logga chi e quale comando ha eseguito (solo moderatori) +settings.boolean.logIgnored = Messaggi ignorati +settings.boolean.logIgnored.tip = Logga i messaggi che vengono ignorati anche dall'elenco Ignora +settings.log.section.other = Altre impostazioni +settings.log.folder = Cartella\: +settings.log.splitLogs = Dividi Logs per\: +settings.option.logSplit.never = Mai +settings.option.logSplit.daily = Giornalmente +settings.option.logSplit.weekly = Settimanalmente +settings.option.logSplit.monthly = Mesilmente +settings.boolean.logSubdirectories = Sotto directory del canale +settings.boolean.logSubdirectories.tip = Organizza Logs nelle sotto directory dei canali +settings.boolean.logLockFiles = Bloccare i files +settings.boolean.logLockFiles.tip = Ottiene l'accesso esclusivo ai file di registro per garantire che nessun altro programma scriva su di esso. Può anche a volte impedire la lettura. +settings.log.timestamp = Data e ora\: +settings.option.logTimestamp.off = Disattivato + +!-- TAB Completion --! +settings.section.completion = Completamento del TAB (Nomi, Emotes, Comandi) +settings.completion.option.names = Nomi +settings.completion.option.emotes = Emotes +settings.completion.option.namesEmotes = Nomi, poi Emotes +settings.completion.option.emotesNames = Emotes, poi Nomi +settings.completion.option.custom = Completamento personalizzato +settings.completion.info = Suggerimento\: Indipendentemente da queste impostazioni, puoi aggiungere un prefisso con @ sempre per il TAB-complete ai nomi, con . (punto) per usare il completamento personalizzato e con \: per completare le Emoji.

    Esempio\: \:think + TAB > \:thinking\: +settings.string.completionSearch = Cerca in Emotes / Comandi\: +# Example: Emote "joshWithIt" would match only when entering the beginning of "joshWithIt" +settings.string.completionSearch.option.start = All'inizio del nome +# Example: Emote "joshWithIt" would match when entering the beginning of "joshWithIt", "With" or "It" +settings.string.completionSearch.option.words = All'inizio, e parole in maiuscolo +# Example: Emote "joshWithIt" would match when entering any part of the name (even just "t") +settings.string.completionSearch.option.anywhere = Ovunque nel nome +settings.section.completionNames = Nomi localizzati +settings.boolean.completionPreferUsernames = Preferisci il nome normale per i comandi basati su nomi utente +settings.boolean.completionAllNameTypes = Includi tutti i tipi di nome nei risultati (Regolari/Localizzati/Personalizzati) +settings.boolean.completionAllNameTypesRestriction = Solo quando non più di due partite +settings.section.completionAppearance = Aspetto / Comportamento +settings.boolean.completionShowPopup = Mostra informazioni popup +settings.boolean.completionCommonPrefix = Completa al prefisso comune (se più di una corrispondenza) +settings.completion.nameSorting = Ordine dei nomi\: +settings.completion.option.predictive = Predittiva +settings.completion.option.alphabetical = Alfabetica +settings.completion.option.userlist = Uguale alla lista utenti + +!-- Dialogs Settings --! +settings.section.dialogs = Posizione / Dimensione della finestra di dialogo +settings.dialogs.restore = Ripristina finestre dialogo\: +settings.dialogs.option.openDefault = Apri sempre nella posizione predefinita +settings.dialogs.option.keepSession = Mantenere la posizione durante la sessione +settings.dialogs.option.restore = Mantieni la posizione / dimensione dell'ultima sessione +settings.dialogs.option.reopen = Riapri dall'ultima sessione +settings.boolean.restoreOnlyIfOnScreen = Ripristina la posizione solo se sullo schermo +settings.boolean.attachedWindows = Sposta anche le finestre di dialog quando sposti la finestra principale + +!-- Minimizing Settings --! +settings.section.minimizing = Minimizza / Tray (orologio) +settings.boolean.minimizeToTray = Minimiza nella tray (vicino all'orologio) +settings.boolean.closeToTray = Vicino alla tray +settings.boolean.trayIconAlways = Mostra sempre l'icona nella Tray (orologio) +settings.boolean.hideStreamsOnMinimize = Nascondi finestra Live Stream quando minimizzato +settings.boolean.hideStreamsOnMinimize.tip = Nasconde automaticamente la finestra di Live Stream quando viene minimizzata la finestra principale + +!-- Other Window Settings --! +settings.section.otherWindow = Altro +# Enable the confirmation dialog when opening an URL out of Chatty +settings.boolean.urlPrompt = Richiesta di "Aprire URL" +settings.boolean.chatScrollbarAlways = Mostra sempre la scrollbar nelle chat +settings.window.defaultUserlistWidth = Larghezza nick list predefinita +# In context of Userlist Width, so don't need to repeat "Userlist" +settings.window.minUserlistWidth = Larghezza minima\: +settings.boolean.userlistEnabled = Abilita nick list predefinita +settings.boolean.userlistEnabled.tip = Come predefinito, puoi usare Shift+F10 per nascondere/visualizzare la nick list +settings.boolean.inputEnabled = Abilita il campo di input predefinito +settings.boolean.inputEnabled.tip = Come predefinito, puoi usare Ctrl+F10 per nascondere/visualizzare il campo di inserimento + +!-- Tab Settings --! +settings.section.tabs = Impostazioni Tabulazione +settings.tabs.order = Ordine della Tabulazione\: +settings.tabs.option.normal = Normale (come aperto) +settings.tabs.option.alphabetical = Alfabetico +settings.tabs.placement = Posizionamento delle schede +settings.tabs.option.top = Alto +settings.tabs.option.left = Sinistra +settings.tabs.option.right = Destra +settings.tabs.option.bottom = Basso +settings.tabs.layout = Disposizione TAB\: +settings.tabs.option.wrap = Avvolgere (più righe) +settings.tabs.option.scroll = Scroll (riga singola) +settings.boolean.tabsMwheelScrolling = Scorri le schede con la rotellina del mouse per cambiare canale +settings.boolean.tabsMwheelScrollingAnywhere = Scorri la casella di input per cambiare canale + +!-- Stream Settings --! +settings.section.streamHighlights = Stream in evidenza +settings.section.streamHighlightsCommand = Comandi della chat +settings.streamHighlights.info = I momenti salienti dello Stream (scrittura di uptime/note su file) possono essere aggiunti tramite il comando /addStreamHighlight, hotkey (impostazioni Hotkeys) o comando chat (che puoi configurare qui). Vedere l'aiuto per ulteriori informazioni. +settings.streamHighlights.matchInfo = Si consiglia vivamente di limitare i comandi della chat ad utenti fidati, come i Moderatori. +settings.streamHighlights.channel = Comando del canale\: +settings.streamHighlights.command = Nome del comando\: +settings.streamHighlights.match = Accesso al comando\: +settings.boolean.streamHighlightChannelRespond = Rispondi al comando con un messaggio di chat +settings.boolean.streamHighlightChannelRespond.tip = Invia un messaggio alla chat, in modo che i moderatori possano vedere che il comando ha avuto successo +settings.boolean.streamHighlightMarker = Crea un indicatore dello Stream quando aggiungi l'evidenziazione dello Stream +settings.boolean.streamHighlightMarker.tip = Crea un indicatore dello Stream oltre a scrivere l'evidenziazione dello Stream sul file + +!===================! +!== Emotes Dialog ==! +!===================! +# {0} = Name of the channel (or "-" if no channel) +emotesDialog.title = Emoticons (Globali/Abbonati/{0}) +emotesDialog.tab.favorites = Preferiti +emotesDialog.tab.myEmotes = Mie Emotes +emotesDialog.tab.channel = Canale +emotesDialog.tab.other = Altro +emotesDialog.noFavorites = Non hai aggiunto nessuna emotes preferita +emotesDialog.noFavorites.hint = (Click destro sulla emote e scegli 'Preferiti') +emotesDialog.subscriptionRequired = Devi essere Abbonato per usare queste emotes\: +emotesDialog.notFoundFavorites = Preferiti non ancora trovati (metadati non caricati)\: +emotesDialog.favoriteCmInfo = (Click destro per visualizzare le informazioni e rimuovere dai Preferiti, se necessario.) +emotesDialog.noSubemotes = Non sembri avere emote sub o turbo +emotesDialog.subEmotesJoinChannel = (Devi entrare in un canale di loro per essere riconosciuto.) +emotesDialog.otherSubemotes = Altro +emotesDialog.noChannel = Nessun canale. +emotesDialog.noChannelEmotes = Nessuna emotes trovata per \#{0} +emotesDialog.noChannelEmotes2 = Nessuna FFZ o BTTV emotes trovata. +emotesDialog.channelEmotes = Emotes specifiche per \#{0} +emotesDialog.subemotes = Emotes secondarie {0} +emotesDialog.subscriptionRequired2 = (È necessario essere abbonati per utilizzare questi.) +emotesDialog.globalTwitch = Emotes globali di Twitch +emotesDialog.globalFFZ = Emotes globali di FFZ +emotesDialog.globalBTTV = Emotes globali di BTTV +# {0} = Emote Code +emotesDialog.details.title = Dettagli Emotes\: {0} +emotesDialog.details.code = Codice\: +emotesDialog.details.type = Tipo\: +emotesDialog.details.id = Emote ID\: +emotesDialog.details.channel = Canale\: +# Details where the emote can be used (channel/globally) +emotesDialog.details.usableIn = Usabilità\: +emotesDialog.details.usableInChannel = Canale +emotesDialog.details.usableEverywhere = Ovunque +# Details who can use the emote (restricted/everyone) +emotesDialog.details.access = Accesso\: +emotesDialog.details.everyone = Chiunque +emotesDialog.details.restricted = Limitato +emotesDialog.details.accessAvailable = (Hai accesso) +emotesDialog.details.size = Dimensione regolare\: +emotesDialog.details.by = Emote da\: +emotesDialog.details.info = Click destro sulla emotes qui o in chat per aprire il menù contestuale con Info/Opzioni diff --git a/src/chatty/lang/Strings_ko.properties b/src/chatty/lang/Strings_ko.properties index a9c651e6b..04598a728 100644 --- a/src/chatty/lang/Strings_ko.properties +++ b/src/chatty/lang/Strings_ko.properties @@ -55,6 +55,7 @@ menubar.dialog.moderationLog = 모드 명령어 기록 menubar.dialog.chatRules = 채팅 규칙 # This is in context of the "StreamChat" submenu menubar.dialog.streamchat = 다이얼로그 열기 +menubar.stream.addhighlight = 생방송 하이라이트 추가 !-- Help Menu --! menubar.menu.help = 도움말 @@ -102,7 +103,7 @@ emoteCm.showDetails = 자세히 보기 # Add emote to ignored emotes emoteCm.ignore = 무시 # Add emote to favorites -emoteCm.favorite = 즐겨찾기 +emoteCm.favorite = 즐겨찾기 추가 # Remove emote from favorites emoteCm.unfavorite = 즐겨찾기 삭제 emoteCm.showEmotes = 이모티콘 보기 @@ -112,10 +113,16 @@ emoteCm.showEmotes = 이모티콘 보기 !==================! # {0} = Number of Live Streams, {1} = Current sorting streams.title = {0}개의 생방송 [정렬\: {1}] -streams.sorting.recent = 최근 -streams.sorting.name = 아이디 -streams.sorting.game = 게임 -streams.sorting.viewers = 시청자 +streams.sorting.recent = 갱신 순 (가장 최근) +streams.sorting.recent.tip = 제목/게임 변경 또는 최근 생방송 시작한 순서 +streams.sorting.uptime = 방송 시간 (최신 순) +streams.sorting.uptime.tip = 가장 최근 생방송 시작한 순서 +streams.sorting.name = 아이디 순 +streams.sorting.name.tip = 채널명 (알파벳순으로) +streams.sorting.game = 게임 순 +streams.sorting.game.tip = 게임명 (알파벳순으로) +streams.sorting.viewers = 시청자 순 +streams.sorting.viewers.tip = 시청자가 많은 순서 streams.cm.menu.sortBy = 정렬 기준 streams.cm.removedStreams = 종료된 방송 streams.cm.refresh = 새로고침 @@ -165,6 +172,11 @@ dialog.button.test = 테스트 # Open a help of sorts dialog.button.help = 도움말 +!====================! +!== General Status ==! +!====================! +status.loading = 로딩 중.. + !====================! !== Connect Dialog ==! !====================! @@ -197,7 +209,7 @@ login.access.user.tip = 팔로우 중인 생방송 채널을 요청하는데 사 login.access.editor = 접근 권한 수정 login.access.editor.tip = 채널 관리에서 방송 상태를 수정하려면 login.access.broadcast = 방송 수정 -login.access.broadcast.tip = 방송 책갈피를 생성하는데 사용 +login.access.broadcast.tip = 생방송 책갈피를 생성하거나 생방송 태그를 설정하는데 사용 # Commercials as in Advertisements login.access.commercials = 광고 실행 login.access.commercials.tip = 방송에 광고를 실행하려면 @@ -258,6 +270,8 @@ admin.button.presets = 프리셋 admin.button.fav = 즐찾 admin.button.selectGame = 게임 선택 admin.button.removeGame = 삭제 +admin.button.selectTags = 태그 선택 +admin.button.removeTags = 삭제 admin.button.update = 업데이트 # {0} = Duration since last update (e.g. 5m) admin.infoLoaded = 마지막 불러온 시간\: {0} 전에 @@ -269,11 +283,12 @@ admin.infoUpdated = 업데이트 완료 !-- Status History --! admin.presets.title = 상태 프리셋 (현재 게임\: {0}) -admin.presets.button.useStatus = 상태 불러오기 (제목/게임) +admin.presets.button.useStatus = 상태 불러오기 (제목/게임/태그) admin.presets.button.useTitleOnly = 제목만 불러오기 admin.presets.column.fav = 즐찾 admin.presets.column.title = 제목 admin.presets.column.game = 게임 +admin.presets.column.tags = 태그 admin.presets.column.lastActivity = 마지막 활동 admin.presets.column.usage = 사용 admin.presets.setting.currentGame = 현재 게임 @@ -283,6 +298,8 @@ admin.presets.cm.toggleFavorite = 즐겨찾기 전환 admin.presets.cm.remove = 삭제 admin.presets.cm.useStatus = 상태 불러오기 admin.presets.cm.useTitleOnly = 제목만 불러오기 +admin.presets.cm.useGameOnly = 게임만 불러오기 +admin.presets.cm.useTagsOnly = 태그만 불러오기 # HTML admin.presets.info = F키로 즐겨찾기에 추가나 삭제가 가능하며, Del키로 지울 수 있습니다. 또한 오른쪽 클릭으로 콘텍스트 메뉴를 열 수 있습니다. @@ -298,6 +315,21 @@ admin.game.searching = 검색 중.. # {0} = Number of search results, {1} = Number of favorites admin.game.listInfo = 검색\: {0} / 즐겨찾기\: {1} +!-- Select Tags --! +admin.tags.title = 최대 {0}개 태그 선택 +admin.tags.listInfo = 태그\: {0}/{1} - 즐겨찾기\: {2}/{3} +admin.tags.info = 태그를 더블 클릭하여 목록에 추가/삭제합니다. "사용했거나 즐겨찾기만 보기"를 체크 해제하여 모든 태그를 탐색합니다. 텍스트를 입력하면 현재 목록에 필터를 적용합니다. +admin.tags.button.showAll = 사용했거나 즐겨찾기만 보기 +admin.tags.button.clearFilter = 필터 지우기 +# Favorite as an action, to add to favorites +admin.tags.button.favorite = 즐겨찾기 추가 +# Unfavorite as an action, to remove from favorites +admin.tags.button.unfavorite = 즐겨찾기 삭제 +admin.tags.button.addSelected = 선택한 것 추가 +admin.tags.button.remove.tip = "{0}" 태그 제거 +admin.tags.cm.replaceAll = 모두 바꾸기 +admin.tags.cm.replace = "{0}" 태그와 바꾸기 + !=========================! !== Channel Info Dialog ==! !=========================! @@ -335,8 +367,9 @@ channelInfo.viewers.cm.verticalZoom = 수직 줌 !=================! userDialog.title = 유저\: userDialog.setting.pin = 다이얼로그 고정 +userDialog.setting.pin.tip = 직접 닫기 전까지 동일한 유저의 다이얼로그를 열린 채로 고정시키기 userDialog.editBanReasons = 강제 퇴장 사유 프리셋 수정(한줄당 하나) -userDialog.selectBanReason = 강제 퇴장 사유 고르기(선택적) +userDialog.selectBanReason = 강제 퇴장 사유 고르기(선택) userDialog.customReason = 프리셋에 없는 사유\: userDialog.loading = 로딩 중.. # {0} = Account age (example: "1.5 years") @@ -345,6 +378,14 @@ userDialog.registered = 등록 이후\: {0} 경과 userDialog.registered.tip = 계정 생성\: {0} # {0} = Number of followers userDialog.followers = 팔로워 수\: +# {0} = User ID +userDialog.id = 아이디\: {0} +# {0} = Follow age (example: "1.5 years") +userDialog.followed = 팔로우 기간\: {0} 동안 +# {0} = Follow age (example: "1 year 277 days"), {1} = Follow date/time +userDialog.followed.tip = 팔로우 기간\: {0} 동안 ({1}) +userDialog.notFollowing = 팔로우 중이 아님 +userDialog.error = 요청 오류 !=======================! !== Chat Rules Dialog ==! @@ -428,10 +469,6 @@ settings.chooseFolder.button.default = 기본값 settings.chooseFolder.button.open = 열기 !-- Chat Font --! -settings.section.chatFont = 채팅 글꼴 -settings.chatFont.button.selectFont = 글꼴 선택 -settings.chatFont.fontName = 글꼴명\: -settings.chatFont.fontSize = 글꼴 크기\: settings.chatFont.lineSpacing = 줄 간격\: settings.chatFont.option.smallest = 가장 좁게 settings.chatFont.option.smaller = 더 좁게 @@ -444,7 +481,6 @@ settings.chatFont.messageSpacing = 메시지 간격\: settings.chatFont.bottomMargin = 바닥 여백 !-- Input / Userlist Font --! -settings.section.otherFonts = 입력 상자/유저 목록 글꼴 settings.otherFonts.inputFont = 입력 상자\: settings.otherFonts.userlistFont = 유저 목록\: settings.otherFonts.info = 참고\: 프로그램의 일반 글꼴 크기는 테마를 설정하는 '외양' 설정에서 수정할 수 있습니다. @@ -457,7 +493,7 @@ settings.chooseFont.enterText = 추가로 텍스트를 입력하여 테스트하 !-- Startup --! settings.section.startup = 시작 -settings.boolean.splash = 시작 화면 보기 +settings.boolean.splash = 시작 로딩 화면 보기 settings.boolean.splash.tip = Chatty가 실행되는 것을 나타내는 작은 창을 보여줍니다 settings.startup.onStart = 시작 시\: settings.startup.channels = 채널\: @@ -561,7 +597,7 @@ settings.emoticons.cheers.option.animated = 움직이는 이미지 settings.section.3rdPartyEmotes = 서드파티 이모티콘 settings.boolean.bttvEmotes = BetterTTV 이모티콘 사용 settings.boolean.showAnimatedEmotes = 움직이는 이모티콘 보기 -settings.boolean.showAnimatedEmotes.tip = 현재 BTTV만 GIF 이모티콘을 지원합니다 +settings.boolean.showAnimatedEmotes.tip = 현재 BTTV만 GIF 이모티콘을 지원합니다. settings.boolean.ffz = FrankerFaceZ (FFZ) 사용 settings.boolean.ffzModIcon = FFZ 모드 아이콘 사용 settings.boolean.ffzModIcon.tip = 몇몇 채널에서 커스텀 모드 아이콘 보기 @@ -581,19 +617,19 @@ settings.ignoredEmotes.info1 = 무시한 이모티콘은 이미지로 변환하 settings.ignoredEmotes.info2 = 이 목록에 추가할 때 이모티콘 콘텍스트 메뉴(채팅에서 이모티콘 오른쪽 클릭)에서도 사용 가능합니다. !-- Messages --! -settings.section.deletedMessages = 삭제된 메시지 (임시 퇴장/강제 퇴장) +settings.section.deletedMessages = 삭제된 메시지 (임시 차단/강제 퇴장) settings.option.deletedMessagesMode.delete = 메시지 삭제 settings.option.deletedMessagesMode.strikeThrough = 취소선 긋기 settings.option.deletedMessagesMode.strikeThroughShorten = 취소선 긋고 자르기 settings.deletedMessages.max = 최대. settings.deletedMessages.characters = 문자 settings.boolean.banDurationAppended = 강제 퇴장 기간 보기 -settings.boolean.banDurationAppended.tip = 최근에 삭제된 메시지 뒤에 타임아웃 시간을 초 단위로 보여줍니다. +settings.boolean.banDurationAppended.tip = 최근에 삭제된 메시지 뒤에 임시 차단 시간을 초 단위로 보여줍니다. settings.boolean.banReasonAppended = 강제 퇴장 사유 보기 (매니저만) settings.boolean.banReasonAppended.tip = 최근에 삭제된 메시지 뒤에 강제 퇴장 사유를 보여줍니다. (매니저만, 직접 실행한 강제 퇴장 제외) settings.boolean.showBanMessages = 다음 설정에 따라 강제 퇴장 메시지 별도로 보기\: settings.boolean.banDurationMessage = 강제 퇴장 기간 보기 -settings.boolean.banDurationMessags.tip = 별도의 강제 퇴장 메시지에 타임아웃 시간을 초 단위로 보여줍니다. +settings.boolean.banDurationMessags.tip = 별도의 강제 퇴장 메시지에 임시 차단 시간을 초 단위로 보여줍니다. settings.boolean.banReasonMessage = 강제 퇴장 사유 보기 (매니저만) settings.boolean.banReasonMessage.tip = 별도의 강제 퇴장 메시지에 강제 퇴장 사유를 보여줍니다. (매니저만, 직접 실행한 강제 퇴장 제외) settings.boolean.combineBanMessages = 강제 퇴장 메시지 포함 @@ -612,13 +648,36 @@ settings.section.modInfos = 매니저 전용 정보 settings.boolean.showModActions = 매니저 활동을 채팅에 표시 (<확장 - 모드 명령어 기록>과 비슷) settings.boolean.showModActions.tip = 내 명령어를 제외하고 어떤 모드 명령어가 실행됐는지 보여줍니다(확장 - 모드 명령어 기록에서도 볼 수 있습니다). settings.boolean.showModActionsRestrict = 채팅에서 이미 보이는 활동은 숨기기 (예. 강제 퇴장) -settings.boolean.showModActionsRestrict.tip = 채팅에서 정보 메시지에 @Mod를 추가함으로써 이 모드 활동이 보여지면, 별도의 모드 활동 메시지를 보여주지 않습니다. +settings.boolean.showModActionsRestrict.tip = 채팅에서 @Mod를 덧붙여서 이 모드 활동이 정보 메시지에 보여지면, 별도의 모드 활동 메시지를 보여주지 않습니다. settings.boolean.showActionBy = @Mod가 야기한 활동을 추가 (예. 강제 퇴장) settings.boolean.showActionBy.tip = 몇몇 모드가 짧은 시간 안에 같은 활동을 한다면, 부정확하게 표시될 수 있습니다. settings.section.userDialog = 유저 다이얼로그 settings.boolean.closeUserDialogOnAction = 작업 수행(버튼) 후 유저 다이얼로그 닫기 settings.boolean.closeUserDialogOnAction.tip = 버튼 클릭 후, 유저 정보 다이얼로그를 자동으로 닫습니다. settings.boolean.openUserDialogByMouse = 항상 마우스 포인터 근처에 다이얼로그 열기 +settings.boolean.reuseUserDialog = 같은 유저의 이미 열려있는 유저 다이얼로그를 재사용 +settings.boolean.reuseUserDialog.tip = 이미 열려있는 유저의 유저 다이얼로그를 열때, 다른 다이얼로그로 열거나 교체하지 않습니다. +settings.long.clearUserMessages.option.-1 = 절대 +settings.long.clearUserMessages.option.3 = 3시간 +settings.long.clearUserMessages.option.6 = 6시간 +settings.long.clearUserMessages.option.12 = 12시간 +settings.long.clearUserMessages.option.24 = 24시간 +settings.long.clearUserMessages.label = 다음 시간 동안 활동하지 않은 유저의 메시지 기록 지우기\: + +!-- Names --! +settings.label.mentions = 클릭 가능한 멘션\: +settings.label.mentions.tip = 최근 채팅한 유저의 유저명을 클릭 가능하게 만들고 채팅 메시지에서 강조합니다. +settings.label.mentionsBold = 굵게 +settings.label.mentionsUnderline = 밑줄 +settings.label.mentionsColored = 색칠 +settings.label.mentionsColored.tip = 클릭 가능한 멘션을 유저 색상으로 설정합니다. +settings.long.markHoveredUser.option.0 = 끄기 +settings.long.markHoveredUser.option.1 = 모두 / 항상 +settings.long.markHoveredUser.option.2 = 멘션만 +settings.long.markHoveredUser.option.3 = Ctrl을 누르는 동안 멘션만 +settings.long.markHoveredUser.option.4 = Ctrl을 누르는 동안 +settings.label.markHoveredUser = 마우스 오버한 유저명 강조\: +settings.label.markHoveredUser.tip = 채팅에서 마우스 오버한 유저명의 다른 등장도 강조합니다. !-- Highlight --! settings.section.highlightMessages = 강조된 메시지 @@ -627,13 +686,15 @@ settings.boolean.highlightEnabled.tip = 조건과 일치하는 메시지를 다 settings.boolean.highlightUsername = 내 닉네임 강조 settings.boolean.highlightUsername.tip = 목록에 추가하지 않더라도 내 닉네임을 포함하는 메시지를 강조합니다. settings.boolean.highlightNextMessages = 강조 후속 조치 -settings.boolean.highlightNextMessages.tip = 마지막 강조 이후 같은 사용자의 바로 쓰여진 메시지를 강조합니다. +settings.boolean.highlightNextMessages.tip = 마지막 강조 이후 같은 유저의 바로 쓰여진 메시지를 강조합니다. settings.boolean.highlightOwnText = 내 메시지도 강조 확인 settings.boolean.highlightOwnText.tip = 내 메시지를 강조할 수 있으며, 그렇지 않으면 내 메시지를 강조 표시하지 않습니다. 테스트하기 좋습니다. settings.boolean.highlightIgnored = 무시한 메시지도 확인 settings.boolean.highlightIgnored.tip = 무시한 메시지도 강조할 수 있으며, 그렇지 않으면 무시한 메시지는 강조 표시되지 않습니다. settings.boolean.highlightMatches = 강조/무시와 일치하는 부분 강조 settings.boolean.highlightMatches.tip = 조건에 일치하는 텍스트를 사각형으로 감싸서 강조합니다. (강조된/무시한 메시지 창 포함) +settings.boolean.highlightMatchesAll = 모든 등장 강조 +settings.boolean.highlightMatchesAll.tip = 예를 들어 링크와 멘션에서 일치하는 강조도 강조합니다. !-- Ignore --! settings.section.ignoreMessages = 무시 메시지 @@ -659,10 +720,10 @@ settings.boolean.logMessage = 채팅 메시지 settings.boolean.logMessage.tip = 일반 채팅 메시지를 기록합니다. settings.boolean.logInfo = 정보 메시지 settings.boolean.logInfo.tip = 방송 제목, 트위치나 채팅 연결에 대한 메시지와 같은 정보 메시지를 기록합니다. -settings.boolean.logBan = 강제 퇴장/임시 퇴장 -settings.boolean.logBan.tip = 강제 퇴장 메시지와 강제 퇴장/임시 퇴장을 기록합니다. +settings.boolean.logBan = 강제 퇴장/임시 차단 +settings.boolean.logBan.tip = 강제 퇴장/임시 차단을 강제 퇴장 메시지로 기록합니다. settings.boolean.logDeleted = 삭제된 메시지 -settings.boolean.logDeleted.tip = 삭제된 메지시 기록하기 +settings.boolean.logDeleted.tip = 삭제된 메지시를 삭제된 메시지라고 기록하기 settings.boolean.logMod = 모드/언모드 # MOD/UNMOD should not be translated since it refers to how it appears in the log settings.boolean.logMod.tip = 모드/언모드 메시지를 기록합니다. @@ -705,7 +766,7 @@ settings.string.completionSearch = 이모티콘/명령어 검색\: # Example: Emote "joshWithIt" would match only when entering the beginning of "joshWithIt" settings.string.completionSearch.option.start = 이름의 시작 # Example: Emote "joshWithIt" would match when entering the beginning of "joshWithIt", "With" or "It" -settings.string.completionSearch.option.words = 시작, 대문자로 된 단어 +settings.string.completionSearch.option.words = 첫 문자나 대문자로 된 단어 # Example: Emote "joshWithIt" would match when entering any part of the name (even just "t") settings.string.completionSearch.option.anywhere = 이름내 어디든 settings.section.completionNames = 닉네임 @@ -714,7 +775,6 @@ settings.boolean.completionAllNameTypes = 결과에 모든 타입의 이름을 settings.boolean.completionAllNameTypesRestriction = 2개 이하로 일치할 때만 settings.section.completionAppearance = 외관 / 동작 settings.boolean.completionShowPopup = 팝업 보기 -settings.completion.itemsShown = 최대로 보여지는 항목\: settings.boolean.completionCommonPrefix = 공통 접두사로 완성 (1개 이상 일치할 경우) settings.completion.nameSorting = 이름 정렬\: settings.completion.option.predictive = 예측 @@ -726,14 +786,18 @@ settings.section.dialogs = 다이얼로그 위치/크기 settings.dialogs.restore = 다이얼로그 복원\: settings.dialogs.option.openDefault = 항상 기본 위치에서 열기 settings.dialogs.option.keepSession = 세션에 있는 동안만 위치 유지 -settings.dialogs.option.restore = 마지막 세션에 있던 위치로 복원 -settings.dialogs.option.reopen = 마지막 세션에서 다시 열고 복원 +settings.dialogs.option.restore = 마지막 세션에 있던 위치/크기 유지 +settings.dialogs.option.reopen = 마지막 세션으로부터 다시 열기 settings.boolean.restoreOnlyIfOnScreen = 화면에 있을 때만 위치 복원 settings.boolean.attachedWindows = 메인 창 이동시 다이얼로그도 이동 !-- Minimizing Settings --! -settings.boolean.minimizeToTray = 최소화시 트레이로 -settings.boolean.closeToTray = 닫기시 트레이로 +settings.section.minimizing = 최소화 / 트레이 +settings.boolean.minimizeToTray = 최소화 시 트레이로 +settings.boolean.closeToTray = 닫기 시 트레이로 +settings.boolean.trayIconAlways = 항상 트레이 아이콘 보기 +settings.boolean.hideStreamsOnMinimize = 최소화 시 생방송 창 숨기기 +settings.boolean.hideStreamsOnMinimize.tip = 메인 창을 최소화 할 때 생방송 창도 자동으로 숨깁니다. !-- Other Window Settings --! settings.section.otherWindow = 기타 @@ -766,13 +830,16 @@ settings.boolean.tabsMwheelScrollingAnywhere = 입력 상자에서도 마우스 !-- Stream Settings --! settings.section.streamHighlights = 생방송 하이라이트 -settings.streamHighlights.info = 생방송 하이라이트(파일에 업타임/메모 작성)는 /addStreamHighlight 명령어, 단축키(단축키 설정) 또는 모드 채팅 명령어(여기서 설정 가능한 것)로 추가할 수 있습니다. 더 많은 정보는 도움말을 확인하세요. +settings.section.streamHighlightsCommand = 채팅 명령어 +settings.streamHighlights.info = 생방송 하이라이트(파일에 방송 시간/메모를 작성)는 /addStreamHighlight 명령어, 단축키(단축키 설정) 또는 모드 채팅 명령어(여기서 설정 가능한 것)로 추가할 수 있습니다. 더 많은 정보는 도움말을 확인하세요. +settings.streamHighlights.matchInfo = 매니저와 같이 신뢰할 수 있는 유저에게 채팅 명령어를 제한하는 것을 매우 추천합니다. settings.streamHighlights.channel = 모드 명령어 채널\: settings.streamHighlights.command = 모드 명령어\: +settings.streamHighlights.match = 명령어 권한\: settings.boolean.streamHighlightChannelRespond = 채팅 메시지로 모드 명령어에 반응 -settings.boolean.streamHighlightChannelRespond.tip = 명령어가 성공했는지 볼 수 있게 채팅 메시지를 보냅니다 -settings.boolean.streamHighlightMarker = 생방송 마커도 같이 생성 -settings.boolean.streamHighlightMarker.tip = 생방송 하이라이트를 파일에 작성시 생방송 마커도 추가로 생성합니다 +settings.boolean.streamHighlightChannelRespond.tip = 명령어가 성공했는지 볼 수 있게 채팅 메시지를 보냅니다. +settings.boolean.streamHighlightMarker = 생방송 하이라이트를 추가할 때 생방송 마커도 같이 생성 +settings.boolean.streamHighlightMarker.tip = 생방송 하이라이트를 작성시 생방송 마커도 추가로 파일에 생성합니다. !===================! !== Emotes Dialog ==! diff --git a/src/chatty/lang/Strings_nl.properties b/src/chatty/lang/Strings_nl.properties new file mode 100644 index 000000000..382799acc --- /dev/null +++ b/src/chatty/lang/Strings_nl.properties @@ -0,0 +1,26 @@ +## +## Contributors: Lander03xD +## +## This file is UTF-8 encoded. +## + +!==================! +!== Main Menubar ==! +!==================! + +!-- Main Menu --! +menubar.dialog.connect = Verbind +menubar.dialog.settings = Instellingen +menubar.dialog.login = Inloggen +menubar.dialog.save = Opslaan +menubar.application.exit = Sluiten + +!-- View Menu --! +menubar.setting.ontop = Altijd zichtbaar +menubar.menu.options = Opties +menubar.menu.titlebar = Titelbalk +menubar.setting.titleShowChannelState = Kanaal Status +menubar.setting.showJoinsParts = Toon joins/parts +menubar.setting.showModMessages = Toon mod/unmod +menubar.setting.mainResizable = Schaalbaar venster +menubar.dialog.search = Zoek tekst diff --git a/src/chatty/lang/Strings_pl.properties b/src/chatty/lang/Strings_pl.properties index 484f47820..1899d8c4a 100644 --- a/src/chatty/lang/Strings_pl.properties +++ b/src/chatty/lang/Strings_pl.properties @@ -55,6 +55,7 @@ menubar.dialog.moderationLog = Log moderacji czatu menubar.dialog.chatRules = Zasady chatu # This is in context of the "StreamChat" submenu menubar.dialog.streamchat = Otwórz okno dialogowe +menubar.stream.addhighlight = Dodaj znacznik streamu !-- Help Menu --! menubar.menu.help = Pomoc @@ -74,6 +75,8 @@ channelCm.copyStreamname = Skopiuj nazwę kanału channelCm.dialog.chatRules = Pokaż zasady panujące w czacie channelCm.follow = Obserwuj kanał channelCm.unfollow = Przestań obserwować +channelCm.favorite = Dodaj do ulubionych +channelCm.unfavorite = Usuń z ulubionych channelCm.speedruncom = Otwórz Speedrun.com channelCm.closeChannel = Zamknij kanał # Uses plural form and shows number when joining more than one channel @@ -113,9 +116,17 @@ emoteCm.showEmotes = Pokaż emotikony # {0} = Number of Live Streams, {1} = Current sorting streams.title = Aktualne transmisje ({0}) [Sortowanie\: {1}] streams.sorting.recent = Ostatnie +streams.sorting.recent.tip = Ostatnio rozpoczęta transmisja lub ostatnio zmieniony tytuł/gra +streams.sorting.uptime = Czas (najnowszy) +streams.sorting.uptime.tip = Ostatnio rozpoczęte transmisje streams.sorting.name = Nazwa +streams.sorting.name.tip = Nazwa kanału (alfabetycznie) streams.sorting.game = Gra +streams.sorting.game.tip = Nazwa gry (alfabetycznie) streams.sorting.viewers = Widzowie +streams.sorting.viewers.tip = Największa ilość widzów +streams.sortingOption.fav = Najpierw ulubione +streams.sortingOption.fav.tip = Kanały dodane do ulubionych w "Kanały - Ulubione / Historia" są wyświetlane jako pierwsze streams.cm.menu.sortBy = Sortuj za pomocą.. streams.cm.removedStreams = Usunięte transmisje.. streams.cm.refresh = Odśwież @@ -165,6 +176,11 @@ dialog.button.test = Test # Open a help of sorts dialog.button.help = Pomoc +!====================! +!== General Status ==! +!====================! +status.loading = Wczytywanie... + !====================! !== Connect Dialog ==! !====================! @@ -258,6 +274,8 @@ admin.button.presets = Zbiór ustaw. admin.button.fav = Ulub. admin.button.selectGame = Wybierz grę admin.button.removeGame = Usuń +admin.button.selectTags = Wybierz tagi +admin.button.removeTags = Usuń admin.button.update = Uaktualnij # {0} = Duration since last update (e.g. 5m) admin.infoLoaded = Ostatnio zaktualizowane informacje\: {0} temu @@ -274,6 +292,7 @@ admin.presets.button.useTitleOnly = Wykorzystaj tylko tytuł admin.presets.column.fav = Ulub. admin.presets.column.title = Tytuł admin.presets.column.game = Gra +admin.presets.column.tags = Tagi admin.presets.column.lastActivity = Ostatnia aktywność admin.presets.column.usage = Wykorzystanie admin.presets.setting.currentGame = Aktualna gra @@ -283,6 +302,8 @@ admin.presets.cm.toggleFavorite = Przełącz ulubione admin.presets.cm.remove = Usuń admin.presets.cm.useStatus = Wykorzystaj status admin.presets.cm.useTitleOnly = Wykorzystaj tylko tytuł +admin.presets.cm.useGameOnly = Wykorzystuj tylko grę +admin.presets.cm.useTagsOnly = Wykorzystuj tylko tagi # HTML admin.presets.info = Wybierz pozycję i naciśnij F by dodać/usunąć z ulubionych. @@ -298,6 +319,21 @@ admin.game.searching = Wyszukiwanie.. # {0} = Number of search results, {1} = Number of favorites admin.game.listInfo = Znalezione\: {0} / Ulubione\: {1} +!-- Select Tags --! +admin.tags.title = Wybierz co najwyżej {0} tagów +admin.tags.listInfo = Tagów\: {0}/{1} - Ulubione\: {2}/{3} +admin.tags.info = Kliknij dwukrotnie na tagu na liście by go dodać/usunąć. By zobaczyć wszystkie tagi, wyłącz "Pokazuj tylko Aktywne / Ulubione". By filtrować listę, wprowadź tekst. +admin.tags.button.showAll = Pokaż tylko Aktywne / Ulubione +admin.tags.button.clearFilter = Usuń filtr +# Favorite as an action, to add to favorites +admin.tags.button.favorite = Dodaj do ulub. +# Unfavorite as an action, to remove from favorites +admin.tags.button.unfavorite = Usuń z ulub. +admin.tags.button.addSelected = Dodaj zaznaczony +admin.tags.button.remove.tip = Usuń "{0}" +admin.tags.cm.replaceAll = Usuń wszystkie +admin.tags.cm.replace = Zamień "{0}" + !=========================! !== Channel Info Dialog ==! !=========================! @@ -335,6 +371,7 @@ channelInfo.viewers.cm.verticalZoom = Pionowe przybliżenie !=================! userDialog.title = Użytkownik\: userDialog.setting.pin = Przypięte okna +userDialog.setting.pin.tip = Przypięte ogna dialogowe pozostają otwarte przy tym samym użytkowniku, do czasu kiedy nie zostaną ręcznie zamknięte. userDialog.editBanReasons = Edytuj predefiniowanie powody bana (jeden powód na linię)\: userDialog.selectBanReason = Wybierz powód bana (opcjonalne) userDialog.customReason = Nie obecny powód\: @@ -345,6 +382,14 @@ userDialog.registered = Zarejestrowany\: {0} temu userDialog.registered.tip = Konto utworzone\: {0} # {0} = Number of followers userDialog.followers = Obserwujący\: {0} +# {0} = User ID +userDialog.id = ID\: {0} +# {0} = Follow age (example: "1.5 years") +userDialog.followed = Zaczęto obserwować\: {0} temu +# {0} = Follow age (example: "1 year 277 days"), {1} = Follow date/time +userDialog.followed.tip = Zaczęto obserwować\: {0} temu ({1}) +userDialog.notFollowing = Nieobserwowany +userDialog.error = Błąd żądania !=======================! !== Chat Rules Dialog ==! @@ -428,10 +473,6 @@ settings.chooseFolder.button.default = Domyślny settings.chooseFolder.button.open = Otwórz !-- Chat Font --! -settings.section.chatFont = Czcionka czatu -settings.chatFont.button.selectFont = Wybierz czcionkę -settings.chatFont.fontName = Nazwa czcionki\: -settings.chatFont.fontSize = Wielkość czcionki\: settings.chatFont.lineSpacing = Odstęp pomiędzy liniami\: settings.chatFont.option.smallest = Najmniejsza settings.chatFont.option.smaller = Mniejsza @@ -444,7 +485,6 @@ settings.chatFont.messageSpacing = Odstęp wiadomości\: settings.chatFont.bottomMargin = Dolny margines !-- Input / Userlist Font --! -settings.section.otherFonts = Wprowadzanie / Czcionka listy użytkowników settings.otherFonts.inputFont = Wprowadzanie\: settings.otherFonts.userlistFont = Lista użytkowników\: settings.otherFonts.info = Uwaga\: Główna wielkość czcionki dla programu, może być edytowana za pomocą strony ustawień wyglądu. @@ -527,6 +567,7 @@ settings.section.msgColors = Własne kolory wiadomości settings.boolean.msgColorsEnabled = Włącz własne kolory wiadomości settings.boolean.msgColorsEnabled.tip = Jeżeli ta opcja jest włączona, wpisy w tabeli poniżej mogą wpływać na kolor wiadomości pojawiających się w czacie settings.section.msgColorsOther = Inne ustawienia +settings.boolean.msgColorsPrefer = Wykorzystuj własny kolor wiadomości nad kolorem podkreślenia settings.boolean.actionColored = Koloryzuj wiadomości czynności (/me) wykorzystując kolor użytkownika !-- Usercolors --! @@ -537,6 +578,11 @@ settings.string.nickColorCorrection.option.normal = Normalna settings.string.nickColorCorrection.option.strong = Silna settings.string.nickColorCorrection.option.old = Stara settings.string.nickColorCorrection.option.gray = Skala szarości +settings.label.nickColorBackground = Zmień tło by poprawić czytelność +settings.label.nickColorBackground.tip = Jeżeli nazwa użytkownika wyświetlona na własnym tle jest słabo czytelna, wykorzystuj główny kolor tła (jako, że kolory użytkownika zostają poprawione dla głównego koloru tła) +settings.long.nickColorBackground.option.0 = Wyłączone +settings.long.nickColorBackground.option.1 = Normalne +settings.long.nickColorBackground.option.2 = Pogrubione !-- Usericons --! settings.section.usericons = Ikony Użytkowników (Odznaczenia) @@ -580,6 +626,10 @@ settings.ignoredEmotes.title = Ignorowane emotikony settings.ignoredEmotes.info1 = Ignorowane emotikony zostaną wyświetlone jako odpowiadający im kod, zamiast obrazka. settings.ignoredEmotes.info2 = Możesz wykorzystać menu kontekstowe emotikon (kliknięcie prawym przyciskiem myszy na emotikonie), by dodać go do listy. +!-- Chat --! +settings.boolean.showImageTooltips = Pokaż chmurki dla Emotów / Odznak +settings.boolean.showTooltipImages = Pokaż obrazki w chmurce + !-- Messages --! settings.section.deletedMessages = Usunięte wiadomości (Timeouty/Bany) settings.option.deletedMessagesMode.delete = Usuń wiadomość @@ -619,6 +669,33 @@ settings.section.userDialog = Okna dialogowe settings.boolean.closeUserDialogOnAction = Zamknij okno dialogowe poprzez wykonanie akcji (naciśnięcie przycisku) settings.boolean.closeUserDialogOnAction.tip = Po naciśnięciu przycisku, okno dialogowe użytkownika zostanie automatycznie zamknięte. settings.boolean.openUserDialogByMouse = Zawsze otwieraj okno dialogowe w pobliżu kursora myszy +settings.boolean.reuseUserDialog = Wykorzystuj ponownie okno dialogowe tego samego użytkownika +settings.boolean.reuseUserDialog.tip = Zapobiega otwarciu nowego okna dialogowego, jeżeli jedno z okien dla wybranego użytkownika jest już otwarte. +settings.long.clearUserMessages.option.-1 = Nigdy +settings.long.clearUserMessages.option.3 = 3 godziny +settings.long.clearUserMessages.option.6 = 6 godzin +settings.long.clearUserMessages.option.12 = 12 godzin +settings.long.clearUserMessages.option.24 = 24 godziny +settings.long.clearUserMessages.label = Wyczyść historię wiadomości użytkownika nieaktywnego przez\: +settings.label.banReasonsHotkey = Skrót otwierające listę powodów nałożonych banów\: +settings.label.banReasonsInfo = Powód nałożonego bana może być zmieniony bezpośrednio w Oknie Dialogowym Użytkownika + +!-- Names --! +settings.label.mentions = Klikanie na wzmianki\: +settings.label.mentions.tip = Nazwy użytkowników, którzy ostatnio pisali są odznaczane w czacie i da się na nie kliknąć. +settings.label.mentionsInfo = Klikowalne zaznaczenia (Informacje)\: +settings.label.mentionsInfo.tip = Tak jak powyżej, z wyjątkiem wiadomości informacyjnych (np. odpowiedzi na komendy, Automod) +settings.label.mentionsBold = Pogrubienie +settings.label.mentionsUnderline = Podkreślenie +settings.label.mentionsColored = Kolorowanie +settings.label.mentionsColored.tip = Odznaczanie kolorem wzmianek użytkownika +settings.long.markHoveredUser.option.0 = Wyłączone +settings.long.markHoveredUser.option.1 = Wszystkie / Zawsze +settings.long.markHoveredUser.option.2 = Tylko wzmianki +settings.long.markHoveredUser.option.3 = Tylko wzmianki oraz wszystkie przy wciśniętym Ctrl +settings.long.markHoveredUser.option.4 = Tylko przy wciśniętym Ctrl +settings.label.markHoveredUser = Zaznacz nazwę użytkownika przy najechaniu +settings.label.markHoveredUser.tip = Zaznacz wystąpienia nazwy użytkownika w czacie, gdy najeżdżasz na niego kursorem !-- Highlight --! settings.section.highlightMessages = Podkreślenia wiadomości @@ -634,6 +711,8 @@ settings.boolean.highlightIgnored = Sprawdzaj zignorowane wiadomości settings.boolean.highlightIgnored.tip = Pozwala na podkreślanie zignorowanych wiadomości. W przeciwnym wypadku, zignorowane wiadomości nigdy nie są podkreślane. settings.boolean.highlightMatches = Wyróżniaj pasujące części podkreślenia/ignorowania settings.boolean.highlightMatches.tip = Otacza sekcję tekstu, które spowodowały podkreślenie kwadratem (oraz oknie podkreślonych/zignorowanych wiadomości). +settings.boolean.highlightMatchesAll = Oznacz wszystkie wystąpienia +settings.boolean.highlightMatchesAll.tip = Zaznacz także pasujące zaznaczenia, np. linki i wzmianki !-- Ignore --! settings.section.ignoreMessages = Ignorowanie wiadomości @@ -701,6 +780,13 @@ settings.completion.option.namesEmotes = Nazwy, po czym Emotikony settings.completion.option.emotesNames = Emotikony, po czym nazwy settings.completion.option.custom = Własne wykończenie settings.completion.info = Podpowiedź\: Niezależnie od ustawień powyżej, możesz wykorzystać przedrostek @ by zawsze wykończyć za pomocą nazw, . (kropka) by wykorzystać własne wykończenie lub \: by wykończyć za pomocą Emota.

    Na przykład\: \:think + TAB > \:thinking\: +settings.label.completionEmotePrefix = Prefix emotikonów Twitcha\: +settings.label.completionEmotePrefix.tip = Wykorzystaj ten prefix, by zawsze wykończyć Emotem (Emoji zawsze wykorzystują '\:'). +settings.completionEmotePrefix.option.none = Brak +settings.long.completionMixed.option.0 = Najlepiej pasujące +settings.long.completionMixed.option.1 = Najpierw Emoji +settings.long.completionMixed.option.2 = Najpierw Emoty +settings.long.completionMixed.tip = Sposób w jaki sortowana jest lista mieszana Emotów i Emoji settings.string.completionSearch = Przeszukiwanie emotów / komend\: # Example: Emote "joshWithIt" would match only when entering the beginning of "joshWithIt" settings.string.completionSearch.option.start = Małpa na początku nazwy @@ -714,12 +800,15 @@ settings.boolean.completionAllNameTypes = Zawieraj wszystkie typy w wynikach (No settings.boolean.completionAllNameTypesRestriction = Tylko, gdy znaleziono nie więcej niż 2 rezultaty settings.section.completionAppearance = Wygląd / Zachowanie settings.boolean.completionShowPopup = Pokaż informacje jako popup -settings.completion.itemsShown = Maks. ilość wyświetlonych\: +settings.boolean.completionShowPopup.tip = Wyświetlaj aktualne wyniki wykończenia w popupie +settings.label.searchResults = wyniki wyszukiwania +settings.boolean.completionAuto = Pokazuj automatycznie dla niektórych typów (np. Emoji) settings.boolean.completionCommonPrefix = Ukończ wykorzystując domyślny przedrostek (jeśli znaleziono więcej niż jeden rezultat) settings.completion.nameSorting = Sortowanie nazw\: settings.completion.option.predictive = Przewidywane settings.completion.option.alphabetical = Alfabetyczne settings.completion.option.userlist = Za pomocą listy użytkowników +settings.boolean.completionSpace = Dołącz spację !-- Dialogs Settings --! settings.section.dialogs = Pozycja/Rozmiar okien dialogowych @@ -732,8 +821,12 @@ settings.boolean.restoreOnlyIfOnScreen = Przywróć pozycję tylko jeśli znajdu settings.boolean.attachedWindows = Przesuń okna dialogowe podczas przesuwania okna !-- Minimizing Settings --! -settings.boolean.minimizeToTray = Minimalizuj do tacki systemowej -settings.boolean.closeToTray = Zamykaj do tacki systemowej +settings.section.minimizing = Minimalizacja / obszar powiadomień +settings.boolean.minimizeToTray = Minimalizuj do zasobnika +settings.boolean.closeToTray = Zamykaj do zasobnika +settings.boolean.trayIconAlways = Zawsze pokazuj ikonkę w zasobniku +settings.boolean.hideStreamsOnMinimize = Ukrywaj okno nadających kanałów podczas minimalizacji +settings.boolean.hideStreamsOnMinimize.tip = Automatycznie ukryj okno Nadających kanałów podczas minimalizacji głównego okna !-- Other Window Settings --! settings.section.otherWindow = Inne @@ -766,9 +859,12 @@ settings.boolean.tabsMwheelScrollingAnywhere = Przesuń kursor nad obszar wprowa !-- Stream Settings --! settings.section.streamHighlights = Znaczniki streamu +settings.section.streamHighlightsCommand = Komenda czatu settings.streamHighlights.info = Znaczniki Streamu (zapisany w pliku czas streamu z adnotacją) mogą zostać utworzone za pomocą komendy /addStreamHighlight, skrótu klawiszowego (zdefiniowanego w opcjach) lub poprzez komendę moderatora (którą możesz tu dodać). Zobacz pomoc, by otrzymać dodatkowe informacje. +settings.streamHighlights.matchInfo = Jest wysoce zalecane, by ograniczyć działanie komendy do zaufanych użytkowników, jak np. moderatorów. settings.streamHighlights.channel = Kanał komend moderatora\: settings.streamHighlights.command = Komenda moderatora\: +settings.streamHighlights.match = Dostęp do komendy\: settings.boolean.streamHighlightChannelRespond = Odpowiedz na komendę moderatora wiadomością settings.boolean.streamHighlightChannelRespond.tip = Wyślij wiadomość w czacie, tak by moderatorzy mogli zobaczyć, że komenda została wykonana pomyślnie settings.boolean.streamHighlightMarker = Dodatkowo utwórz marker streamu diff --git a/src/chatty/lang/Strings_ru.properties b/src/chatty/lang/Strings_ru.properties index 09128450e..50b76aebb 100644 --- a/src/chatty/lang/Strings_ru.properties +++ b/src/chatty/lang/Strings_ru.properties @@ -399,10 +399,6 @@ settings.chooseFolder.button.default = По умолчанию settings.chooseFolder.button.open = Открыть !-- Chat Font --! -settings.section.chatFont = Шрифт -settings.chatFont.button.selectFont = Выбрать шрифт -settings.chatFont.fontName = Название шрифта -settings.chatFont.fontSize = Размер шрифта settings.chatFont.lineSpacing = Межстрочный интервал\: settings.chatFont.option.smallest = Наименьший settings.chatFont.option.smaller = Меньше @@ -415,7 +411,6 @@ settings.chatFont.messageSpacing = Расстояние между сообще settings.chatFont.bottomMargin = Нижнее поле\: !-- Input / Userlist Font --! -settings.section.otherFonts = Ввода / Список зрителей settings.otherFonts.inputFont = Ввод\: settings.otherFonts.userlistFont = Список зрителей\: settings.otherFonts.info = Примечание\: Общий размер шрифта программы может быть изменен во вкладке 'Тема оформления' @@ -637,7 +632,6 @@ settings.completion.option.custom = Пользовательское автоз settings.completion.info = Примечание\: Вне зависимости от этих настроек, с префиксом @ Автозаполнение будет работать на имена, с . (точка) - пользовательское автозаполнение, с \: - на Эмодзи.

    Пример\: \:think + TAB > \:thinking\: settings.section.completionAppearance = Вид / Поведение settings.boolean.completionShowPopup = Показывать всплывающее окно -settings.completion.itemsShown = Максимальное количество элементов\: settings.completion.nameSorting = Сортировка\: settings.completion.option.alphabetical = По афлавиту settings.completion.option.userlist = По списку зрителей diff --git a/src/chatty/lang/Strings_tr.properties b/src/chatty/lang/Strings_tr.properties index 41907b261..ce818df70 100644 --- a/src/chatty/lang/Strings_tr.properties +++ b/src/chatty/lang/Strings_tr.properties @@ -165,6 +165,11 @@ dialog.button.test = Test # Open a help of sorts dialog.button.help = Yardım +!====================! +!== General Status ==! +!====================! +status.loading = Yükleniyor.. + !====================! !== Connect Dialog ==! !====================! @@ -258,6 +263,8 @@ admin.button.presets = Ayarlanmış admin.button.fav = Fav admin.button.selectGame = Oyun seç admin.button.removeGame = Kaldır +admin.button.selectTags = Etiket seç +admin.button.removeTags = Sil admin.button.update = Güncelle # {0} = Duration since last update (e.g. 5m) admin.infoLoaded = Bilgi en son yüklendi\: {0} önce @@ -274,6 +281,7 @@ admin.presets.button.useTitleOnly = Sadece başlığı kullan admin.presets.column.fav = Fav admin.presets.column.title = Başlık admin.presets.column.game = Oyun +admin.presets.column.tags = Etiketler admin.presets.column.lastActivity = Son Aktivite admin.presets.column.usage = Kullanım admin.presets.setting.currentGame = Şu anki oyun @@ -283,6 +291,8 @@ admin.presets.cm.toggleFavorite = Toggle favorite admin.presets.cm.remove = Kaldır admin.presets.cm.useStatus = Durumu kullan admin.presets.cm.useTitleOnly = Sadece başlığı kullan +admin.presets.cm.useGameOnly = Sadece oyun kullan +admin.presets.cm.useTagsOnly = Sadece etiketleri kullan # HTML admin.presets.info = Favori geçişi için seç ve F bas, silmek için Del bas, veya sağ-tıklayarak içerik menüsünü aç. @@ -298,6 +308,14 @@ admin.game.searching = Aranıyor.. # {0} = Number of search results, {1} = Number of favorites admin.game.listInfo = Arama\: {0} / Favoriler\: {1} +!-- Select Tags --! +admin.tags.title = {0} etikete kadar seç +admin.tags.listInfo = Etiketler\: {0}/{1} - Favoriler\: {2}/{3} +# Favorite as an action, to add to favorites +admin.tags.button.favorite = Favori Yap +# Unfavorite as an action, to remove from favorites +admin.tags.button.unfavorite = Favoriden Çıkar + !=========================! !== Channel Info Dialog ==! !=========================! @@ -428,10 +446,6 @@ settings.chooseFolder.button.default = Varsayılan settings.chooseFolder.button.open = Aç !-- Chat Font --! -settings.section.chatFont = Sohbet Yazı Tipi -settings.chatFont.button.selectFont = Yazı tipi seç -settings.chatFont.fontName = Yazı Tipi İsmi\: -settings.chatFont.fontSize = Yazı Tipi Boyutu\: settings.chatFont.lineSpacing = Satır Aralığı\: settings.chatFont.option.smallest = En küçük settings.chatFont.option.smaller = Daha küçük @@ -444,7 +458,6 @@ settings.chatFont.messageSpacing = Mesaj Aralığı\: settings.chatFont.bottomMargin = Alttaki Boşluk\: !-- Input / Userlist Font --! -settings.section.otherFonts = Giriş / Kullanıcı Listesi Yazı Tipi settings.otherFonts.inputFont = Giriş\: settings.otherFonts.userlistFont = Kullanıcı Listesi\: settings.otherFonts.info = Not\: Programın genel yazı boyutu 'Görünüş' sekmesindeki Tema bölümünde değiştirilebilir. @@ -714,7 +727,6 @@ settings.boolean.completionAllNameTypes = Sonuca bütün isim çeşitlerini dahi settings.boolean.completionAllNameTypesRestriction = Sadece en fazla iki eşleşme olduğunda settings.section.completionAppearance = Görünüm / Davranış settings.boolean.completionShowPopup = Bilgi popup'ını göster -settings.completion.itemsShown = Gösterilen Maks Öğeler\: settings.boolean.completionCommonPrefix = Ortak prefix için tamamla (bir eşleşmeden fazla ise) settings.completion.nameSorting = İsim Sıralama\: settings.completion.option.predictive = Öngörü diff --git a/src/chatty/lang/Strings_zh_TW.properties b/src/chatty/lang/Strings_zh_TW.properties index dd43c1f19..9f4112615 100644 --- a/src/chatty/lang/Strings_zh_TW.properties +++ b/src/chatty/lang/Strings_zh_TW.properties @@ -111,9 +111,9 @@ chat.connecting = 嘗試連線到 {0} chat.secured = 安全連線 chat.joining = 正在進入 {0} chat.joined = 你已經進入 {0} -chat.joinError.notConnected = 無法進入 '{0}' (未連線) -chat.joinError.alreadyJoined = 無法進入 '{0}' (已進入) -chat.joinError.invalid = 無法進入 '{0}' (無效的頻道名稱) +chat.joinError.notConnected = 無法進入 ''{0}'' (未連線) +chat.joinError.alreadyJoined = 無法進入 ''{0}'' (已進入) +chat.joinError.invalid = 無法進入 ''{0}'' (無效的頻道名稱) chat.disconnected = 斷線 chat.error.unknownHost = 未知的主機 chat.error.connectionTimeout = 連線逾時 @@ -288,10 +288,6 @@ searchDialog.button.search = 搜尋 settings.restartRequired = 有些更改過的設定值要在重新啟動Chatty之後才會生效。 !-- Chat Font --! -settings.section.chatFont = 聊天室字型 -settings.chatFont.button.selectFont = 選擇字型 -settings.chatFont.fontName = 字型名稱 -settings.chatFont.fontSize = 字型大小 settings.chatFont.lineSpacing = 行距 settings.chatFont.option.smallest = 最小 settings.chatFont.option.smaller = 較小 @@ -303,7 +299,6 @@ settings.chatFont.option.biggest = 最大 settings.chatFont.messageSpacing = 訊息間距 !-- Input / Userlist Font --! -settings.section.otherFonts = 輸入區/名單字型 settings.otherFonts.inputFont = 輸入: !-- Look & Feel --! diff --git a/src/chatty/util/Livestreamer.java b/src/chatty/util/Livestreamer.java index 9dbe97ea3..597712b11 100644 --- a/src/chatty/util/Livestreamer.java +++ b/src/chatty/util/Livestreamer.java @@ -141,7 +141,7 @@ private static String[] split(String input) { return result.toArray(new String[result.size()]); } - private static String filterToken(String input) { + public static String filterToken(String input) { return input.replaceAll("--twitch-oauth-token \\w+", "--twitch-oauth-token "); } diff --git a/src/chatty/util/Pair.java b/src/chatty/util/Pair.java new file mode 100644 index 000000000..8b404ff26 --- /dev/null +++ b/src/chatty/util/Pair.java @@ -0,0 +1,52 @@ + +package chatty.util; + +import java.util.Objects; + +/** + * A generic pair class. + * + * @author tduva + */ +public class Pair { + + public final K key; + public final V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return key+"="+value; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pair other = (Pair) obj; + if (!Objects.equals(this.key, other.key)) { + return false; + } + if (!Objects.equals(this.value, other.value)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 71 * hash + Objects.hashCode(this.key); + hash = 71 * hash + Objects.hashCode(this.value); + return hash; + } + +} diff --git a/src/chatty/util/RawMessageTest.java b/src/chatty/util/RawMessageTest.java index 918377403..99be6024f 100644 --- a/src/chatty/util/RawMessageTest.java +++ b/src/chatty/util/RawMessageTest.java @@ -103,6 +103,12 @@ public static String simulateIRC(String channel, String parameters, String local if (type.equals("userstate3")) { return "@badge-info=;badges=moderator/1,turbo/1;color=#0000FF;display-name=abctest;emote-sets=0,849934,83973,143367,491534,335887,548868,1103900,20493,24589,239630,294921,24590,927745,2066,382998,178198,581660,217115,383007,583703,931884,22564,296993,57382,229416,368683,32815,835644,151600,120882,362550,960567,718897,927794,14399,815183,26692,839754,274499,532545,88138,317512,409675,118862,202834,202833,125010,86101,61528,825428,464989,22619,602199,223325,59487,737364,434278,893036,161888,553071,755820,18537,727138,866404,739425,1149055,749689,157814,391280,280690,315506,16506,233592,125,8318,716938,16513,407685,92292,141445,995457,544922,442514,630940,819353,10390,133268,657554,637072,839829,850066,481432,827564,360610,581792,907429,952487,10412,14510,16560,90291,334007,1058978,643258,16566,69815,878778,936116,1050794,258232,189,63677,538805,946352,14527,209084,193,108739,1038538,32967,387266,22728,184523,743617,123085,22733,57549,32974,452809,669914,65749,32981,106708,162007,354515,606431,215,176341,135386,522463,561362,16604,317656,800977,1077454,735444,887020,254176,968939,798954,231,233,579809,1108217,20717,538853,1048829,776420,176371,98549,98548,247,162043,334079,612595,164102,12549,20742,174346,176392,14604,641287,4368,1065216,583961,276758,682265,80148,1016088,532752,217370,817426,170269,74019,700716,1030436,102701,919842,108844,391477,22833,923967,809274,426289,172346,411966,833844,1095977,833846,833847,125242,1116461,317,2369,88387,141633,485696,63816,694595,63819,833856,1016157,604506,1012059,690515,160088,260447,20831,657770,131425,817519,182630,219494,868714,18795,479596,18798,303466,555367,2416,860537,274812,455038,164219,309630,280959,434554,446843,70015,215421,14722,506245,588170,16774,280962,352643,283011,627073,303496,555397,151948,18834,154000,20883,756120,278928,870809,608660,864656,123295,629143,971152,487847,668072,711084,31143,721312,764327,1354168,590247,1022369,168370,534968,522676,737727,342450,59832,248255,1073580,2494,125377,500167,12741,14789,248267,455116,975300,57803,111053,22989,186829,16850,78291,14805,895451,1579470,444881,674258,795095,160222,1358281,674261,641513,518629,207328,498148,186853,1024484,352745,401898,651749,68076,4591,762362,242161,631291,412149,174587,551409,1004021,76282,705009,12796,174591,231934,92671,231932,743947,174592,12805,330240,14854,696844,598542,121354,379400,18958,659972,14864,621080,12818,369175,1045018,21016,223771,864788,168478,1192462,150044,12832,21024,21026,6694,211496,320040,829984,133682,21043,4661,14903,178741,674354,137785,639538,573,666167,1063470,4671,205372,68161,23105,203330,711243,430661,633418,123460,434752,43592,318028,43593,68172,14926,381515,768580,1014336,1055327,16976,593,178769,412245,475732,68185,453214,547408,12889,68186,158297,426589,12896,113248,82533,143975,1131124,17000,866917,12906,658016,21099,356974,113261,121453,615013,12909,21101,420459,354932,1065573,377459,445040,477823,1051240,633,316029,957042,785035,547465,719497,1106579,678542,10885,19078,21126,993927,33417,121480,107146,150153,883331,180877,1387162,547480,227986,291477,322197,348822,696985,1051267,510610,422557,637591,1018513,268955,959121,199329,66210,160417,86695,66219,414379,17072,135859,240306,74418,78519,111289,160441,789175,479929,150205,21187,19142,283340,1063641,420552,144082,211667,19155,125650,90836,172759,1043161,318172,101080,357084,199385,559826,13019,418521,664298,625387,778985,987885,983782,473836,416491,525031,572135,436981,66294,531199,94969,70392,19194,387839,672497,539381,811762,109315,355078,533261,11018,578306,541444,316171,389898,897794,2832,21266,1078018,693017,19220,242452,6937,576275,285466,21279,15140,19236,131878,391969,396064,836388,326440,1061693,330539,375607,670520,1225505,72501,107317,21301,76596,279345,717631,1592109,422704,394033,459568,502576,1037112,4920,475960,11073,643912,230215,557902,299843,660290,1213274,15180,342859,62287,478024,971614,971615,150352,162645,574289,471900,701265,207710,570197,594772,297819,969583,84837,666479,179050,125800,711523,66411,105323,119659,670560,895846,752487,818017,852835,971616,19311,164723,439159,592760,473971,621437,2935,1512303,64383,1174416,990090,82822,107398,70536,414607,162697,678785,918405,19343,97169,551832,631705,158611,600987,969626,17301,9111,621470,947093,557972,609173,797584,19357,359320,19359,19363,172967,181159,119719,228266,742304,84909,1020832,588728,771001,795582,68530,15284,93109,381875,512959,9147,242620,525256,60354,756687,721870,431041,60360,533441,695235,918471,701376,170953,1029059,125900,691143,275409,173012,13271,818128,989,971731,191452,68574,154589,13281,13283,19427,578541,529390,547809,414700,15339,418794,986082,236531,19442,365559,494576,125945,33786,226297,1019,605170,699381,353285,107525,132103,488450,912394,19464,629761,1096730,498700,812032,33805,13326,953374,1020958,142358,168982,33813,80916,824345,388124,11292,107548,99358,246812,928814,318502,601131,1004589,341030,1088564,103460,543776,986151,568353,15403,1317945,1072,1121312,742462,95290,140350,556085,115774,707637,517189,11334,945224,1096,353357,486479,105544,296014,80970,107594,29772,486474,939072,652379,101458,429137,13402,533586,91233,99425,756843,472166,857196,3170,986220,607340,21607,326755,947305,3178,1033316,597093,140399,148589,273524,474229,789631,3190,81015,138357,441457,324732,930935,521341,121982,687242,15489,922765,308354,1146007,803979,107659,951452,900248,70809,615568,593045,60573,601236,902288,545942,212124,935057,15520,197798,210085,521377,21671,423073,574625,801957,1004735,29873,320694,603312,21691,3260,13500,216254,304314,224451,482503,29889,806088,169156,955593,726211,1105112,1653968,824513,70863,64719,273620,15569,236753,5332,603358,529618,664784,1076427,19677,324826,920811,660707,1094906,17644,111853,21742,60654,101615,318709,3314,900345,728317,658685,17657,267518,124154,793840,992499,406779,285946,324859,337159,611594,7428,357633,636172,138506,292110,361743,292111,1012995,394507,1049885,152845,683291,640282,292112,105748,593180,392467,572702,732435,1170696,118046,386341,486695,70947,1109300,21799,1320,183593,539939,5420,423208,392503,527674,585020,3381,470322,916788,949559,17722,390463,404797,939315,9535,177469,568631,826700,17730,146753,154945,1000780,419136,318797,867652,17738,21834,5451,109898,1045828,21838,165200,107860,89431,537950,523600,56665,410970,136543,17760,415078,118112,17762,126307,13667,970093,177510,263520,240998,93545,316781,505196,1074555,318824,101740,767333,169331,1121636,218490,249210,476542,89467,163192,19837,853363,931184,77184,19842,1168788,408960,32137,19852,17808,109969,11666,21907,19860,880025,413087,15770,32154,1015189,32155,109978,783760,19868,294296,1439,21920,75169,568745,239009,408996,564650,105893,724396,748972,898469,21929,21930,421290,1454,1103264,626104,1443236,419253,546236,191924,300467,202169,1062314,392638,1111469,366010,585161,3527,19911,105928,224714,196047,550341,19920,251346,992733,427474,732639,204245,290258,7639,607697,343518,546260,816592,11744,95713,394726,984558,89571,988653,1007084,81381,886249,1121780,60903,212459,1048038,118250,163305,337385,1518,155122,564734,239096,988659,595447,1471976,65023,812554,607745,595459,3595,441866,7693,853504,247309,810524,13842,71187,874014,13844,17941,613918,5658,167452,847405,511525,912937,110116,265772,996897,796195,151090,20024,1046071,1134121,13881,419388,134719,448071,585290,169537,22089,396876,1023557,646722,1612,15951,679493,56915,265820,1136203,1628,998995,540245,501337,222816,595565,499297,679532,85609,292462,108138,751200,20076,872033,73324,22126,503401,20079,122480,575099,781951,22133,183925,452209,710281,16004,300672,18056,601728,102027,196239,239246,386696,378507,7823,214668,253580,1040000,396951,628378,67220,1474189,104095,1697,26274,122533,847525,16043,306863,18096,108209,904892,292534,22195,216756,935609,810677,882356,540338,1724,757431,18110,634550,65220,489155,59079,396993,937673,1095383,157384,868039,22222,67281,519895,425684,558803,454360,515800,1054415,32484,771823,32485,87780,241382,71401,278253,132841,18157,18159,77550,16112,579320,67317,124662,12025,820981,128765,868081,20221,20222,216828,57090,294662,458500,771848,14088,16138,20234,771846,411403,960256,915225,763677,874263,98076,212765,704298,5921,917292,943919,972591,442148,859950,20260,401187,229156,1144630,98089,171816,208681,16171,861984,1705783,171821,16179,223031,978747,151348,20280,114488,20283,427837,83772,493370,10048,298816,532290,991044,431956,786271,788312,667487,118615,186196,270162,311132,675671,67423,1568631,374628,685931,16226,1015656,669539,929636,218984,249704,567139,501610,216941,259948,137074,747386,438130,786301,966520,405361,83832,524158,757617,563060,778103,3966,6015,276356,190342,61317,884615,710529,298888,20366,444296,853891,153490,477079,81810,298897,122782,427942,73635,20387,589739,14245,130987,862119,954276,14253,157612,913315,530360,985023,120755,137137,10164,18357,358323,128950,333746,16313,534449,14266,360382,612274,14272,18369,20417,18370,20418,4036,724942,892874,57288,1099739,18381,18382,956352,536519,8144,853981,952286,22481,98261,169943,92121,757719,92124,536534,968656,4063,139234,438247,20452,14310,73702,1343475,22504,389103,14315,129005,380905,20461,794593,149485,14321,20469,385008,450547,813049,270322,12281,180223,75775,364539;mod=1;subscriber=0;user-type=mod :tmi.twitch.tv USERSTATE "+channel; } + if (type.equals("femote")) { + return "@badge-info=subscriber/22;badges=subscriber/18;color=#420000;display-name=Test;emotes=300580752:0-7/300580752_HF:9-19;flags=;id=abc;mod=0;room-id=0;subscriber=1;tmi-sent-ts=123;turbo=0;user-id=123;user-type= :test!test@test.tmi.twitch.tv PRIVMSG "+channel+" :cohhLick cohhLick_HF so tasty"; + } + if (type.equals("hl")) { + return "@badge-info=subscriber/19;badges=subscriber/12,premium/1;color=;display-name=Test;emotes=300737210:11-18/300737204:20-27;flags=;id=123;mod=0;msg-id=highlighted-message;room-id=123;subscriber=1;tmi-sent-ts=123;turbo=0;user-id=123;user-type= :test!test@test.tmi.twitch.tv PRIVMSG "+channel+" :hello chat itmejpM1 itmejpM3 , just testing this highlight popup to see how glitzy this post will get"; + } return null; } diff --git a/src/chatty/util/StreamHighlightHelper.java b/src/chatty/util/StreamHighlightHelper.java index 7183a7b29..698089b6a 100644 --- a/src/chatty/util/StreamHighlightHelper.java +++ b/src/chatty/util/StreamHighlightHelper.java @@ -9,6 +9,7 @@ import chatty.gui.Highlighter.HighlightItem; import chatty.util.api.StreamInfo; import chatty.util.api.TwitchApi; +import chatty.util.irc.MsgTags; import chatty.util.settings.Settings; import java.io.BufferedWriter; import java.io.IOException; @@ -56,7 +57,7 @@ public StreamHighlightHelper(Settings settings, TwitchApi api) { * @param line The content of the message * @return A response to either echo or send to the channel */ - public String modCommand(User user, String line) { + public String modCommand(User user, String line, MsgTags tags) { //--------- // Channel //--------- @@ -85,7 +86,7 @@ public String modCommand(User user, String line) { return null; } HighlightItem item = new Highlighter.HighlightItem(match); - if (!item.matches(HighlightItem.Type.REGULAR, line, user)) { + if (!item.matches(HighlightItem.Type.REGULAR, line, user, tags)) { return null; } //--------------------------------------- diff --git a/src/chatty/util/StringUtil.java b/src/chatty/util/StringUtil.java index d975ffe62..164274366 100644 --- a/src/chatty/util/StringUtil.java +++ b/src/chatty/util/StringUtil.java @@ -302,6 +302,94 @@ public static String concat(String sep, Object... args) { return b.toString(); } + public static List split(String input, char splitAt, int limit) { + return split(input, splitAt, '"', '\\', limit, 1); + } + + public static List split(String input, char splitAt, int limit, int remove) { + return split(input, splitAt, '"', '\\', limit, remove); + } + + /** + * Split the given input String by the {@code splitAt} character. Sections + * enclosed in the {@code quote} character and characters prefixed by the + * {@code escape} character aren't checked for the {@code splitAt} + * character. + *

    + * Whether quote/escape characters are removed from the result can be + * controlled by the {@code remove} value: + *

      + *
    • 0 - don't remove + *
    • 1 - remove from all parts, except result number {@code limit} (if + * {@code limit} > 0) + *
    • 2 - remove from all parts + *
    + * + * @param input The input to be split + * @param splitAt The split character + * @param quote The quote character + * @param escape The escape character, also used to escape the quote + * character and itself + * @param limit Maximum number of parts in the result (0 for high limit) + * @param remove 0 - don't remove quote/escape characters, 1 - remove from + * all parts (except result number "limit", if limit > 0), 2 - remove from + * all parts + * @return + */ + public static List split(String input, char splitAt, char quote, char escape, int limit, int remove) { + if (input == null) { + return null; + } + List result = new ArrayList<>(); + StringBuilder b = new StringBuilder(); + boolean quoted = false; + boolean escaped = false; + limit = Math.abs(limit); + if (limit == 0) { + limit = Integer.MAX_VALUE; + } + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + + // Add one escaped character + if (escaped) { + b.append(c); + escaped = false; + } + // Next character escaped + else if (c == escape) { + escaped = true; + if (remove == 0) { + b.append(c); + } + } + // Begin and end quoted section + else if (c == quote) { + quoted = !quoted; + if (remove == 0) { + b.append(c); + } + } + // Split character found, ignore if quoted or max count reached + else if (c == splitAt && !quoted && result.size()+1 < limit) { + result.add(b.toString()); + b = new StringBuilder(); + if (result.size()+1 == limit && remove < 2) { + // Add remaining text without parsing + result.add(input.substring(i+1)); + return result; + } + } + // Nothing special, just add character + else { + b.append(c); + } + } + // Add last + result.add(b.toString()); + return result; + } + public static final void main(String[] args) { System.out.println(shortenTo("abcdefghi", 8, 5)); System.out.println(concats("a", null, "b", null)); diff --git a/src/chatty/util/TwitchEmotesApi.java b/src/chatty/util/TwitchEmotesApi.java index 64dfe4f5a..ae73f25e0 100644 --- a/src/chatty/util/TwitchEmotesApi.java +++ b/src/chatty/util/TwitchEmotesApi.java @@ -18,6 +18,8 @@ import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -188,7 +190,7 @@ public void setTwitchApi(TwitchApi api) { public EmotesetInfo getInfoByEmote(Object unique, Consumer listener, Emoticon emote) { EmotesetInfo info; if (emote.type != Emoticon.Type.TWITCH - || emote.numericId == Emoticon.ID_UNDEFINED) { + || emote.stringId == null) { return null; } if (emote.hasGlobalEmoteset()) { @@ -200,11 +202,15 @@ public EmotesetInfo getInfoByEmote(Object unique, Consumer listene return info; } } + int numericId = getNumericEmoteId(emote.stringId); + if (numericId == -1) { + return null; + } info = byId.getOrQuerySingle(unique, result -> { if (listener != null) { - listener.accept(result.get(emote.numericId)); + listener.accept(result.get(numericId)); } - }, ASAP, emote.numericId); + }, ASAP, numericId); return info; } @@ -287,6 +293,10 @@ public Map requestBySets(Consumer streamRestrictions; public final String url; public final boolean literal; - public final int numericId; public final String stringId; public final String stringIdAlias; public final String urlX2; @@ -144,7 +143,6 @@ public static class Builder { private Set streamRestrictions; private Set infos; private int emoteset = SET_UNDEFINED; - private int numericId = ID_UNDEFINED; private String stringId = null; private String stringIdAlias = null; private String creator; @@ -192,11 +190,6 @@ public Builder setLiteral(boolean literal) { return this; } - public Builder setNumericId(int id) { - this.numericId = id; - return this; - } - public Builder setStringId(String id) { this.stringId = id; return this; @@ -251,8 +244,8 @@ public Emoticon build() { */ public String getEmoteUrl(int factor) { if (type == Type.TWITCH) { - if (numericId != ID_UNDEFINED) { - return getTwitchEmoteUrlById(numericId, factor); + if (stringId != null) { + return getTwitchEmoteUrlById(stringId, factor); } } else if (type == Type.BTTV && stringId != null) { return getBttvEmoteUrl(stringId, factor); @@ -264,7 +257,7 @@ public String getEmoteUrl(int factor) { return null; } - public static String getTwitchEmoteUrlById(int id, int factor) { + public static String getTwitchEmoteUrlById(String id, int factor) { return "https://static-cdn.jtvnw.net/emoticons/v1/"+id+"/"+factor+".0"; } @@ -335,7 +328,6 @@ protected Emoticon(Builder builder) { this.stream = builder.stream; this.emotesetInfo = builder.emotesetInfo; this.literal = builder.literal; - this.numericId = builder.numericId; this.stringId = builder.stringId; this.stringIdAlias = builder.stringIdAlias; this.creator = builder.creator; @@ -594,7 +586,7 @@ public EmoticonImage getIcon(EmoticonUser user) { */ private String getCachedSizeId() { if (type == Type.TWITCH) { - return type+"."+numericId; + return type+"."+stringId; } if (type == Type.BTTV) { return type+"."+stringId; @@ -619,7 +611,7 @@ private Dimension getCachedSize() { * @param h The height */ private void setCachedSize(int w, int h) { - if ((type == Type.TWITCH && numericId != ID_UNDEFINED) + if ((type == Type.TWITCH && stringId != null) || (type == Type.BTTV && stringId != null)) { EmoticonSizeCache.setSize(getCachedSizeId(), w, h); } diff --git a/src/chatty/util/api/EmoticonFavorites.java b/src/chatty/util/api/EmoticonFavorites.java index 862b933a8..a6f473965 100644 --- a/src/chatty/util/api/EmoticonFavorites.java +++ b/src/chatty/util/api/EmoticonFavorites.java @@ -153,7 +153,7 @@ private List favoriteToList(Favorite f) { * @param twitchEmotesById All Twitch emotes currently loaded * @param more */ - public void find(Map twitchEmotesById, + public void find(Map twitchEmotesById, Set... more) { if (favoritesNotFound.isEmpty()) { return; diff --git a/src/chatty/util/api/EmoticonManager.java b/src/chatty/util/api/EmoticonManager.java index f005a17e3..ee7f60733 100644 --- a/src/chatty/util/api/EmoticonManager.java +++ b/src/chatty/util/api/EmoticonManager.java @@ -146,7 +146,7 @@ private static Emoticon parseEmoticon(JSONObject emote, int emoteSet) { emoteSet = ((Number)emote.get("emoticon_set")).intValue(); } b.setEmoteset(emoteSet); - b.setNumericId(id); + b.setStringId(String.valueOf(id)); return b.build(); } catch (NullPointerException | ClassCastException ex) { return null; diff --git a/src/chatty/util/api/Emoticons.java b/src/chatty/util/api/Emoticons.java index aab7d76f9..bf91baf28 100644 --- a/src/chatty/util/api/Emoticons.java +++ b/src/chatty/util/api/Emoticons.java @@ -93,7 +93,7 @@ public class Emoticons { /** * Custom Emotes for lookup by id. All of these are also in customEmotes. */ - private final Map customEmotesById = new HashMap<>(); + private final Map customEmotesById = new HashMap<>(); /** * Emoji should be sorted by length, so that longer Emoji (which can be @@ -122,7 +122,7 @@ public int compare(Emoticon s1, Emoticon s2) { /** * All loaded Twitch Emotes, by their Twitch Emote Id. */ - private final HashMap twitchEmotesById = new HashMap<>(); + private final HashMap twitchEmotesById = new HashMap<>(); /** * Emoticons restricted to a channel (FrankerFaceZ/BTTV). @@ -287,8 +287,8 @@ public void addEmoticons(Set newEmoticons) { } } // By Twitch Emote ID - if (emote.type == Emoticon.Type.TWITCH && emote.numericId != Emoticon.ID_UNDEFINED) { - twitchEmotesById.put(emote.numericId, emote); + if (emote.type == Emoticon.Type.TWITCH && emote.stringId != null) { + twitchEmotesById.put(emote.stringId, emote); } } LOGGER.info("Added "+newEmoticons.size()+" emotes." @@ -335,7 +335,7 @@ private void addEmote(Collection collection, Emoticon emote) { * @param emote */ public void addTempEmoticon(Emoticon emote) { - twitchEmotesById.put(emote.numericId, emote); + twitchEmotesById.put(emote.stringId, emote); } private static int clearOldEmoticonImages(Collection emotes) { @@ -350,7 +350,7 @@ public Set getCustomEmotes() { return customEmotes; } - public Emoticon getCustomEmoteById(int id) { + public Emoticon getCustomEmoteById(String id) { return customEmotesById.get(id); } @@ -393,7 +393,7 @@ public Set getOtherGlobalEmotes() { return otherGlobalEmotes; } - public HashMap getEmoticonsById() { + public HashMap getEmoticonsById() { return twitchEmotesById; } @@ -825,7 +825,7 @@ private boolean loadCustomEmote(String line) { boolean literal = true; String url = null; int emoteset = Emoticon.SET_UNDEFINED; - int id = Emoticon.ID_UNDEFINED; + String id = null; // Use Dimension because it's easier to check if one value is set Dimension size = null; String streamRestriction = null; @@ -838,11 +838,7 @@ private boolean loadCustomEmote(String line) { literal = false; code = item.substring("re:".length()); } else if (item.startsWith("id:")) { - try { - id = Integer.parseInt(item.substring("id:".length())); - } catch (NumberFormatException ex) { - // Just don't set the id - } + id = item.substring("id:".length()); } else if (item.startsWith("set:")) { try { emoteset = Integer.parseInt(item.substring("set:".length())); @@ -887,14 +883,14 @@ private boolean loadCustomEmote(String line) { if (code != null && url != null) { Emoticon.Builder b = new Emoticon.Builder(Emoticon.Type.CUSTOM, code, url); b.setLiteral(literal).setEmoteset(emoteset); - b.setNumericId(id); + b.setStringId(id); if (size != null) { b.setSize(size.width, size.height); } b.addStreamRestriction(streamRestriction); Emoticon emote = b.build(); customEmotes.add(emote); - if (id != Emoticon.ID_UNDEFINED) { + if (id != null) { customEmotesById.put(id, emote); } return true; @@ -921,8 +917,8 @@ public String getCustomEmotesInfo() { if (emote.emoteSet != Emoticon.SET_UNDEFINED) { info.add("set:"+emote.emoteSet); } - if (emote.numericId != Emoticon.ID_UNDEFINED) { - info.add("id:"+emote.numericId); + if (emote.stringId != null) { + info.add("id:"+emote.stringId); } b.append("\""+emote.code+"\" "); if (info.size() > 0) { @@ -961,7 +957,7 @@ public static TagEmotes parseEmotesTag(String tag) { * expected, this emote or at least any further ranges for * this emote are ignored. */ - int id = Integer.parseInt(emote.substring(0, idEnd)); + String id = emote.substring(0, idEnd); String[] emoteRanges = emote.substring(idEnd+1).split(","); // Save all ranges for this emote @@ -1042,11 +1038,11 @@ public int hashCode() { */ public static class TagEmote { - public final int id; + public final String id; public final int end; - public TagEmote(int start, int end) { - this.id = start; + public TagEmote(String id, int end) { + this.id = id; this.end = end; } diff --git a/src/chatty/util/api/usericons/Usericon.java b/src/chatty/util/api/usericons/Usericon.java index 12cb5cfc5..bde211408 100644 --- a/src/chatty/util/api/usericons/Usericon.java +++ b/src/chatty/util/api/usericons/Usericon.java @@ -37,7 +37,7 @@ public class Usericon implements Comparable { private static final Set statusDef = new HashSet<>(Arrays.asList( "$mod", "$sub", "$admin", "$staff", "$turbo", "$broadcaster", "$bot", - "$globalmod", "$anymod")); + "$globalmod", "$anymod", "$vip")); /** * The type determines whether it should replace any of the default icons @@ -61,6 +61,7 @@ public enum Type { BITS(11, "Bits", "BIT", "$", "bits", null), OTHER(12, "Other", "OTH", "'", null, null), VIP(13, "VIP", "VIP", "!", "vip", null), + HL(14, "Highlighted by channel points", "HL", "'", null, null), UNDEFINED(-1, "Undefined", "UDF", null, null, null); public Color color; diff --git a/src/chatty/util/api/usericons/UsericonManager.java b/src/chatty/util/api/usericons/UsericonManager.java index b85d1f225..e8e12b312 100644 --- a/src/chatty/util/api/usericons/UsericonManager.java +++ b/src/chatty/util/api/usericons/UsericonManager.java @@ -98,6 +98,7 @@ private synchronized void addFallbackIcons() { addFallbackIcon(Usericon.Type.TURBO, "icon_turbo.png"); addFallbackIcon(Usericon.Type.GLOBAL_MOD, "icon_globalmod.png"); addFallbackIcon(Usericon.Type.BOT, "icon_bot.png"); + addFallbackIcon(Usericon.Type.HL, "icon_hl.png"); // addFallbackIcon(Usericon.Type.RESUB, "icon_sub.png"); // addFallbackIcon(Usericon.Type.NEWSUB, "icon_sub.png"); // List test = new ArrayList<>(); @@ -135,7 +136,8 @@ public synchronized Set getTwitchBadgeTypes() { return result; } - public synchronized List getBadges(Map badgesDef, User user, boolean botBadgeEnabled) { + public synchronized List getBadges(Map badgesDef, + User user, boolean botBadgeEnabled, boolean pointsHl) { List icons = getTwitchBadges(badgesDef, user); if (user.isBot() && botBadgeEnabled) { Usericon icon = getIcon(Usericon.Type.BOT, null, null, user); @@ -145,6 +147,12 @@ public synchronized List getBadges(Map badgesDef, User } addThirdPartyIcons(icons, user); addAddonIcons(icons, user); + if (pointsHl) { + Usericon icon = getIcon(Usericon.Type.HL, null, null, user); + if (icon != null) { + icons.add(0, icon); + } + } return icons; } diff --git a/src/chatty/util/ffz/FrankerFaceZParsing.java b/src/chatty/util/ffz/FrankerFaceZParsing.java index b4e1e2d26..10ca293f6 100644 --- a/src/chatty/util/ffz/FrankerFaceZParsing.java +++ b/src/chatty/util/ffz/FrankerFaceZParsing.java @@ -217,7 +217,7 @@ public static Emoticon parseEmote(JSONObject emote, String streamRestriction, b.setX2Url(url2); b.setSize(width, height); b.setCreator(creator); - b.setNumericId(id); + b.setStringId(String.valueOf(id)); b.addStreamRestriction(streamRestriction); b.addInfo(info); b.setSubType(subType); diff --git a/src/chatty/util/ffz/WebsocketManager.java b/src/chatty/util/ffz/WebsocketManager.java index 5f8cbcf81..76894194a 100644 --- a/src/chatty/util/ffz/WebsocketManager.java +++ b/src/chatty/util/ffz/WebsocketManager.java @@ -10,6 +10,7 @@ import chatty.util.api.Emoticon; import chatty.util.api.EmoticonUpdate; import chatty.util.settings.Settings; +import java.net.InetAddress; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -116,11 +117,31 @@ public void handleConnect() { } private static String[] getServers() { - return new String[] { - "catbag.frankerfacez.com", - "andknuckles.frankerfacez.com", - "tuturu.frankerfacez.com" - }; + String main = "socket.frankerfacez.com"; + try { + InetAddress[] servers = InetAddress.getAllByName(main); + String[] result = new String[servers.length]; + for (int i = 0; i < servers.length; i++) { + String host = servers[i].getCanonicalHostName(); + /** + * Check that it's not an IP (not ideal, but should be ok), and + * fall back to the main host if needed. It needs the hostname + * for SSL host verification (which can be turned off, but I'd + * rather try it like this for now). + */ + if (host.contains("frankerfacez.com")) { + result[i] = host; + } else { + result[i] = main; + } + } + return result; + } catch (Exception ex) { + LOGGER.warning("Failed to resolve socket.frankerfacez.com (using host directly)"); + return new String[]{ + main + }; + } } /** diff --git a/src/chatty/util/irc/IrcMsgTags.java b/src/chatty/util/irc/IrcMsgTags.java new file mode 100644 index 000000000..f7551e380 --- /dev/null +++ b/src/chatty/util/irc/IrcMsgTags.java @@ -0,0 +1,265 @@ + +package chatty.util.irc; + +import chatty.Helper; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Decodes and encodes IRCv3 message tags and provides convenience methods to + * access them. + * + * The data of IrcMsgTags objects is read-only. Use the factory methods to + * create new instances. + * + * @author tduva + */ +public class IrcMsgTags { + + private static final Map EMPTY_TAGS = new HashMap<>(); + + /** + * Empty IrcMsgTags object. Can be used if no tags should be provided, to still + * have a IrcMsgTags object. + */ + public static final IrcMsgTags EMPTY = new IrcMsgTags(null); + + private final Map tags; + + protected IrcMsgTags(Map tags) { + if (tags == null) { + this.tags = EMPTY_TAGS; + } else { + this.tags = tags; + } + } + + /** + * Returns true if the given key is contained in these tags. + * + * @param key The key to look for + * @return true if the key is in the tags, false otherwise + */ + public boolean containsKey(String key) { + return tags.containsKey(key); + } + + /** + * Returns true if there are no key/value pairs. + * + * @return true if empty + */ + public boolean isEmpty() { + return tags.isEmpty(); + } + + /** + * Returns true if the given key in the tags is equal to "1", false + * otherwise. + * + * @param key The key to check + * @return True if equal to 1, false otherwise + */ + public boolean isTrue(String key) { + return "1".equals(tags.get(key)); + } + + public boolean isValue(String key, String value) { + return value.equals(tags.get(key)); + } + + public boolean isValueOf(String key, String... values) { + for (String value : values) { + if (value.equals(tags.get(key))) { + return true; + } + } + return false; + } + + public boolean isEmpty(String key) { + return !tags.containsKey(key) || tags.get(key).isEmpty(); + } + + /** + * Returns the String associated with key, or null if the key doesn't exist. + * + * @param key The key to look up in the tags + * @return String associated with this key, or null + */ + public String get(String key) { + return get(key, null); + } + + /** + * Returns the String associated with key, or the defaultValue if the key + * doesn't exist. + * + * @param key The key to look up in the tags + * @param defaultValue The default value to return if key isn't in tags + * @return String associated with this key, or defaultValue + */ + public String get(String key, String defaultValue) { + if (tags.containsKey(key)) { + return tags.get(key); + } + return defaultValue; + } + + /** + * Returns the integer associated with the given key, or the defaultValue if + * no integer was found for that key. + * + * @param key The key to retrieve the value for + * @param defaultValue The default value to return if the given key doesn't + * point to an integer value + * @return The integer associated with the key, or defaultValue + */ + public int getInteger(String key, int defaultValue) { + if (tags.get(key) != null) { + try { + return Integer.parseInt(tags.get(key)); + } catch (NumberFormatException ex) { + // Just go to default value + } + } + return defaultValue; + } + + public boolean hasInteger(String key) { + try { + Integer.parseInt(tags.get(key)); + return true; + } catch (Exception ex) { + return false; + } + } + + /** + * Returns the long associated with the given key, or the defaultValue if no + * long was found for that key. + * + * @param key The key to retrieve the value for + * @param defaultValue The default value to return if the given key doesn't + * point to a long value + * @return The long associated with the key, or defaultValue + */ + public long getLong(String key, long defaultValue) { + if (tags.get(key) != null) { + try { + return Long.parseLong(tags.get(key)); + } catch (NumberFormatException ex) { + // Just go to default value + } + } + return defaultValue; + } + + /** + * Build a IRCv3 tags String for this tags object (no leading @). + * + * @return The tags string (may be empty if this tags object is empty) + */ + public String toTagsString() { + StringBuilder b = new StringBuilder(); + Iterator it = tags.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + String value = tags.get(key); + if (isValidKey(key)) { + b.append(key); + if (isValidValue(value)) { + b.append("=").append(escapeValue(value)); + } + if (it.hasNext()) { + b.append(";"); + } + } + } + return b.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IrcMsgTags other = (IrcMsgTags) obj; + if (!Objects.equals(this.tags, other.tags)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + Objects.hashCode(this.tags); + return hash; + } + + private static final Pattern KEY_PATTERN = Pattern.compile("[a-z0-9-]+", Pattern.CASE_INSENSITIVE); + + private static boolean isValidKey(String key) { + return KEY_PATTERN.matcher(key).matches(); + } + + private static boolean isValidValue(String value) { + return value != null; + } + + private static String escapeValue(String value) { + return Helper.tagsvalue_encode(value); + } + + @Override + public String toString() { + return tags.toString(); + } + + //================ + // Factory Methods + //================ + + public static Map createTags(String... args) { + Map tags = new HashMap<>(); + Iterator it = Arrays.asList(args).iterator(); + while (it.hasNext()) { + String key = it.next(); + if (it.hasNext()) { + tags.put(key, it.next()); + } else { + tags.put(key, null); + } + } + return tags; + } + + public static Map parseTags(String data) { + if (data == null) { + return null; + } + String[] tags = data.split(";"); + if (tags.length > 0) { + Map result = new HashMap<>(); + for (String tag : tags) { + String[] keyValue = tag.split("=",2); + if (keyValue.length == 2) { + result.put(keyValue[0], Helper.tagsvalue_decode(keyValue[1])); + } else if (!keyValue[0].isEmpty()) { + result.put(keyValue[0], null); + } + } + return result; + } + return null; + } + +} diff --git a/src/chatty/util/irc/MsgTags.java b/src/chatty/util/irc/MsgTags.java index e712f9bd0..bf158fba3 100644 --- a/src/chatty/util/irc/MsgTags.java +++ b/src/chatty/util/irc/MsgTags.java @@ -1,227 +1,35 @@ package chatty.util.irc; -import chatty.Helper; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import java.util.Objects; -import java.util.regex.Pattern; /** - * Decodes and encodes IRCv3 message tags and provides convenience methods to - * access them. - * - * The data of MsgTags objects is read-only. Use the factory methods to create - * new instances. + * Implementation of IRCv3 tags with Twitch-specific methods. * * @author tduva */ -public class MsgTags { - - private static final Map EMPTY_TAGS = new HashMap<>(); +public class MsgTags extends IrcMsgTags { - /** - * Empty MsgTags object. Can be used if no tags should be provided, to still - * have a MsgTags object. - */ public static final MsgTags EMPTY = new MsgTags(null); - private final Map tags; - - private MsgTags(Map tags) { - if (tags == null) { - this.tags = EMPTY_TAGS; - } else { - this.tags = tags; - } - } - - /** - * Returns true if the given key is contained in these tags. - * - * @param key The key to look for - * @return true if the key is in the tags, false otherwise - */ - public boolean containsKey(String key) { - return tags.containsKey(key); - } - - /** - * Returns true if there are no key/value pairs. - * - * @return true if empty - */ - public boolean isEmpty() { - return tags.isEmpty(); - } - - /** - * Returns true if the given key in the tags is equal to "1", false - * otherwise. - * - * @param key The key to check - * @return True if equal to 1, false otherwise - */ - public boolean isTrue(String key) { - return "1".equals(tags.get(key)); - } - - public boolean isValue(String key, String value) { - return value.equals(tags.get(key)); - } - - public boolean isValueOf(String key, String... values) { - for (String value : values) { - if (value.equals(tags.get(key))) { - return true; - } - } - return false; - } - - public boolean isEmpty(String key) { - return !tags.containsKey(key) || tags.get(key).isEmpty(); - } - - /** - * Returns the String associated with key, or null if the key doesn't exist. - * - * @param key The key to look up in the tags - * @return String associated with this key, or null - */ - public String get(String key) { - return get(key, null); - } - - /** - * Returns the String associated with key, or the defaultValue if the key - * doesn't exist. - * - * @param key The key to look up in the tags - * @param defaultValue The default value to return if key isn't in tags - * @return String associated with this key, or defaultValue - */ - public String get(String key, String defaultValue) { - if (tags.containsKey(key)) { - return tags.get(key); - } - return defaultValue; - } - - /** - * Returns the integer associated with the given key, or the defaultValue if - * no integer was found for that key. - * - * @param key The key to retrieve the value for - * @param defaultValue The default value to return if the given key doesn't - * point to an integer value - * @return The integer associated with the key, or defaultValue - */ - public int getInteger(String key, int defaultValue) { - if (tags.get(key) != null) { - try { - return Integer.parseInt(tags.get(key)); - } catch (NumberFormatException ex) { - // Just go to default value - } - } - return defaultValue; - } - - public boolean hasInteger(String key) { - try { - Integer.parseInt(tags.get(key)); - return true; - } catch (Exception ex) { - return false; - } + public MsgTags(Map tags) { + super(tags); } - /** - * Returns the long associated with the given key, or the defaultValue if no - * long was found for that key. - * - * @param key The key to retrieve the value for - * @param defaultValue The default value to return if the given key doesn't - * point to a long value - * @return The long associated with the key, or defaultValue - */ - public long getLong(String key, long defaultValue) { - if (tags.get(key) != null) { - try { - return Long.parseLong(tags.get(key)); - } catch (NumberFormatException ex) { - // Just go to default value - } - } - return defaultValue; - } - - /** - * Build a IRCv3 tags String for this tags object (no leading @). - * - * @return The tags string (may be empty if this tags object is empty) - */ - public String toTagsString() { - StringBuilder b = new StringBuilder(); - Iterator it = tags.keySet().iterator(); - while (it.hasNext()) { - String key = it.next(); - String value = tags.get(key); - if (isValidKey(key)) { - b.append(key); - if (isValidValue(value)) { - b.append("=").append(escapeValue(value)); - } - if (it.hasNext()) { - b.append(";"); - } - } - } - return b.toString(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final MsgTags other = (MsgTags) obj; - if (!Objects.equals(this.tags, other.tags)) { - return false; - } - return true; + public String getId() { + return get("id"); } - @Override - public int hashCode() { - int hash = 5; - hash = 53 * hash + Objects.hashCode(this.tags); - return hash; + public int getBits() { + return getInteger("bits", 0); } - private static final Pattern KEY_PATTERN = Pattern.compile("[a-z0-9-]+", Pattern.CASE_INSENSITIVE); - - private static boolean isValidKey(String key) { - return KEY_PATTERN.matcher(key).matches(); + public String getRawEmotes() { + return get("emotes"); } - private static boolean isValidValue(String value) { - return value != null; - } - - private static String escapeValue(String value) { - return Helper.tagsvalue_encode(value); - } - - @Override - public String toString() { - return tags.toString(); + public boolean isHighlightedMessage() { + return isValue("msg-id", "highlighted-message"); } //================ @@ -229,10 +37,10 @@ public String toString() { //================ /** - * Parse the given IRCv3 tags String (no leading @) into a MsgTags object. + * Parse the given IRCv3 tags String (no leading @) into a IrcMsgTags object. * * @param tags The tags String - * @return MsgTags object, empty if tags was null + * @return IrcMsgTags object, empty if tags was null */ public static MsgTags parse(String tags) { Map parsedTags = parseTags(tags); @@ -243,43 +51,13 @@ public static MsgTags parse(String tags) { } /** - * Create a new MsgTags object with the given key/value pairs. + * Create a new IrcMsgTags object with the given key/value pairs. * * @param args Alternating key/value pairs - * @return MsgTags object + * @return IrcMsgTags object */ public static MsgTags create(String... args) { - Map tags = new HashMap<>(); - Iterator it = Arrays.asList(args).iterator(); - while (it.hasNext()) { - String key = it.next(); - if (it.hasNext()) { - tags.put(key, it.next()); - } else { - tags.put(key, null); - } - } - return new MsgTags(tags); - } - - private static Map parseTags(String data) { - if (data == null) { - return null; - } - String[] tags = data.split(";"); - if (tags.length > 0) { - Map result = new HashMap<>(); - for (String tag : tags) { - String[] keyValue = tag.split("=",2); - if (keyValue.length == 2) { - result.put(keyValue[0], Helper.tagsvalue_decode(keyValue[1])); - } else if (!keyValue[0].isEmpty()) { - result.put(keyValue[0], null); - } - } - return result; - } - return null; + return new MsgTags(createTags(args)); } } diff --git a/test/chatty/HelperTest.java b/test/chatty/HelperTest.java index 30e865690..6639b92a4 100644 --- a/test/chatty/HelperTest.java +++ b/test/chatty/HelperTest.java @@ -2,6 +2,7 @@ package chatty; import chatty.util.StringUtil; +import java.util.Arrays; import java.util.regex.Matcher; import org.junit.Test; import static org.junit.Assert.*; @@ -198,4 +199,31 @@ public void removeEmojiVariationSelectorTest() { assertEquals(Helper.removeEmojiVariationSelector("\uFE0F\uFE0E"), ""); assertEquals(Helper.removeEmojiVariationSelector("❤️ ❤ ❤︎"), "❤ ❤ ❤"); } + + @Test + public void getChainedCommandsTest() { + chainedTest("", new String[]{}); + chainedTest(null, new String[]{}); + chainedTest("a", new String[]{"a"}); + chainedTest("a | b", new String[]{"a", "b"}); + chainedTest("a|b", new String[]{"a", "b"}); + chainedTest("a || b", new String[]{"a | b"}); + chainedTest("a||b", new String[]{"a|b"}); + chainedTest("a|||b", new String[]{"a||b"}); + chainedTest("|b", new String[]{"b"}); + chainedTest("a|b |c", new String[]{"a", "b", "c"}); + chainedTest("||a|| | b | c", new String[]{"|a|", "b", "c"}); + chainedTest("||||||", new String[]{"|||||"}); + chainedTest("|||||| |", new String[]{"|||||"}); + chainedTest("|||||| | ||", new String[]{"|||||", "|"}); + chainedTest("| | | | | |", new String[]{}); + chainedTest("a | /chain b", new String[]{"a", "/chain b"}); + chainedTest("a |c \\|d |1||", new String[]{"a", "c \\", "d", "1|"}); + chainedTest("b | a ", new String[]{"b", "a"}); + } + + private static void chainedTest(String input, String[] result) { + assertArrayEquals(Helper.getChainedCommands(input).toArray(), result); + } + } diff --git a/test/chatty/gui/HighlighterTest.java b/test/chatty/gui/HighlighterTest.java index c074f5c20..8dd27f86f 100644 --- a/test/chatty/gui/HighlighterTest.java +++ b/test/chatty/gui/HighlighterTest.java @@ -4,12 +4,13 @@ import chatty.Addressbook; import chatty.Room; import chatty.User; +import chatty.gui.Highlighter.HighlightItem.Type; +import chatty.util.irc.MsgTags; import chatty.util.settings.Settings; import java.awt.Color; import java.util.Arrays; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; +import java.util.HashMap; +import java.util.Map; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; @@ -38,7 +39,18 @@ public static void setUpClass() { ab = new Addressbook(null, null, settings); user.setAddressbook(ab); user2.setAddressbook(ab); - ab.add("testUser", "testCat"); + user3.setAddressbook(ab); + user4.setAddressbook(ab); + ab.add("testUser", "testCat,testCat2"); + ab.add("testUser3", "testCat2"); + ab.add("testUser2", "testCat3"); + Map badges = new HashMap<>(); + badges.put("vip", "1"); + badges.put("subscriber", "24"); + user.setTwitchBadges(badges); + Map badges2 = new HashMap<>(); + badges2.put("subscriber", "12"); + user2.setTwitchBadges(badges2); } private void update(String... items) { @@ -60,6 +72,10 @@ public void test() { assertTrue(highlighter.check(user, "test message")); assertFalse(highlighter.check(user, "abc")); + update(" test"); + assertTrue(highlighter.check(user, "test message")); + assertFalse(highlighter.check(user, "abc")); + update(""); assertFalse(highlighter.check(user, "test")); assertFalse(highlighter.check(user3, "")); @@ -69,6 +85,18 @@ public void test() { assertTrue(highlighter.check(user, "Mäh")); assertTrue(highlighter.check(user, "MÄH")); + update(" mäh "); + assertTrue(highlighter.check(user, "mäh")); + + update("\""); + assertTrue(highlighter.check(user, "\"")); + assertTrue(highlighter.check(user, "blah\" blubb")); + assertFalse(highlighter.check(user, "mäh")); + + update("abc\\blah"); + assertTrue(highlighter.check(user, "abc\\blah")); + assertFalse(highlighter.check(user, "mäh")); + // cs: update("cs:Test"); assertTrue(highlighter.check(user, " Test ")); @@ -91,6 +119,10 @@ public void test() { assertTrue(highlighter.check(user, "!bett")); assertTrue(highlighter.check(user3, "!bet abc")); + update("start:\\abc"); + assertTrue(highlighter.check(user, "\\abc")); + assertFalse(highlighter.check(user, "mäh")); + // w: update("w:Test"); assertTrue(highlighter.check(user, "test message")); @@ -115,6 +147,14 @@ public void test() { assertTrue(highlighter.check(user, "hmm dum dum dumdidum")); assertFalse(highlighter.check(user, "Dum")); + update("reg:\"abc\""); + assertTrue(highlighter.check(user, "test \"abc\" test")); + assertFalse(highlighter.check(user, "test abc test")); + + update("reg:\\\\"); + assertTrue(highlighter.check(user, "test\\test")); + assertFalse(highlighter.check(user, "test abc test")); + update("re*:dumdi|dum"); assertTrue(highlighter.check(user, "dumdi")); assertTrue(highlighter.check(user, "dum")); @@ -205,10 +245,92 @@ public void test() { ab.add("testUser2", "testCat"); assertTrue(highlighter.check(user2, "test")); + update("cat:testCat chan:testChannel"); + assertTrue(highlighter.check(user, "test")); + + update("cat:testCat,"); + assertTrue(highlighter.check(user, "test")); + assertFalse(highlighter.check(user4, "test")); + + update("cat:testCat"); + assertFalse(highlighter.check(null, "test")); + + update("cat:testCat2"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("cat:testCat cat:testCat2"); + assertTrue(highlighter.check(user, "test")); + assertFalse(highlighter.check(user4, "test")); + + update("cat:testCat,testCat2"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("!cat:abc"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("!cat:testCat"); + assertFalse(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("!cat:testCat,testCat2"); + assertFalse(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("!cat:testCat !cat:testCat2"); + assertFalse(highlighter.check(user, "test")); + assertFalse(highlighter.check(user4, "test")); + + update("!cat:testCat,testCat2,testCat3"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + + update("!cat:testCat2"); + assertFalse(highlighter.check(user, "test")); + assertFalse(highlighter.check(user4, "test")); + + update("!cat:testCat2,testCat3"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + assertTrue(highlighter.check(user3, "test")); + + update("!cat:testCat2 !cat:testCat3"); + assertFalse(highlighter.check(user, "test")); + assertFalse(highlighter.check(user4, "test")); + assertFalse(highlighter.check(user3, "test")); + + update("!cat:testCat !cat:testCat3"); + assertFalse(highlighter.check(user, "test")); + assertTrue(highlighter.check(user4, "test")); + assertFalse(highlighter.check(user3, "test")); + + update("chan:testChannel"); + assertTrue(highlighter.check(user, "test")); + assertFalse(highlighter.check(user3, "test")); + + update("chan:testChannel,testChannel2"); + assertTrue(highlighter.check(user, "test")); + assertTrue(highlighter.check(user3, "test")); + + update("!chan:testChannel,testChannel2"); + assertFalse(highlighter.check(user, "test")); + assertFalse(highlighter.check(user3, "test")); + update("!chan:testChannel2 test"); assertTrue(highlighter.check(user, "test")); assertFalse(highlighter.check(user, "mäh")); assertFalse(highlighter.check(user3, "test")); + + // user: + update("user:abc"); + assertFalse(highlighter.check(user, "mäh")); + update("user:testUser"); + assertTrue(highlighter.check(user, "mäh")); + assertFalse(highlighter.check(null, "mäh")); + update("config:info user:testUser"); + assertFalse(highlighter.check(Type.INFO, "abc", null, ab, null, MsgTags.EMPTY)); // reuser: update("reuser:test.*"); @@ -225,6 +347,38 @@ public void test() { assertFalse(highlighter.check(user, "whatever")); assertTrue(highlighter.check(user2, "whatever")); assertFalse(highlighter.check(user4, "whatever")); + assertFalse(highlighter.check(null, "whatever")); + + // Badge + update("config:b|vip"); + assertTrue(highlighter.check(user, "whatever")); + assertFalse(highlighter.check(user2, "whatever")); + + update("config:b|subscriber/12"); + assertFalse(highlighter.check(user, "whatever")); + assertTrue(highlighter.check(user2, "whatever")); + + update("config:b|subscriber"); + assertTrue(highlighter.check(user, "whatever")); + assertTrue(highlighter.check(user2, "whatever")); + + update("config:b|subscriber/12 color:red", "config:b|vip color:blue"); + assertTrue(highlighter.check(user, "whatever")); + assertEquals(highlighter.getLastMatchColor(), Color.BLUE); + assertTrue(highlighter.check(user2, "whatever")); + assertEquals(highlighter.getLastMatchColor(), Color.RED); + + update("config:b|subscriber/12,b|vip"); + assertTrue(highlighter.check(user, "whatever")); + assertTrue(highlighter.check(user2, "whatever")); + + update("config:b|subscriber/12 config:b|vip"); + assertFalse(highlighter.check(user, "whatever")); + assertFalse(highlighter.check(user2, "whatever")); + + update("config:b|subscriber/24 config:b|vip"); + assertTrue(highlighter.check(user, "whatever")); + assertFalse(highlighter.check(user2, "whatever")); // Color update("color:red testi", "test"); @@ -427,6 +581,9 @@ public void testStatusReq() { assertFalse(highlighter.check(staff, "")); assertFalse(highlighter.check(subscriber, "")); + // Status with no user + assertFalse(highlighter.check(null, "abc")); + // Highlighter shouldn't take empty items update(""); assertFalse(highlighter.check(broadcaster, "test")); @@ -450,9 +607,27 @@ public void testStatusReq() { Highlighter.HighlightItem item = new Highlighter.HighlightItem(""); assertTrue(item.matchesAny("", null)); assertTrue(item.matchesAny("abc", null)); - assertTrue(item.matches(Highlighter.HighlightItem.Type.REGULAR, "", broadcaster)); - assertTrue(item.matches(Highlighter.HighlightItem.Type.REGULAR, "", normal)); - assertFalse(item.matches(Highlighter.HighlightItem.Type.INFO, "", normal)); + assertTrue(item.matches(Highlighter.HighlightItem.Type.REGULAR, "", broadcaster, MsgTags.EMPTY)); + assertTrue(item.matches(Highlighter.HighlightItem.Type.REGULAR, "", normal, MsgTags.EMPTY)); + assertFalse(item.matches(Highlighter.HighlightItem.Type.INFO, "", normal, MsgTags.EMPTY)); + + // Several status prefixes + update("status:bm status:s"); + assertFalse(highlighter.check(broadcaster, "test")); + assertFalse(highlighter.check(normal, "")); + assertFalse(highlighter.check(modTurbo, "test")); + assertFalse(highlighter.check(admin, "")); + assertFalse(highlighter.check(adminBroadcasterTurbo, "hello")); + assertFalse(highlighter.check(staff, "")); + assertFalse(highlighter.check(subscriber, "")); + User modSub = new User("abc", Room.EMPTY); + modSub.setModerator(true); + modSub.setSubscriber(true); + User broadcasterSub = new User("abc", Room.createRegular("#abc")); + broadcasterSub.setBroadcaster(true); + broadcasterSub.setSubscriber(true); + assertTrue(highlighter.check(modSub, "")); + assertTrue(highlighter.check(broadcasterSub, "")); } @Test @@ -539,35 +714,78 @@ public void testNew() { updateBlacklist(); update("config:info abc"); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.INFO, "abc", null, ab, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", null, ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, ab, null)); + assertTrue(highlighter.check(Type.INFO, "abc", null, ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.REGULAR, "abc", null, ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.ANY, "abc", null, ab, null, MsgTags.EMPTY)); update("abc"); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.INFO, "abc", null, ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, ab, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "", null, ab, null)); + assertFalse(highlighter.check(Type.INFO, "abc", null, ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.ANY, "abc", null, ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "", null, ab, null, MsgTags.EMPTY)); update("config:info chan:joshimuz"); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "#joshimuz", ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.INFO, "abc", "#joshimuz", ab, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", "#joshimuz", ab, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "joshimuz", ab, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "#somechannel", ab, null)); + assertTrue(highlighter.check(Type.ANY, "abc", "#joshimuz", ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.INFO, "abc", "#joshimuz", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.REGULAR, "abc", "#joshimuz", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "joshimuz", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#somechannel", ab, null, MsgTags.EMPTY)); update("chan:testchannel"); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", "#testchannel", ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", "#testchannel", ab, user)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", null, ab, user)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", "#somechannel", ab, user)); + assertTrue(highlighter.check(Type.REGULAR, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.REGULAR, "abc", "#testchannel", ab, user, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.REGULAR, "abc", null, ab, user, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.REGULAR, "abc", "#somechannel", ab, user, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.REGULAR, "abc", null, null, null, MsgTags.EMPTY)); + // Channel categories / missing user or ab update("chanCat:subonly"); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "#testchannel", ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, null, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, ab, null)); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + // No ab/user given + assertFalse(highlighter.check(Type.ANY, "abc", null, null, null, MsgTags.EMPTY)); + // No user, but ab given + assertFalse(highlighter.check(Type.ANY, "abc", null, ab, null, MsgTags.EMPTY)); ab.add("#testchannel", "subonly"); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "#testchannel", ab, null)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, null, user)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", "#somechannel", null, user)); + // Ab/channel given, but no user + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + // Ab given, but no user/channel + assertFalse(highlighter.check(Type.ANY, "abc", null, ab, null, MsgTags.EMPTY)); + // Gets ab and chan from user + assertTrue(highlighter.check(Type.ANY, "abc", null, null, user, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#somechannel", null, user, MsgTags.EMPTY)); + + update("!chanCat:subonly"); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + + // Either of the categories + update("chanCat:subonly,modding"); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + + // Either of the categories (category added) + ab.add("#testchannel2", "modding"); + update("chanCat:subonly,modding"); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + + // Not one of the categories + update("!chanCat:subonly,modding"); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + + // Not both categories + update("!chanCat:subonly !chanCat:modding"); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + + // One has both categories + ab.add("#testchannel2", "subonly"); + update("!chanCat:subonly,modding"); + assertTrue(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); + // Both have both categories + ab.add("#testchannel", "modding"); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel", ab, null, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.ANY, "abc", "#testchannel2", ab, null, MsgTags.EMPTY)); update("config:firstmsg"); assertTrue(highlighter.check(user, "abc")); @@ -576,17 +794,75 @@ public void testNew() { assertTrue(highlighter.check(user2, "abc")); update("config:info,firstmsg"); assertFalse(highlighter.check(user2, "abc")); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, ab, user2)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.INFO, "abc", null, ab, user2)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "abc", null, ab, user2)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, null, null)); - assertFalse(highlighter.check(Highlighter.HighlightItem.Type.ANY, "abc", null, ab, user)); + assertTrue(highlighter.check(Type.ANY, "abc", null, ab, user2, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.INFO, "abc", null, ab, user2, MsgTags.EMPTY)); + assertFalse(highlighter.check(Type.REGULAR, "abc", null, ab, user2, MsgTags.EMPTY)); + // No user given + assertFalse(highlighter.check(Type.ANY, "abc", null, null, null, MsgTags.EMPTY)); + // User with a message already added (checks 0 since message is normally added after checking) + assertFalse(highlighter.check(Type.ANY, "abc", null, ab, user, MsgTags.EMPTY)); update("config:any"); assertTrue(highlighter.check(user, "abc")); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.ANY, "", null, ab, user2)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.INFO, "", null, ab, user2)); - assertTrue(highlighter.check(Highlighter.HighlightItem.Type.REGULAR, "", null, ab, user2)); + assertTrue(highlighter.check(Type.ANY, "", null, ab, user2, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.INFO, "", null, ab, user2, MsgTags.EMPTY)); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user2, MsgTags.EMPTY)); + + update("config:hl"); + assertFalse(highlighter.check(user, "abc")); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message"))); + + // Tags prefix + update("config:t|msg-id=highlighted-message"); + assertFalse(highlighter.check(user, "abc")); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message"))); + + // Several tags + update("config:t|msg-id,t|subscriber=1"); + assertFalse(highlighter.check(user, "abc")); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("subscriber", "1"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "subscriber", "0"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "subscriber", "1"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("subscriber", "0"))); + + update("config:t|msg-id config:t|subscriber=1"); + assertFalse(highlighter.check(user, "abc")); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("subscriber", "1"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "subscriber", "0"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "subscriber", "1"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("subscriber", "0"))); + + update("config:t|msg-id config:t|subscriber=1,t|mod=1"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "mod", "1"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "mod", "0"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message", "mod", "1", "subscriber", "1"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("msg-id", "highlighted-message"))); + + // Tags prefix with regex value matching + update("config:t|color=reg:(#00FF7F|#FF00FF)"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("color", "#00FF7F"))); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("color", "#FF00FF"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("color", "#123456"))); + + // New list parsing/adding space + update("config:t|test=abc\\,lol"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc,lol"))); + update("config:t|test=\"a,b,c\",!notify"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "a,b,c"))); + update("config:t|test=\"abc lol\""); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc lol"))); + update("config:t|test=reg:abc\\ lol"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc lol"))); + update("config:t|test=reg:abc\\ \\\\w{2\\,3}"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc lol"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc rofl"))); + assertFalse(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abclol"))); + update("config:t|test=reg:\"abc \\\\w{2,3}\""); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc lol"))); + update("config:t|test=abc\\\\slol"); + assertTrue(highlighter.check(Type.REGULAR, "", null, ab, user, MsgTags.create("test", "abc\\slol"))); } } diff --git a/test/chatty/util/StringUtilTest.java b/test/chatty/util/StringUtilTest.java index 1d9be3063..b15375291 100644 --- a/test/chatty/util/StringUtilTest.java +++ b/test/chatty/util/StringUtilTest.java @@ -2,6 +2,7 @@ package chatty.util; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import org.junit.Test; import static org.junit.Assert.*; @@ -73,4 +74,43 @@ public void testFirstToUpperCase() { } + @Test + public void testSplit() { + assertEquals(StringUtil.split(null, 'a', 10), null); + testSplit2(',', 0, "", ""); + testSplit2(',', 0, "abc", "abc"); + testSplit2(',', 0, "\\abc", "abc"); + testSplit2(',', 0, "\\\\abc", "\\abc"); + testSplit2(',', 0, "a,b,c", "a", "b", "c"); + testSplit2(',', 1, "a,b,c", "a,b,c"); + testSplit2(',', 2, "a,b,c", "a", "b,c"); + testSplit2(',', 0, "'a,b',c", "a,b", "c"); + testSplit2(',', 0, "\\'a,b,c", "'a", "b", "c"); + testSplit2(',', 2, "\\'a,b,c'", "'a", "b,c'"); + testSplit2(',', '\'', '\\', 2, 2, "\\'a,b,c'", "'a", "b,c"); + testSplit2(',', 2, "a,b,\\c", "a", "b,\\c"); + testSplit2(',', '\'', '\\', 2, 2, "a,b,\\c", "a", "b,c"); + testSplit2(',', 2, "a\\,b,c", "a,b", "c"); + testSplit2(',', 0, "t|test=abc\\,lol", "t|test=abc,lol"); + + testSplit2(',', '#', '#', 2, 1, "a,b,c", "a", "b,c"); + testSplit2(',', '#', '#', 2, 1, "a#,b,c", "a,b", "c"); + testSplit2(' ', '-', '$', 2, 1, "abc- -123 -b c-", "abc 123", "-b c-"); + testSplit2(' ', '-', '$', 2, 1, "abc- $-123 -b c-", "abc -123 b", "c-"); + testSplit2(' ', '-', '$', 2, 1, "abc$ 123 -b c-", "abc 123", "-b c-"); + testSplit2(' ', '-', '$', 3, 1, "abc$ 123 -b c-", "abc 123", "b c"); + testSplit2(' ', '-', '$', 0, 1, "abc$ 123 -b c-", "abc 123", "b c"); + testSplit2(' ', '-', '$', 2, 2, "abc$ 123 -b c-", "abc 123", "b c"); + testSplit2(' ', '-', '$', 2, 0, "abc$ 123 -b c-", "abc$ 123", "-b c-"); + testSplit2(' ', '-', '$', 0, 0, "abc$ 123 -b c-", "abc$ 123", "-b c-"); + } + + private static void testSplit2(char split, int limit, String input, String... result) { + testSplit2(split, '\'', '\\', limit, 1, input, result); + } + + private static void testSplit2(char split, char quote, char escape, int limit, int remove, String input, String... result) { + assertEquals(StringUtil.split(input, split, quote, escape, limit, remove), Arrays.asList(result)); + } + }