diff --git a/src/chatty/TwitchClient.java b/src/chatty/TwitchClient.java index 035fc1dcd..8fbf1aa44 100644 --- a/src/chatty/TwitchClient.java +++ b/src/chatty/TwitchClient.java @@ -1138,7 +1138,7 @@ else if (command.equals("bantest")) { args.add("tirean"); args.add("300"); args.add("still not using LiveSplit Autosplitter D:"); - g.printModerationAction(new ModeratorActionData("", "", "tduvatest", "timeout", args, "tduva")); + g.printModerationAction(new ModeratorActionData("", "", "tduvatest", "timeout", args, "tduva"), false); } else if (command.equals("loadsoferrors")) { for (int i=0;i<10000;i++) { SwingUtilities.invokeLater(new Runnable() { @@ -1623,8 +1623,13 @@ private class PubSubResults implements PubSubListener { public void messageReceived(Message message) { if (message.data != null && message.data instanceof ModeratorActionData) { ModeratorActionData data = (ModeratorActionData)message.data; - g.printModerationAction(data); - chatLog.modAction(data); + if (data.stream != null) { + g.printModerationAction(data, data.created_by.equals(c.getUsername())); + chatLog.modAction(data); + User user = c.getUser(Helper.toChannel(data.stream), data.created_by); + user.addModAction(data.getCommandAndParameters()); + g.updateUserinfo(user); + } } } @@ -2144,8 +2149,16 @@ public void aboutToSaveSettings(Settings settings) { private class Messages implements TwitchConnection.ConnectionListener { + private void checkModLogListen(User user) { + if (user.hasChannelModeratorRights() && user.nick.equals(c.getUsername())) { + pubsub.setLocalUsername(c.getUsername()); + pubsub.listenModLog(Helper.toStream(user.getChannel()), settings.getString("token")); + } + } + @Override - public void onChannelJoined(String channel) { + public void onChannelJoined(User user) { + String channel = user.getChannel(); channelFavorites.addChannelToHistory(channel); g.printLine(channel,"You have joined " + channel); @@ -2154,6 +2167,7 @@ public void onChannelJoined(String channel) { api.requestChatIcons(Helper.toStream(channel), false); requestChannelEmotes(channel); frankerFaceZ.joined(channel); + checkModLogListen(user); } @Override @@ -2161,6 +2175,7 @@ public void onChannelLeft(String channel) { chatLog.info(channel, "You have left "+channel); closeChannel(channel); frankerFaceZ.left(channel); + pubsub.unlistenModLog(Helper.toStream(channel)); } @Override @@ -2188,6 +2203,7 @@ public void onUserUpdated(User user) { g.updateUser(user); } g.updateUserinfo(user); + checkModLogListen(user); } @Override @@ -2287,7 +2303,7 @@ public void onBan(User user, long duration, String reason) { @Override public void onRegistered() { g.updateHighlightSetUsername(c.getUsername()); - pubsub.listenModLog(c.getUsername(), settings.getString("token")); + //pubsub.listenModLog(c.getUsername(), settings.getString("token")); } @Override diff --git a/src/chatty/TwitchConnection.java b/src/chatty/TwitchConnection.java index 9edfb071c..075f2f76c 100644 --- a/src/chatty/TwitchConnection.java +++ b/src/chatty/TwitchConnection.java @@ -715,10 +715,10 @@ void onJoin(String channel, String nick, String prefix) { */ joinChecker.cancel(channel); debug("JOINED: " + channel); + User user = userJoined(channel, nick); if (this == irc && !onChannel(channel)) { - listener.onChannelJoined(channel); + listener.onChannelJoined(user); } - userJoined(channel, nick); joinedChannels.add(channel); } else { /** @@ -1285,7 +1285,7 @@ public interface ConnectionListener { void onJoinAttempt(String channel); - void onChannelJoined(String channel); + void onChannelJoined(User channel); void onChannelLeft(String channel); diff --git a/src/chatty/User.java b/src/chatty/User.java index 7330a4838..5790d26f2 100644 --- a/src/chatty/User.java +++ b/src/chatty/User.java @@ -232,6 +232,10 @@ public synchronized void addSub(String message, int months) { addLine(new SubMessage(System.currentTimeMillis(), message, months)); } + public synchronized void addModAction(String commandAndParameters) { + addLine(new ModAction(System.currentTimeMillis(), commandAndParameters)); + } + /** * Adds a Message. * @@ -776,6 +780,7 @@ public static class Message { public static final int MESSAGE = 0; public static final int BAN = 1; public static final int SUB = 2; + public static final int MOD_ACTION = 3; private final Long time; private final int type; @@ -840,6 +845,17 @@ public SubMessage(Long time, String message, int months) { } } + public static class ModAction extends Message { + + public final String commandAndParameters; + + public ModAction(Long time, String commandAndParameters) { + super(MOD_ACTION, time); + this.commandAndParameters = commandAndParameters; + } + + } + // public static final void main(String[] args) { // ArrayList list = new ArrayList<>(); // for (int i=0;i<100000;i++) { diff --git a/src/chatty/gui/MainGui.java b/src/chatty/gui/MainGui.java index 7419b1442..484db33a4 100644 --- a/src/chatty/gui/MainGui.java +++ b/src/chatty/gui/MainGui.java @@ -1771,6 +1771,7 @@ public void stateChanged(ChangeEvent e) { state.update(true); updateChannelInfoDialog(); emotesDialog.updateStream(channels.getLastActiveChannel().getStreamName()); + moderationLog.setChannel(channels.getLastActiveChannel().getStreamName()); } } @@ -2833,14 +2834,15 @@ public void run() { }); } - public void printModerationAction(final ModeratorActionData data) { + public void printModerationAction(final ModeratorActionData data, + final boolean ownAction) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { moderationLog.add(data); String channel = Helper.toValidChannel(data.stream); - if (client.settings.getBoolean("showModActions") && channels.isChannel(channel)) { + if (!ownAction && client.settings.getBoolean("showModActions") && channels.isChannel(channel)) { channels.getChannel(channel).printLine(String.format("[ModAction] %s: /%s %s", data.created_by, data.moderation_action, diff --git a/src/chatty/gui/components/ModerationLog.java b/src/chatty/gui/components/ModerationLog.java index 6afe87be3..5482e760f 100644 --- a/src/chatty/gui/components/ModerationLog.java +++ b/src/chatty/gui/components/ModerationLog.java @@ -6,6 +6,10 @@ import chatty.util.StringUtil; import chatty.util.api.pubsub.ModeratorActionData; import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.swing.JDialog; import javax.swing.JScrollBar; import javax.swing.JScrollPane; @@ -26,6 +30,10 @@ public class ModerationLog extends JDialog { private final JTextArea log; private final JScrollPane scroll; + private final Map> cache = new HashMap<>(); + + private String currentChannel; + public ModerationLog(MainGui owner) { super(owner); log = createLogArea(); @@ -50,26 +58,57 @@ private static JTextArea createLogArea() { return text; } + public void setChannel(String channel) { + if (channel != null && !channel.equals(currentChannel)) { + currentChannel = channel; + setTitle("Moderation Actions ("+channel+")"); + + List cached = cache.get(channel); + if (cached != null) { + StringBuilder b = new StringBuilder(); + for (String line : cached) { + b.append(line); + b.append("\n"); + } + log.setText(b.toString()); + scrollDown(); + } else { + log.setText(null); + } + } + } + public void add(ModeratorActionData data) { - if (data.stream != null) { - setTitle("Moderator Actions ("+data.stream+")"); + if (data.stream == null) { + return; } - String line = String.format("%s: /%s %s", + String channel = data.stream; + + String line = String.format("[%s] %s: /%s %s", + DateTime.currentTime(), data.created_by, data.moderation_action, StringUtil.join(data.args," ")); - printLine(log, line); + + if (channel.equals(currentChannel)) { + printLine(log, line); + } + + if (!cache.containsKey(channel)) { + cache.put(channel, new ArrayList()); + } + cache.get(channel).add(line); } private void printLine(JTextArea text, String line) { try { Document doc = text.getDocument(); - String linebreak = doc.getLength() > 0 ? "\n" : ""; - doc.insertString(doc.getLength(), linebreak+"["+DateTime.currentTime()+"] "+line, null); + String linebreak = "\n"; + doc.insertString(doc.getLength(), line+linebreak, null); JScrollBar bar = scroll.getVerticalScrollBar(); boolean scrollDown = bar.getValue() == bar.getMaximum() - bar.getVisibleAmount(); if (scrollDown) { - text.setCaretPosition(doc.getLength()); + scrollDown(); } clearSomeChat(doc); } catch (BadLocationException e) { @@ -77,6 +116,10 @@ private void printLine(JTextArea text, String line) { } } + private void scrollDown() { + log.setCaretPosition(log.getDocument().getLength()); + } + /** * Removes some lines from the given Document so it won't exceed the maximum * number of lines. @@ -96,7 +139,7 @@ public void clearSomeChat(Document doc) { * @param doc * @param amount */ - public void removeFirstLines(Document doc, int amount) { + private void removeFirstLines(Document doc, int amount) { if (amount < 1) { amount = 1; } diff --git a/src/chatty/gui/components/UserInfo.java b/src/chatty/gui/components/UserInfo.java index 7c13edf31..dce154b12 100644 --- a/src/chatty/gui/components/UserInfo.java +++ b/src/chatty/gui/components/UserInfo.java @@ -5,6 +5,7 @@ import chatty.User; import chatty.User.BanMessage; import chatty.User.Message; +import chatty.User.ModAction; import chatty.User.SubMessage; import chatty.User.TextMessage; import chatty.gui.GuiUtil; @@ -567,6 +568,13 @@ else if (sm.months > 1) { b.append(sm.message); b.append("\n"); } + else if (m.getType() == Message.MOD_ACTION) { + ModAction ma = (ModAction)m; + b.append(DateTime.format(m.getTime(), TIMESTAMP_SPECIAL)); + b.append("ModAction: /"); + b.append(ma.commandAndParameters); + b.append("\n"); + } } return b.toString(); } diff --git a/src/chatty/gui/components/help/help-releases.html b/src/chatty/gui/components/help/help-releases.html index 75ca749fc..95aab3866 100644 --- a/src/chatty/gui/components/help/help-releases.html +++ b/src/chatty/gui/components/help/help-releases.html @@ -53,7 +53,7 @@

- Added setting to save Addressbook to file on change - Some changes to the build process - Fixed bug with empty Whisper TAB appearing -- Added initial support for displaying mod actions (Broadcaster only) +- Added initial support for displaying mod actions (Broadcaster/Mods only) - Fixed some dialogs not reopening even with the appropriate setting diff --git a/src/chatty/gui/components/help/help-settings.html b/src/chatty/gui/components/help/help-settings.html index f0961274d..3d2be09db 100644 --- a/src/chatty/gui/components/help/help-settings.html +++ b/src/chatty/gui/components/help/help-settings.html @@ -202,6 +202,14 @@

Other

+ +
Show moderator actions in chat
+
Outputs extra messages for commands that moderators execute in the + channel, except for the commands you exexcute yourself.
+
This is only available for the broadcaster and moderators.
+
This is a Twitch Beta, so it may still change and break things.
+
To view mod actions you can also open a separate dialog via + Extra - Moderation Log.

Name Capitalization

@@ -930,7 +938,7 @@

Messages Types

VIEWERS: 12,521
Mod Actions
-
Logs the commands performed by mods in your channel (Broadcaster only).
+
Logs the commands performed by mods in your channel (Broadcaster/Mods only).
MOD_ACTION: tduva (host coollertmb)
diff --git a/src/chatty/gui/components/settings/MessageSettings.java b/src/chatty/gui/components/settings/MessageSettings.java index c6e2407a5..418456e6c 100644 --- a/src/chatty/gui/components/settings/MessageSettings.java +++ b/src/chatty/gui/components/settings/MessageSettings.java @@ -102,8 +102,8 @@ public MessageSettings(final SettingsDialog d) { otherSettingsPanel.add(d.addSimpleBooleanSetting( "showModActions", - "Show moderator actions in chat (Broadcaster only)", - "Show what commands mods perform in your channel (you can also open Extra - Moderation Log)"), + "Show moderator actions in chat (Broadcaster/Mods only) [Beta]", + "Show what commands mods perform, except your own (you can also open Extra - Moderation Log)"), d.makeGbc(0, 4, 3, 1, GridBagConstraints.WEST)); diff --git a/src/chatty/util/api/pubsub/Helper.java b/src/chatty/util/api/pubsub/Helper.java index 0ba1640f6..bb752c323 100644 --- a/src/chatty/util/api/pubsub/Helper.java +++ b/src/chatty/util/api/pubsub/Helper.java @@ -24,7 +24,7 @@ public static String createOutgoingMessage(String type, String nonce, Object dat public static String getStreamFromTopic(String topic, Map userIds) { try { - long userId = Long.valueOf(topic.substring(topic.indexOf(".")+1)); + long userId = Long.valueOf(topic.substring(topic.lastIndexOf(".")+1)); return userIds.get(userId); } catch (NumberFormatException ex) { return null; diff --git a/src/chatty/util/api/pubsub/Manager.java b/src/chatty/util/api/pubsub/Manager.java index 4eeea08d7..6f04a7a07 100644 --- a/src/chatty/util/api/pubsub/Manager.java +++ b/src/chatty/util/api/pubsub/Manager.java @@ -6,8 +6,10 @@ import chatty.util.api.UserIDs; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Logger; @@ -22,15 +24,18 @@ public class Manager { private final static Logger LOGGER = Logger.getLogger(Manager.class.getName()); - private final Client c; + private final TwitchApi api; + private final Client c; private final String server; - + private final Map userIds = Collections.synchronizedMap(new HashMap()); private final Map modLogListen = Collections.synchronizedMap(new HashMap()); - private Timer pingTimer; - private String token; - private final TwitchApi api; + + private volatile Timer pingTimer; + private volatile String token; + private volatile long localUserId = -1; + private volatile String localUsername; public Manager(String server, final PubSubListener listener, TwitchApi api) { this.api = api; @@ -44,6 +49,9 @@ public void handleReceived(String received) { if (message.type.equals("MESSAGE")) { listener.messageReceived(message); } + if (message.error != null && !message.error.isEmpty()) { + LOGGER.warning("[PubSub] Errror: "+message); + } } @Override @@ -54,12 +62,7 @@ public void handleSent(String sent) { @Override public void handleConnect() { startPinging(); - - for (Long userId : modLogListen.values()) { - if (userId != -1) { - sendListenModLog(userId); - } - } + sendAllTopics(); } @Override @@ -69,6 +72,20 @@ public void handleDisconnect() { }); } + private void sendAllTopics() { + Set listenTo = new HashSet<>(); + synchronized (modLogListen) { + for (Long userId : modLogListen.values()) { + if (userId != -1) { + listenTo.add(userId); + } + } + } + for (Long userId : listenTo) { + sendListenModLog(userId, true); + } + } + /** * Get a textual representation of the connection status for output to the * user. @@ -79,6 +96,13 @@ public String getStatus() { return c.getStatus(); } + public void setLocalUsername(String username) { + if (localUsername == null || !localUsername.equals(username)) { + this.localUsername = username; + this.localUserId = getUserId(username); + } + } + public void listenModLog(String username, String token) { if (!hasServer()) { return; @@ -90,8 +114,20 @@ public void listenModLog(String username, String token) { this.token = token; long userId = getUserId(username); modLogListen.put(username, userId); + LOGGER.info("[PubSub] LISTEN ModLog "+username+" "+userId); if (userId != -1) { - sendListenModLog(userId); + sendListenModLog(userId, true); + } + } + + public void unlistenModLog(String username) { + synchronized(modLogListen) { + if (modLogListen.containsKey(username)) { + if (modLogListen.get(username) != -1) { + sendListenModLog(modLogListen.get(username), false); + } + modLogListen.remove(username); + } } } @@ -107,30 +143,45 @@ public void setUserId(String username, long userId) { userIds.put(userId, username); return userId; } - LOGGER.info("[PubSub] Pending userId request for "+username); return -1; } - + /** + * The given userId is now known, so act on it if necessary. + * + * @param username + * @param userId + */ private void setUserId(String username, long userId) { userIds.put(userId, username); // Topics to still request - if (modLogListen.get(username) == -1) { + if (modLogListen.containsKey(username) && modLogListen.get(username) == -1) { modLogListen.put(username, userId); - sendListenModLog(userId); + sendListenModLog(userId, true); + } + + // If local userId hasn't been set yet, request everything now + if (localUserId == -1 && username.equals(localUsername)) { + localUserId = userId; + sendAllTopics(); } } - private void sendListenModLog(Long userId) { + private void sendListenModLog(Long userId, boolean listen) { + if (localUserId == -1) { + return; + } + JSONArray topics = new JSONArray(); - topics.add("chat_moderator_actions."+userId); + topics.add("chat_moderator_actions."+localUserId+"."+userId); JSONObject data = new JSONObject(); data.put("topics", topics); data.put("auth_token", token); connect(); - c.send(Helper.createOutgoingMessage("LISTEN", "", data)); + c.send(Helper.createOutgoingMessage(listen ? "LISTEN" : "UNLISTEN", "", data)); + LOGGER.info("[PubSub] "+(listen ? "LISTEN" : "UNLISTEN")+" ModLog "+userId); } private void startPinging() { diff --git a/src/chatty/util/api/pubsub/Message.java b/src/chatty/util/api/pubsub/Message.java index a0e9aadff..84dbb3a1a 100644 --- a/src/chatty/util/api/pubsub/Message.java +++ b/src/chatty/util/api/pubsub/Message.java @@ -64,4 +64,9 @@ public static Message fromJson(String json, Map userIds) { } } + @Override + public String toString() { + return type+"["+nonce+"/"+error+"/"+data+"]"; + } + } diff --git a/src/chatty/util/api/pubsub/ModeratorActionData.java b/src/chatty/util/api/pubsub/ModeratorActionData.java index 29f0bff9c..b8500df6f 100644 --- a/src/chatty/util/api/pubsub/ModeratorActionData.java +++ b/src/chatty/util/api/pubsub/ModeratorActionData.java @@ -1,6 +1,7 @@ package chatty.util.api.pubsub; +import chatty.util.StringUtil; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -59,4 +60,8 @@ public static ModeratorActionData decode(String topic, String message, Map