From a1fec9835f7bd03c923236467be6d16545d63308 Mon Sep 17 00:00:00 2001
From: tduva
Date: Mon, 18 Mar 2024 12:23:17 +0100
Subject: [PATCH 1/5] Improve /createClip output, make links in info messages
more flexible
---
src/chatty/TwitchClient.java | 20 ++-
src/chatty/gui/MainGui.java | 12 +-
src/chatty/gui/UserListener.java | 3 +-
.../components/textpane/ChannelTextPane.java | 34 ++--
.../gui/components/textpane/InfoMessage.java | 14 +-
.../components/textpane/LinkController.java | 20 ++-
.../notifications/NotificationManager.java | 7 +-
src/chatty/util/api/Requests.java | 29 +++-
src/chatty/util/api/ResultManager.java | 28 +++-
src/chatty/util/api/TwitchApi.java | 11 +-
src/chatty/util/irc/MsgTags.java | 152 +++++++++++++++---
11 files changed, 257 insertions(+), 73 deletions(-)
diff --git a/src/chatty/TwitchClient.java b/src/chatty/TwitchClient.java
index 94f4ce690..2d4b04e55 100644
--- a/src/chatty/TwitchClient.java
+++ b/src/chatty/TwitchClient.java
@@ -73,6 +73,7 @@
import chatty.util.api.Follower;
import chatty.util.api.FollowerInfo;
import chatty.util.api.ResultManager;
+import chatty.util.api.ResultManager.CreateClipResult;
import chatty.util.api.StreamCategory;
import chatty.util.api.StreamInfo.StreamType;
import chatty.util.api.StreamInfo.ViewerStats;
@@ -1616,9 +1617,18 @@ private void addCommands() {
commandAddStreamMarker(p.getRoom(), p.getArgs());
});
commands.add("createClip", p -> {
- api.createClip(p.getRoom().getStream(), result -> {
- g.printLine(p.getRoom(), result);
+ api.subscribe(ResultManager.Type.CREATE_CLIP, commands, (CreateClipResult) (editUrl, viewUrl, error) -> {
+ if (error != null) {
+ g.printLine(p.getRoom(), error);
+ }
+ else {
+ // Update indices when changing info text
+ MsgTags tags = MsgTags.createLinks(
+ new MsgTags.Link(MsgTags.Link.Type.URL, editUrl, 14, 22));
+ g.printInfo(p.getRoom(), "Clip created (Edit Clip): "+viewUrl, tags);
+ }
});
+ api.createClip(p.getRoom().getStream());
});
c.addNewCommands(commands, this);
commands.add("addStreamHighlight", p -> {
@@ -2096,7 +2106,7 @@ public void run() {
} else if (command.equals("testr")) {
api.test();
} else if (command.equals("joinlink")) {
- MsgTags tags = MsgTags.create("chatty-channel-join", Helper.toChannel("twitch"));
+ MsgTags tags = MsgTags.createLinks(new MsgTags.Link(MsgTags.Link.Type.JOIN, Helper.toChannel("twitch"), "Join"));
g.printInfo(c.getRoomByChannel(channel), "Join link:", tags);
}
}
@@ -2740,7 +2750,7 @@ public void messageReceived(chatty.util.api.eventsub.Message message) {
String channel = Helper.toChannel(raid.fromLogin);
String text = String.format("[Raid] Now raiding %s with %d viewers.",
raid.toLogin, raid.viewers);
- MsgTags tags = MsgTags.create("chatty-channel-join", Helper.toChannel(raid.toLogin));
+ MsgTags tags = MsgTags.createLinks(new MsgTags.Link(MsgTags.Link.Type.JOIN, Helper.toChannel(raid.toLogin), "Join"));
g.printInfo(c.getRoomByChannel(channel), text, tags);
}
String pollMessage = PollPayload.getPollMessage(message);
@@ -2771,7 +2781,7 @@ public void messageReceived(chatty.util.api.eventsub.Message message) {
String infoText = String.format("[Shoutout] Was given to %s (@%s)",
shoutout.target_name,
shoutout.moderator_login);
- MsgTags tags = MsgTags.create("chatty-channel-join", Helper.toChannel(shoutout.target_login));
+ MsgTags tags = MsgTags.createLinks(new MsgTags.Link(MsgTags.Link.Type.JOIN, Helper.toChannel(shoutout.target_login), "Join"));
g.printInfo(c.getRoomByChannel(channel), infoText, tags);
// Mod Action
List args = new ArrayList<>();
diff --git a/src/chatty/gui/MainGui.java b/src/chatty/gui/MainGui.java
index 278afca9a..44317827d 100644
--- a/src/chatty/gui/MainGui.java
+++ b/src/chatty/gui/MainGui.java
@@ -2672,10 +2672,14 @@ public void usericonClicked(Usericon usericon, MouseEvent e) {
}
@Override
- public void linkClicked(Channel channel, String link) {
- if (link.startsWith("join.")) {
- String c = link.substring("join.".length());
- client.joinChannel(c);
+ public void linkClicked(Channel channel, MsgTags.Link link) {
+ switch (link.type) {
+ case JOIN:
+ client.joinChannel(link.target);
+ break;
+ case URL:
+ UrlOpener.openUrl(link.target);
+ break;
}
}
diff --git a/src/chatty/gui/UserListener.java b/src/chatty/gui/UserListener.java
index 92db273a3..cd02b55c0 100644
--- a/src/chatty/gui/UserListener.java
+++ b/src/chatty/gui/UserListener.java
@@ -5,6 +5,7 @@
import chatty.gui.components.Channel;
import chatty.util.api.Emoticon;
import chatty.util.api.usericons.Usericon;
+import chatty.util.irc.MsgTags;
import java.awt.event.MouseEvent;
/**
@@ -21,6 +22,6 @@ public interface UserListener {
public void userClicked(User user, String messageId, String autoModMsgId, MouseEvent e);
public void emoteClicked(Emoticon emote, MouseEvent e);
public void usericonClicked(Usericon usericon, MouseEvent e);
- public void linkClicked(Channel channel, String link);
+ public void linkClicked(Channel channel, MsgTags.Link link);
}
diff --git a/src/chatty/gui/components/textpane/ChannelTextPane.java b/src/chatty/gui/components/textpane/ChannelTextPane.java
index afe3d2296..d8324f3f7 100644
--- a/src/chatty/gui/components/textpane/ChannelTextPane.java
+++ b/src/chatty/gui/components/textpane/ChannelTextPane.java
@@ -76,6 +76,7 @@
import chatty.util.api.usericons.UsericonManager;
import java.util.function.Function;
import chatty.gui.transparency.TransparencyComponent;
+import chatty.util.irc.MsgTags.Link;
import java.util.function.Consumer;
@@ -762,10 +763,10 @@ private void printInfoMessage2(InfoMessage message, AttributeSet style) {
printTimestamp(style);
printChannelIcon(null, message.localUser);
printSpecialsInfo(message.text, style, message.highlightMatches, message.tags);
- Pair link = message.getLink();
- if (link != null) {
+
+ for (Link link : message.getAppendedLinks()) {
print(" ", style);
- print(link.key, styles.generalLink(style, link.value));
+ print(link.label, styles.generalLink(style, link));
}
finishLine();
}
@@ -1930,7 +1931,7 @@ public void usericonClicked(Usericon usericon, MouseEvent e) {
}
@Override
- public void linkClicked(Channel channel, String link) {
+ public void linkClicked(Channel channel, MsgTags.Link link) {
}
}
@@ -2673,21 +2674,18 @@ private void findSpecialLinks(TreeMap ranges, HashMap links = tags.getLinks();
+ if (links == null) {
return;
}
- /**
- * This only allows one link per message, just extending the existing
- * join link functionality a bit, but should be good enough for now.
- */
- String chan = tags.getChannelJoin();
- String indices = tags.getChannelJoinIndices();
- String[] split = indices.split("-");
- int start = Integer.parseInt(split[0]);
- int end = Integer.parseInt(split[1]);
- if (!inRanges(start, ranges) && !inRanges(end, ranges)) {
- ranges.put(start, end);
- rangesStyle.put(start, styles.generalLink(baseStyle, "join."+chan));
+
+ for (Link link : links) {
+ if (link.startIndex != -1 && link.endIndex != -1) {
+ if (!inRanges(link.startIndex, ranges) && !inRanges(link.endIndex, ranges)) {
+ ranges.put(link.startIndex, link.endIndex);
+ rangesStyle.put(link.startIndex, styles.generalLink(baseStyle, link));
+ }
+ }
}
}
@@ -3999,7 +3997,7 @@ public MutableAttributeSet info(Color color) {
return info();
}
- public MutableAttributeSet generalLink(AttributeSet base, String target) {
+ public MutableAttributeSet generalLink(AttributeSet base, MsgTags.Link target) {
SimpleAttributeSet result = new SimpleAttributeSet(base);
result.addAttribute(Attribute.GENERAL_LINK, target);
StyleConstants.setUnderline(result, true);
diff --git a/src/chatty/gui/components/textpane/InfoMessage.java b/src/chatty/gui/components/textpane/InfoMessage.java
index 4652e8658..35b38e85f 100644
--- a/src/chatty/gui/components/textpane/InfoMessage.java
+++ b/src/chatty/gui/components/textpane/InfoMessage.java
@@ -5,7 +5,9 @@
import chatty.gui.Highlighter.Match;
import chatty.util.Pair;
import chatty.util.irc.MsgTags;
+import chatty.util.irc.MsgTags.Link;
import java.awt.Color;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -139,14 +141,16 @@ public int getMsgEnd() {
return -1;
}
- public Pair getLink() {
+ public List getAppendedLinks() {
+ List result = new ArrayList<>();
if (tags != null) {
- // When indicdes are set the join link is added differently
- if (tags.getChannelJoin() != null && tags.getChannelJoinIndices() == null) {
- return new Pair<>("Join", "join."+tags.getChannelJoin());
+ for (Link link : tags.getLinks()) {
+ if (link.startIndex == -1) {
+ result.add(link);
+ }
}
}
- return null;
+ return result;
}
}
diff --git a/src/chatty/gui/components/textpane/LinkController.java b/src/chatty/gui/components/textpane/LinkController.java
index 77e73f7d2..3ceddb536 100644
--- a/src/chatty/gui/components/textpane/LinkController.java
+++ b/src/chatty/gui/components/textpane/LinkController.java
@@ -37,6 +37,7 @@
import chatty.util.api.CachedImage;
import chatty.util.api.CachedImage.ImageType;
import chatty.util.api.usericons.Usericon;
+import chatty.util.irc.MsgTags;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
@@ -215,7 +216,7 @@ else if (e.isPopupTrigger()) {
private void handleSingleLeftClick(MouseEvent e, Element element) {
String url;
- String link;
+ MsgTags.Link link;
User user;
CachedImage emoteImage;
CachedImage usericonImage;
@@ -376,8 +377,8 @@ private boolean isUrlDeleted(Element e) {
return deleted;
}
- private String getGeneralLink(Element e) {
- return (String)(e.getAttributes().getAttribute(ChannelTextPane.Attribute.GENERAL_LINK));
+ private MsgTags.Link getGeneralLink(Element e) {
+ return (MsgTags.Link)(e.getAttributes().getAttribute(ChannelTextPane.Attribute.GENERAL_LINK));
}
private User getUser(Element e) {
@@ -533,7 +534,7 @@ private void openContextMenu(MouseEvent e) {
user = getMention(element);
}
String url = getUrl(element);
- String link = getGeneralLink(element);
+ MsgTags.Link link = getGeneralLink(element);
CachedImage emoteImage = getEmoticonImage(element);
CachedImage usericonImage = getUsericonImage(element);
if (user != null) {
@@ -544,9 +545,14 @@ else if (url != null) {
m = new UrlContextMenu(url, isUrlDeleted(element), contextMenuListener);
}
else if (link != null) {
- if (link.startsWith("join.")) {
- String c = Helper.toStream(link.substring("join.".length()));
- m = new StreamsContextMenu(Arrays.asList(new String[]{c}), contextMenuListener);
+ switch (link.type) {
+ case JOIN:
+ String c = Helper.toStream(link.target);
+ m = new StreamsContextMenu(Arrays.asList(new String[]{c}), contextMenuListener);
+ break;
+ case URL:
+ m = new UrlContextMenu(link.target, false, contextMenuListener);
+ break;
}
}
else if (emoteImage != null) {
diff --git a/src/chatty/gui/notifications/NotificationManager.java b/src/chatty/gui/notifications/NotificationManager.java
index be69fc0cf..7a6fecdb2 100644
--- a/src/chatty/gui/notifications/NotificationManager.java
+++ b/src/chatty/gui/notifications/NotificationManager.java
@@ -417,9 +417,10 @@ private void addInfoMsg(Notification n, NotificationData d, String channel, Stri
int start = StringUtil.toLowerCase(d.title).indexOf(stream);
if (start != -1) {
int end = start + stream.length() - 1;
- tags = MsgTags.create(
- "chatty-channel-join", stream,
- "chatty-channel-join-indices", start+"-"+end);
+ tags = MsgTags.createLinks(new MsgTags.Link(
+ MsgTags.Link.Type.JOIN,
+ stream,
+ start, end));
}
}
diff --git a/src/chatty/util/api/Requests.java b/src/chatty/util/api/Requests.java
index 24fc4a6c7..d5560c164 100644
--- a/src/chatty/util/api/Requests.java
+++ b/src/chatty/util/api/Requests.java
@@ -32,6 +32,7 @@
import java.util.logging.Logger;
import org.json.simple.JSONObject;
import chatty.util.api.ResultManager.CategoryResult;
+import chatty.util.api.ResultManager.CreateClipResult;
import chatty.util.api.ResultManager.ShieldModeResult;
import chatty.util.api.TokenInfo.Scope;
import chatty.util.api.TwitchApi.SimpleRequestResult;
@@ -468,31 +469,43 @@ public void createStreamMarker(String userId, String description, String token,
});
}
- public void createClip(String userId, Consumer listener) {
+ public void createClip(String userId) {
String url = makeUrl("https://api.twitch.tv/helix/clips",
"broadcaster_id", userId);
newApi.add(url, "POST", api.defaultToken, r -> {
+ String error = null;
if (r.responseCode == 202) {
- String clipUrl = Parsing.getClipUrl(r.text);
- if (clipUrl != null) {
- listener.accept("Edit clip: "+clipUrl);
+ String editUrl = Parsing.getClipUrl(r.text);
+ if (editUrl != null) {
+ String viewUrl = editUrl.replace("/edit", "");
+ api.resultManager.inform(ResultManager.Type.CREATE_CLIP,
+ (CreateClipResult l) -> {
+ l.result(editUrl, viewUrl, null);
+ });
}
else {
- listener.accept("Error creating clip");
+ error = "Error creating clip";
}
}
else if (r.responseCode == 401) {
- listener.accept("Creating clip failed: Check access under 'Main - Account'");
+ error = "Creating clip failed: Check access under 'Main - Account'";
}
else {
String errorMsg = getErrorMessage(r.text);
if (errorMsg != null) {
- listener.accept(errorMsg);
+ error = errorMsg;
}
else {
- listener.accept("Creating clip failed");
+ error = "Creating clip failed";
}
}
+ if (error != null) {
+ String error2 = error;
+ api.resultManager.inform(ResultManager.Type.CREATE_CLIP,
+ (CreateClipResult l) -> {
+ l.result(null, null, error2);
+ });
+ }
});
}
diff --git a/src/chatty/util/api/ResultManager.java b/src/chatty/util/api/ResultManager.java
index 8cc11b190..187bd6d3c 100644
--- a/src/chatty/util/api/ResultManager.java
+++ b/src/chatty/util/api/ResultManager.java
@@ -21,10 +21,12 @@
public class ResultManager {
private final Map> listeners = new HashMap<>();
+ private final Map uniqueListeners = new HashMap<>();
public enum Type {
CATEGORY_RESULT(CategoryResult.class),
- SHIELD_MODE_RESULT(ShieldModeResult.class);
+ SHIELD_MODE_RESULT(ShieldModeResult.class),
+ CREATE_CLIP(CreateClipResult.class);
private final Class c;
@@ -34,6 +36,19 @@ public enum Type {
}
public void subscribe(Type type, Object listener) {
+ subscribe(type, null, listener);
+ }
+
+ /**
+ * Subscribe to the given type, but remove any previous listener under the
+ * same type with the same unique object provided.
+ *
+ * @param type
+ * @param unique If provided, remove previous listener with the same type
+ * and unique object
+ * @param listener
+ */
+ public void subscribe(Type type, Object unique, Object listener) {
if (listener != null) {
if (!type.c.isInstance(listener)) {
throw new RuntimeException("Invalid parameter");
@@ -41,6 +56,13 @@ public void subscribe(Type type, Object listener) {
if (!listeners.containsKey(type)) {
listeners.put(type, new HashSet<>());
}
+ if (unique != null) {
+ Object prevListener = uniqueListeners.remove(unique);
+ if (prevListener != null) {
+ listeners.get(type).remove(prevListener);
+ }
+ uniqueListeners.put(unique, listener);
+ }
listeners.get(type).add(listener);
}
}
@@ -63,6 +85,10 @@ public interface ShieldModeResult {
public void result(String stream, boolean enabled);
}
+ public interface CreateClipResult {
+ public void result(String editUrl, String viewUrl, String error);
+ }
+
public static void main(String[] args) {
ResultManager m = new ResultManager();
m.subscribe(Type.CATEGORY_RESULT, (CategoryResult) categories -> {
diff --git a/src/chatty/util/api/TwitchApi.java b/src/chatty/util/api/TwitchApi.java
index f9ae198fa..4d768915a 100644
--- a/src/chatty/util/api/TwitchApi.java
+++ b/src/chatty/util/api/TwitchApi.java
@@ -11,6 +11,7 @@
import java.util.*;
import java.util.logging.Logger;
import chatty.util.api.ResultManager.CategoryResult;
+import chatty.util.api.ResultManager.CreateClipResult;
import chatty.util.api.eventsub.EventSubAddResult;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -531,12 +532,12 @@ public void createStreamMarker(String stream, String description, StreamMarkerRe
}, stream);
}
- public void createClip(String stream, Consumer listener) {
+ public void createClip(String stream) {
userIDs.getUserIDsAsap(r -> {
if (r.hasError()) {
- listener.accept("Failed to resolve channel id");
+ resultManager.inform(ResultManager.Type.CREATE_CLIP, (CreateClipResult l) -> l.result(null, null, "Failed to resolve channel id"));
} else {
- requests.createClip(r.getId(stream), listener);
+ requests.createClip(r.getId(stream));
}
}, stream);
}
@@ -559,6 +560,10 @@ public void subscribe(ResultManager.Type type, Object listener) {
resultManager.subscribe(type, listener);
}
+ public void subscribe(ResultManager.Type type, Object unique, Object listener) {
+ resultManager.subscribe(type, unique, listener);
+ }
+
public interface StreamMarkerResult {
public void streamMarkerResult(String error);
}
diff --git a/src/chatty/util/irc/MsgTags.java b/src/chatty/util/irc/MsgTags.java
index ff9c3d733..9f6ef495a 100644
--- a/src/chatty/util/irc/MsgTags.java
+++ b/src/chatty/util/irc/MsgTags.java
@@ -3,8 +3,11 @@
import chatty.util.StringUtil;
import java.math.BigDecimal;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Implementation of IRCv3 tags with Twitch-specific methods.
@@ -13,10 +16,13 @@
*/
public class MsgTags extends IrcMsgTags {
- public static final MsgTags EMPTY = new MsgTags(null);
+ public static final MsgTags EMPTY = new MsgTags(null, null);
+
+ private Map objects;
- public MsgTags(Map tags) {
+ public MsgTags(Map tags, Map objects) {
super(tags);
+ this.objects = objects;
}
public String getId() {
@@ -63,14 +69,6 @@ public long getHistoricTimeStamp() {
}
}
- public String getChannelJoin() {
- return get("chatty-channel-join");
- }
-
- public String getChannelJoinIndices() {
- return get("chatty-channel-join-indices");
- }
-
public boolean isRestrictedMessage() {
return isValue("chatty-is-restricted", "1");
}
@@ -119,27 +117,40 @@ public String getHypeChatInfo() {
//================
/**
- * Parse the given IRCv3 tags String (no leading @) into a IrcMsgTags object.
+ * Parse the given IRCv3 tags String (no leading @) into a MsgTags object.
*
* @param tags The tags String
- * @return IrcMsgTags object, empty if tags was null
+ * @return MsgTags object, empty if tags was null
*/
public static MsgTags parse(String tags) {
Map parsedTags = parseTags(tags);
if (parsedTags == null) {
return EMPTY;
}
- return new MsgTags(parsedTags);
+ return new MsgTags(parsedTags, null);
}
/**
- * Create a new IrcMsgTags object with the given key/value pairs.
+ * Create a new MsgTags object with the given key/value pairs.
*
* @param args Alternating key/value pairs
- * @return IrcMsgTags object
+ * @return MsgTags object
*/
public static MsgTags create(String... args) {
- return new MsgTags(createTags(args));
+ return new MsgTags(createTags(args), null);
+ }
+
+ public void fillObjects(Map map) {
+ if (objects != null) {
+ map.putAll(objects);
+ }
+ }
+
+ private void addObject(String key, Object value) {
+ if (objects == null) {
+ objects = new HashMap<>();
+ }
+ objects.put(key, value);
}
/**
@@ -154,7 +165,12 @@ public static MsgTags merge(MsgTags a, MsgTags b) {
Map result = new HashMap<>();
b.fill(result);
a.fill(result);
- return new MsgTags(result);
+
+ Map objectsResult = new HashMap<>();
+ b.fillObjects(objectsResult);
+ a.fillObjects(objectsResult);
+
+ return new MsgTags(result, objectsResult);
}
/**
@@ -170,7 +186,107 @@ public static MsgTags addTag(MsgTags a, String key, String value) {
Map result = new HashMap<>();
a.fill(result);
result.put(key, value);
- return new MsgTags(result);
+
+ Map objectsResult = new HashMap<>();
+ a.fillObjects(objectsResult);
+
+ return new MsgTags(result, objectsResult);
+ }
+
+ //=======
+ // Links
+ //=======
+
+ @SuppressWarnings("unchecked")
+ public List getLinks() {
+ if (objects != null && objects.containsKey("links")) {
+ return (List ) objects.get("links");
+ }
+ return new ArrayList<>();
+ }
+
+ public static MsgTags createLinks(Link... input) {
+ MsgTags tags = create("");
+ tags.addObject("links", createLinksObject(input));
+ return tags;
+ }
+
+ public static Object createLinksObject(Link... links) {
+ List result = new ArrayList<>();
+ for (Link link : links) {
+ result.add(link);
+ }
+ return result;
+ }
+
+ public static class Link {
+
+ public enum Type {
+ JOIN, URL
+ }
+
+ public final Type type;
+ public final String target;
+ public final String label;
+ public final int startIndex;
+ public final int endIndex;
+
+ public Link(Type type, String target, int startIndex, int endIndex) {
+ this.type = type;
+ this.target = target;
+ this.label = "";
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ }
+
+ public Link(Type type, String target, String label) {
+ this.type = type;
+ this.target = target;
+ this.label = label;
+ this.startIndex = -1;
+ this.endIndex = -1;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[%s.%s %s](%d-%d)",
+ type, target, label, startIndex, endIndex);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Link other = (Link) obj;
+ if (this.startIndex != other.startIndex) {
+ return false;
+ }
+ if (this.endIndex != other.endIndex) {
+ return false;
+ }
+ if (!Objects.equals(this.target, other.target)) {
+ return false;
+ }
+ return Objects.equals(this.label, other.label);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 79 * hash + Objects.hashCode(this.target);
+ hash = 79 * hash + Objects.hashCode(this.label);
+ hash = 79 * hash + this.startIndex;
+ hash = 79 * hash + this.endIndex;
+ return hash;
+ }
+
}
}
From 23231d3ef3cdf27766ceb590d8423122fa97d250 Mon Sep 17 00:00:00 2001
From: tduva
Date: Tue, 19 Mar 2024 13:36:34 +0100
Subject: [PATCH 2/5] Add /userinfoRecent command, related changes
- Add command to open User Info Dialog on users recently afffected by a
successfull Twitch Command or the User Dialog being closed
- Fix User Info Dialog not triggering close listener for actions where
dispose is called (now disposed is used by default as well, and a
different event is used that triggers on dispose)
---
src/chatty/Commands.java | 8 ++++
src/chatty/TwitchCommands.java | 4 +-
src/chatty/gui/MainGui.java | 9 ++++
.../help/help-builtin_commands.html | 5 ++
.../components/userinfo/UserInfoDialog.java | 12 +----
.../components/userinfo/UserInfoManager.java | 34 +++++---------
src/chatty/util/RecentlyAffectedUsers.java | 44 ++++++++++++++++++
src/chatty/util/UniqueLimitedRingBuffer.java | 46 +++++++++++++++++++
8 files changed, 128 insertions(+), 34 deletions(-)
create mode 100644 src/chatty/util/RecentlyAffectedUsers.java
create mode 100644 src/chatty/util/UniqueLimitedRingBuffer.java
diff --git a/src/chatty/Commands.java b/src/chatty/Commands.java
index cb7946ec6..4d170786d 100644
--- a/src/chatty/Commands.java
+++ b/src/chatty/Commands.java
@@ -188,6 +188,14 @@ public String getArgs() {
return parameters.getArgs();
}
+ public String getArgsTrimNonNull() {
+ String args = StringUtil.trim(getArgs());
+ if (args != null) {
+ return args;
+ }
+ return "";
+ }
+
public boolean hasArgs() {
return !StringUtil.isNullOrEmpty(parameters.getArgs());
}
diff --git a/src/chatty/TwitchCommands.java b/src/chatty/TwitchCommands.java
index 70f40bc1c..96b0cdc4f 100644
--- a/src/chatty/TwitchCommands.java
+++ b/src/chatty/TwitchCommands.java
@@ -6,6 +6,7 @@
import chatty.gui.UrlOpener;
import chatty.lang.Language;
import chatty.util.DateTime;
+import chatty.util.RecentlyAffectedUsers;
import chatty.util.StringUtil;
import chatty.util.api.TwitchApi;
import chatty.util.api.TwitchApi.SimpleRequestResultListener;
@@ -58,7 +59,7 @@ public class TwitchCommands {
private static final Set OTHER_COMMANDS = new HashSet<>(Arrays.asList(new String[]{
}));
- private TwitchConnection c;
+ private final TwitchConnection c;
public TwitchCommands(TwitchConnection c) {
this.c = c;
@@ -392,6 +393,7 @@ private void userCommand(TwitchClient client,
if (r.error == null) {
// Success
client.g.addToLine(p.getRoom(), objectId, "OK");
+ RecentlyAffectedUsers.addUser(user);
}
else {
// Failed
diff --git a/src/chatty/gui/MainGui.java b/src/chatty/gui/MainGui.java
index 44317827d..526b3a4dd 100644
--- a/src/chatty/gui/MainGui.java
+++ b/src/chatty/gui/MainGui.java
@@ -2807,6 +2807,15 @@ else if (!Helper.isValidStream(username)) {
openUserInfoDialog(user, p.getParameters().get("msg-id"), null);
}
});
+ client.commands.addEdt("userinfoRecent", p -> {
+ User user = RecentlyAffectedUsers.poll(p.getChannel());
+ if (user == null) {
+ printSystem(p.getRoom(), "No recently affected user found");
+ }
+ else {
+ openUserInfoDialog(user, p.getParameters().get("msg-id"), null);
+ }
+ });
client.commands.addEdt("search", p -> {
openSearchDialog();
});
diff --git a/src/chatty/gui/components/help/help-builtin_commands.html b/src/chatty/gui/components/help/help-builtin_commands.html
index 7f545f2bc..6b98c7a5c 100644
--- a/src/chatty/gui/components/help/help-builtin_commands.html
+++ b/src/chatty/gui/components/help/help-builtin_commands.html
@@ -124,6 +124,11 @@ Open windows in Chatty
/openSubscribers
- Opens the according dialog
/userinfo <username>
- To open the User Info Dialog
on a specific user of the current channel (if available)
+ /userinfoRecent
- To open the User Info Dialog on a
+ user recently affected by a Twitch Command or with recently open
+ User Dialog on that channel. Repeately executing the command (e.g.
+ through a hotkey) goes back in the history of recently affected
+ users, up to 10 users.
/releaseinfo
- Opens the help with the release information
diff --git a/src/chatty/gui/components/userinfo/UserInfoDialog.java b/src/chatty/gui/components/userinfo/UserInfoDialog.java
index 55f5fed60..29d654bcd 100644
--- a/src/chatty/gui/components/userinfo/UserInfoDialog.java
+++ b/src/chatty/gui/components/userinfo/UserInfoDialog.java
@@ -54,8 +54,6 @@ public enum Action {
private final JCheckBox singleMessage = new JCheckBox(SINGLE_MESSAGE_CHECK);
private final BanReasons banReasons;
private final Buttons buttons;
-
- private final ActionListener actionListener;
private User currentUser;
private String currentLocalUsername;
@@ -83,6 +81,7 @@ public UserInfoDialog(final Window parent, UserInfoListener listener,
Settings settings,
final ContextMenuListener contextMenuListener) {
super(parent);
+ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.requester = requester;
this.settings = settings;
GuiUtil.installEscapeCloseOperation(this);
@@ -107,14 +106,7 @@ public void actionPerformed(ActionEvent e) {
}
});
- actionListener = new ActionListener() {
-
- @Override
- public void actionPerformed(ActionEvent e) {
- dispose();
- }
- };
- closeButton.addActionListener(actionListener);
+ closeButton.addActionListener(e -> dispose());
setLayout(new GridBagLayout());
diff --git a/src/chatty/gui/components/userinfo/UserInfoManager.java b/src/chatty/gui/components/userinfo/UserInfoManager.java
index 965c5d916..599044f6e 100644
--- a/src/chatty/gui/components/userinfo/UserInfoManager.java
+++ b/src/chatty/gui/components/userinfo/UserInfoManager.java
@@ -6,8 +6,8 @@
import chatty.gui.GuiUtil;
import chatty.gui.MainGui;
import chatty.gui.components.menus.ContextMenuListener;
+import chatty.util.RecentlyAffectedUsers;
import chatty.util.Timestamp;
-import chatty.util.api.ChannelInfo;
import chatty.util.api.Follower;
import chatty.util.api.FollowerInfo;
import chatty.util.api.TwitchApi;
@@ -18,10 +18,9 @@
import java.awt.Component;
import java.awt.Point;
import java.awt.Window;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.ComponentListener;
-import java.text.SimpleDateFormat;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -33,13 +32,10 @@
public class UserInfoManager {
private final List dialogs = new ArrayList<>();
- private final ComponentListener closeListener;
+ private final WindowListener closeListener;
private final Window dummyWindow = new Window(null);
- private int x;
- private int y;
- private final Point temp2 = new Point();
-
+
private final MainGui main;
private final Settings settings;
private final ContextMenuListener contextMenuListener;
@@ -55,22 +51,13 @@ public UserInfoManager(final MainGui owner, Settings settings,
this.main = owner;
this.settings = settings;
this.contextMenuListener = contextMenuListener;
- closeListener = new ComponentAdapter() {
+ closeListener = new WindowAdapter() {
@Override
- public void componentHidden(ComponentEvent e) {
- handleClosed(e.getComponent());
+ public void windowClosed(WindowEvent e) {
+ handleClosed(e.getWindow());
}
- @Override
- public void componentMoved(ComponentEvent e) {
-// handleChanged(e.getComponent());
- }
-
- @Override
- public void componentResized(ComponentEvent e) {
-// handleChanged(e.getComponent());
- }
};
userInfoListener = new UserInfoListener() {
@@ -215,7 +202,7 @@ private void setInitialLocationAndSize(UserInfoDialog dialog) {
dialog.setSize(400, 360);
}
dialog.setLocation(targetLocation);
- dialog.addComponentListener(closeListener);
+ dialog.addWindowListener(closeListener);
}
private void handleClosed(Component c) {
@@ -229,6 +216,7 @@ private void handleClosed(Component c) {
if (!dialog.isPinned() && numUnpinned() == 1) {
saveLocationAndSize(dialog);
}
+ RecentlyAffectedUsers.addUser(dialog.getUser());
}
private void saveLocationAndSize(Component c) {
diff --git a/src/chatty/util/RecentlyAffectedUsers.java b/src/chatty/util/RecentlyAffectedUsers.java
new file mode 100644
index 000000000..1b6f5154b
--- /dev/null
+++ b/src/chatty/util/RecentlyAffectedUsers.java
@@ -0,0 +1,44 @@
+
+package chatty.util;
+
+import chatty.User;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Store users most recently affected by a command or their User Dialog being
+ * open, per channel.
+ *
+ * @author tduva
+ */
+public class RecentlyAffectedUsers {
+
+ private static final Map> users = new HashMap<>();
+
+ /**
+ * Add a user, removing the user if already present, effectively moving it
+ * to the end (most recent) of the list.
+ *
+ * @param user
+ */
+ public static void addUser(User user) {
+ if (!users.containsKey(user.getChannel())) {
+ users.put(user.getChannel(), new UniqueLimitedRingBuffer<>(10));
+ }
+ users.get(user.getChannel()).append(user);
+ }
+
+ /**
+ * Get and remove the most recent user for this channel.
+ *
+ * @param channel
+ * @return The {@code User} or {@code null} if none is present
+ */
+ public static User poll(String channel) {
+ if (users.containsKey(channel)) {
+ return users.get(channel).pollLast();
+ }
+ return null;
+ }
+
+}
diff --git a/src/chatty/util/UniqueLimitedRingBuffer.java b/src/chatty/util/UniqueLimitedRingBuffer.java
new file mode 100644
index 000000000..8658df89d
--- /dev/null
+++ b/src/chatty/util/UniqueLimitedRingBuffer.java
@@ -0,0 +1,46 @@
+
+package chatty.util;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+
+/**
+ *
+ * @author tduva
+ */
+public class UniqueLimitedRingBuffer {
+
+ private final LinkedList list = new LinkedList<>();
+ private final Set set = new HashSet<>();
+ private final int capacity;
+
+ public UniqueLimitedRingBuffer(int capacity) {
+ this.capacity = capacity;
+ }
+
+ public void append(T e) {
+ if (set.add(e)) {
+ list.addLast(e);
+ }
+ else {
+ list.remove(e);
+ list.addLast(e);
+ }
+ if (list.size() > capacity) {
+ list.removeFirst();
+ }
+ }
+
+ public T pollLast() {
+ T removed = list.pollLast();
+ set.remove(removed);
+ return removed;
+ }
+
+ public void remove(T o) {
+ list.remove(o);
+ set.remove(o);
+ }
+
+}
From fb0a2689454edf032dba7273644d8cd59749a375 Mon Sep 17 00:00:00 2001
From: tduva
Date: Wed, 20 Mar 2024 17:24:42 +0100
Subject: [PATCH 3/5] Fix routed historic message not showing historic
timestamp
---
src/chatty/gui/components/textpane/UserMessage.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/chatty/gui/components/textpane/UserMessage.java b/src/chatty/gui/components/textpane/UserMessage.java
index 5629b7c5f..e57b6ff6d 100644
--- a/src/chatty/gui/components/textpane/UserMessage.java
+++ b/src/chatty/gui/components/textpane/UserMessage.java
@@ -56,6 +56,7 @@ public UserMessage copy() {
result.ignoreSource = ignoreSource;
result.routingSource = routingSource;
result.localUser = localUser;
+ result.historicTimeStamp = historicTimeStamp;
return result;
}
From 4ccae72572261e6cdbfd6491cd5297c745e61cb9 Mon Sep 17 00:00:00 2001
From: tduva
Date: Thu, 21 Mar 2024 13:31:28 +0100
Subject: [PATCH 4/5] v0.26
---
src/chatty/Chatty.java | 2 +-
.../gui/components/help/help-releases.html | 42 +++++++++++++++++--
src/chatty/gui/components/help/help.html | 2 +-
src/chatty/gui/components/help/style.css | 2 +-
4 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/src/chatty/Chatty.java b/src/chatty/Chatty.java
index e1c36fff8..9721f81fa 100644
--- a/src/chatty/Chatty.java
+++ b/src/chatty/Chatty.java
@@ -57,7 +57,7 @@ public class Chatty {
* by points. May contain a single "b" for beta versions, which are counted
* as older (so 0.8.7b4 is older than 0.8.7).
*/
- public static final String VERSION = "0.26-b6"; // Remember changing the version in the help
+ public static final String VERSION = "0.26"; // Remember changing the version in the help
/**
* Enable Version Checker (if you compile and distribute this yourself, you
diff --git a/src/chatty/gui/components/help/help-releases.html b/src/chatty/gui/components/help/help-releases.html
index 8e7608b02..6ff395be7 100644
--- a/src/chatty/gui/components/help/help-releases.html
+++ b/src/chatty/gui/components/help/help-releases.html
@@ -76,12 +76,46 @@
full list of changes.
-
-TBD
-
+ Custom Tabs
+ Custom Tabs allow you to route message from regular channels to a Custom Tab based on Highlight matching rules.
+ The new to:
prefix (for Highlights, Ignore, Msg. Colours and the new Routing setting) specifies the name of the Custom Tab you want to route the matching messages to. For example adding to:Mentions regw:yourname
to the Custom Tabs Routing list will copy all messages containing the word "yourname" to a tab called "Mentions".
+ There are also options to copy the text of Desktop Notifications to a Custom Tab, logging Custom Tab messages to a file and more.
+ Message History
+ Added a message history on channel join, which can be enabled in the Settings under "History". It can show regular chat messages from the last 24h for many channels based on the recent-messages.robotty.de API.
+ Channel Moderation Panel
+ Added an "M" button to the input field that opens a panel for changing channel modes (available only for moderators).
+ Twitch Features
+
+ Added command /createClip
for creating a clip on the current channel
+ Added support for Content Classification Labels to Admin Dialog
+
+ Other
+
+ Added support for animated overlayed emotes and 7TV zero-width-emotes
+ Added prefix config:afterban
to match on messages after the user was banned/timed out (if Chatty is aware of it)
+ Added prefix msgs:
to match on the user's past messages (as they are shown in the User Dialog)
+ Added command /userinfoRecent
to open User Info Dialog for user recently affected by a Twitch Command or with recently open User Dialog
+ Updated Pronouns service to new API
+ Added Look&Feel MacOS options (screen menubar, system colors for title bar)
+ Added setting under "Window" to toggle input length limits
+ Various GUI improvements
+ Updated help
+
+ Bugfixes
+
+ Fixed error in context menu for some types of messages
+ Fixed AutoMod Dialog hotkeys reacting when they shouldn't when docked as a tab
+ Fixed favorited emotes not being detected as usable in some cases
+ Fixed PageUp/PageDown sometimes scrolling in wrong channel (by adding as configurable hotkey acting on the active channel)
+ Fixed "Join"-entry being shown in URL context menu when it shouldn't
+ Fixed "User to never highlight" setting not applying to info messages with an attached user
+ Fixed default tab highlight/unread colors not updating when changing from dark to light LaF
+ Fixed tab joined state not always showing correctly after reconnecting
+ Fixed links that are displayed over several lines being clickable in empty spaces in between
+
Version 0.25 (2023-07-21)
diff --git a/src/chatty/gui/components/help/help.html b/src/chatty/gui/components/help/help.html
index 322c153a9..1ab29dc95 100644
--- a/src/chatty/gui/components/help/help.html
+++ b/src/chatty/gui/components/help/help.html
@@ -5,7 +5,7 @@
-
+
diff --git a/src/chatty/gui/components/help/style.css b/src/chatty/gui/components/help/style.css
index d1a1901b3..ace2ce9a5 100644
--- a/src/chatty/gui/components/help/style.css
+++ b/src/chatty/gui/components/help/style.css
@@ -3,7 +3,7 @@ body {
font-size: 1em;
color: black;
background-color: #FDFDFD;
- font-family: Arial, sans-serif;
+ font-family: sans-serif;
padding: 10px;
margin: 0;
}
From cf4d25636a239e204d1206773d9c4ac1cd4807b2 Mon Sep 17 00:00:00 2001
From: tduva
Date: Thu, 21 Mar 2024 13:36:01 +0100
Subject: [PATCH 5/5] Make RecentlyAffectedUsers threadsafe
---
src/chatty/util/RecentlyAffectedUsers.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/chatty/util/RecentlyAffectedUsers.java b/src/chatty/util/RecentlyAffectedUsers.java
index 1b6f5154b..c5c4b3312 100644
--- a/src/chatty/util/RecentlyAffectedUsers.java
+++ b/src/chatty/util/RecentlyAffectedUsers.java
@@ -21,7 +21,7 @@ public class RecentlyAffectedUsers {
*
* @param user
*/
- public static void addUser(User user) {
+ public synchronized static void addUser(User user) {
if (!users.containsKey(user.getChannel())) {
users.put(user.getChannel(), new UniqueLimitedRingBuffer<>(10));
}
@@ -34,7 +34,7 @@ public static void addUser(User user) {
* @param channel
* @return The {@code User} or {@code null} if none is present
*/
- public static User poll(String channel) {
+ public synchronized static User poll(String channel) {
if (users.containsKey(channel)) {
return users.get(channel).pollLast();
}