Skip to content

Commit 8048971

Browse files
committed
Add an option to render Twitch emotes in all messages.
Use `/twitch emotes renderEverywhere` to toggle this. This closes #8. Also, TwitchMessageHandler.processEmotes() now preserves whitespace.
1 parent b885251 commit 8048971

File tree

6 files changed

+86
-11
lines changed

6 files changed

+86
-11
lines changed

src/main/java/me/mini_bomba/streamchatmod/StreamChatMod.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.github.twitch4j.helix.domain.*;
1313
import com.github.twitch4j.tmi.domain.Chatters;
1414
import com.sun.net.httpserver.HttpServer;
15+
import lombok.Getter;
1516
import me.mini_bomba.streamchatmod.asm.hooks.FontRendererHook;
1617
import me.mini_bomba.streamchatmod.commands.TwitchChatCommand;
1718
import me.mini_bomba.streamchatmod.commands.TwitchCommand;
@@ -73,6 +74,7 @@ public class StreamChatMod {
7374
@Nullable
7475
private CredentialManager twitchCredentialManager = null;
7576
@Nullable
77+
@Getter
7678
private String twitchUsername = null;
7779
@Nullable
7880
private List<String> twitchScopes = null;

src/main/java/me/mini_bomba/streamchatmod/StreamConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class StreamConfig {
2222
public final Property subOnlyFormatting;
2323
public final Property minecraftChatPrefix;
2424
public final Property allowMessageDeletion;
25+
public final Property showEmotesEverywhere;
2526
// tokens
2627
protected final Property twitchToken;
2728
// twitch
@@ -62,6 +63,7 @@ public StreamConfig(File configFile) {
6263
subOnlyFormatting = config.get("common", "subOnlyFormatting", false);
6364
minecraftChatPrefix = config.get("common", "minecraftChatPrefix", "!!");
6465
allowMessageDeletion = config.get("common", "allowMessageDeletion", true);
66+
showEmotesEverywhere = config.get("common", "showEmotesEverywhere", false);
6567
// tokens
6668
twitchToken = config.get("tokens", "twitch", "");
6769
// twitch

src/main/java/me/mini_bomba/streamchatmod/StreamEmotes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public boolean isGlobalEmote(String name) {
6565
}
6666

6767
public boolean isChannelEmote(String channelId, String name) {
68+
if (channelId == null) return false;
6869
return channelEmotes.containsKey(channelId) && channelEmotes.get(channelId).containsKey(name);
6970
}
7071

@@ -691,7 +692,6 @@ private TwitchBadgeGlobal(ChatBadgeSet set, ChatBadge badge) {
691692
this.id = TwitchGlobalBadge.getBadgeId(badge);
692693
}
693694
}
694-
695695
private static class TwitchBadgeChannel {
696696
public final ChatBadgeSet set;
697697
public final ChatBadge badge;

src/main/java/me/mini_bomba/streamchatmod/StreamEvents.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package me.mini_bomba.streamchatmod;
22

3+
import com.github.twitch4j.helix.domain.User;
34
import me.mini_bomba.streamchatmod.commands.IDrawsChatOutline;
45
import me.mini_bomba.streamchatmod.events.LocalMessageEvent;
6+
import me.mini_bomba.streamchatmod.runnables.TwitchMessageHandler;
57
import me.mini_bomba.streamchatmod.tweaker.TransformerField;
8+
import me.mini_bomba.streamchatmod.utils.ChatComponentStreamEmote;
69
import net.minecraft.client.gui.GuiChat;
710
import net.minecraft.client.gui.GuiTextField;
11+
import net.minecraft.util.ChatComponentText;
12+
import net.minecraft.util.ChatComponentTranslation;
813
import net.minecraft.util.EnumChatFormatting;
14+
import net.minecraft.util.IChatComponent;
15+
import net.minecraftforge.client.event.ClientChatReceivedEvent;
916
import net.minecraftforge.client.event.GuiScreenEvent;
1017
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
1118
import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -15,6 +22,7 @@
1522

1623
import java.lang.reflect.Field;
1724
import java.util.Arrays;
25+
import java.util.List;
1826

1927
public class StreamEvents {
2028

@@ -96,13 +104,50 @@ public void onLocalMinecraftMessage(LocalMessageEvent event) {
96104
}
97105
event.setCanceled(true);
98106
if (mod.twitch == null || mod.twitchSender == null || !mod.config.twitchEnabled.getBoolean() || mod.twitchSender.getChat() == null) {
99-
StreamUtils.addMessage(EnumChatFormatting.RED+"The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but Twitch chat (or part of it) is disabled!");
107+
StreamUtils.addMessage(EnumChatFormatting.RED + "The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but Twitch chat (or part of it) is disabled!");
100108
return;
101109
}
102110
if (mod.config.twitchSelectedChannel.getString().length() == 0) {
103-
StreamUtils.addMessage(EnumChatFormatting.RED+"The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but no channel is selected!");
111+
StreamUtils.addMessage(EnumChatFormatting.RED + "The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but no channel is selected!");
104112
return;
105113
}
106114
mod.twitchSender.getChat().sendMessage(mod.config.twitchSelectedChannel.getString(), event.message);
107115
}
116+
117+
@SubscribeEvent
118+
public void onMessage(ClientChatReceivedEvent event) {
119+
String channel = mod.config.twitchSelectedChannel.getString();
120+
if (channel.length() == 0) channel = mod.getTwitchUsername();
121+
if (mod.config.showEmotesEverywhere.getBoolean()) {
122+
User user = channel == null ? null : mod.getTwitchUserByName(channel);
123+
event.message = transformComponent(event.message, user == null ? null : user.getId());
124+
}
125+
}
126+
127+
public IChatComponent transformComponent(IChatComponent component, String channelId) {
128+
IChatComponent newComponent;
129+
if (component instanceof ChatComponentText) {
130+
List<IChatComponent> components = TwitchMessageHandler.processEmotes(mod, component.getUnformattedTextForChat(), channelId);
131+
components.stream().filter(c -> !(c instanceof ChatComponentStreamEmote)).forEach(c -> c.setChatStyle(component.getChatStyle().createShallowCopy()));
132+
if (components.size() > 0) {
133+
newComponent = components.get(0);
134+
components.remove(0);
135+
for (IChatComponent c : components) newComponent.appendSibling(c);
136+
} else {
137+
newComponent = new ChatComponentText("");
138+
newComponent.setChatStyle(component.getChatStyle().createShallowCopy());
139+
}
140+
} else if (component instanceof ChatComponentTranslation) {
141+
ChatComponentTranslation castedComponent = (ChatComponentTranslation) component;
142+
newComponent = new ChatComponentTranslation(castedComponent.getKey(), Arrays.stream(castedComponent.getFormatArgs()).map(c -> c instanceof IChatComponent ? transformComponent((IChatComponent) c, channelId) : c).toArray());
143+
newComponent.setChatStyle(castedComponent.getChatStyle().createShallowCopy());
144+
} else {
145+
newComponent = component.createCopy();
146+
newComponent.getSiblings().clear();
147+
}
148+
for (IChatComponent sibling : component.getSiblings()) {
149+
newComponent.appendSibling(transformComponent(sibling, channelId));
150+
}
151+
return newComponent;
152+
}
108153
}

src/main/java/me/mini_bomba/streamchatmod/commands/subcommands/TwitchEmotesSubcommand.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,36 @@ public void processSubcommand(ICommandSender sender, String[] args) throws Comma
6767
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes " + type.name().toLowerCase())))).collect(Collectors.toList()));
6868
components.add(new ChatComponentText(EnumChatFormatting.AQUA + "Animated emotes: " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "Enabled" : EnumChatFormatting.RED + "Disabled"))
6969
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes animated"))));
70+
components.add(new ChatComponentText(EnumChatFormatting.AQUA + "Render emotes everywhere: " + (mod.config.showEmotesEverywhere.getBoolean() ? EnumChatFormatting.GREEN + "Enabled" : EnumChatFormatting.RED + "Disabled"))
71+
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes renderEverywhere"))));
7072
components.add(new ChatComponentText(EnumChatFormatting.GRAY + "Used internal emote slots: " + EnumChatFormatting.AQUA + StreamEmote.getEmoteCount() + EnumChatFormatting.GRAY + "/2048"));
7173
StreamUtils.addMessages(sender, components.toArray(new IChatComponent[0]));
7274
} else {
7375
if (args[0].equalsIgnoreCase("animated")) {
7476
if (args.length == 1)
75-
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Animated emotes are currently " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "enabled" : EnumChatFormatting.RED + "eisabled"));
77+
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Animated emotes are currently " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "enabled" : EnumChatFormatting.RED + "disabled"));
7678
else {
7779
Boolean newState = StreamUtils.readStringAsBoolean(args[1]);
7880
if (newState == null)
7981
throw new CommandException("Invalid boolean value: " + args[1]);
8082
else {
8183
mod.config.allowAnimatedEmotes.set(newState);
8284
FontRendererHook.setAllowAnimated(newState);
83-
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Animated emotes have been " + (newState ? "enabled" : "eisabled"));
85+
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Animated emotes have been " + (newState ? "enabled" : "disabled"));
86+
}
87+
}
88+
return;
89+
}
90+
if (args[0].equalsIgnoreCase("renderEverywhere")) {
91+
if (args.length == 1)
92+
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Twitch emotes are currently rendered " + (mod.config.showEmotesEverywhere.getBoolean() ? EnumChatFormatting.GREEN + "everywhere" : EnumChatFormatting.LIGHT_PURPLE + "only in Twitch chat"));
93+
else {
94+
Boolean newState = StreamUtils.readStringAsBoolean(args[1]);
95+
if (newState == null)
96+
throw new CommandException("Invalid boolean value: " + args[1]);
97+
else {
98+
mod.config.showEmotesEverywhere.set(newState);
99+
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Twitch emotes are now rendered " + (newState ? "everywhere" : "only in Twitch chat"));
84100
}
85101
}
86102
return;
@@ -108,7 +124,7 @@ public void processSubcommand(ICommandSender sender, String[] args) throws Comma
108124
@Override
109125
public List<String> getAutocompletions(String[] args) {
110126
if (args.length == 1) {
111-
List<String> matchingTypes = Stream.concat(Arrays.stream(StreamEmote.Type.values()).map(type -> type.name().toLowerCase()), Stream.of("animated")).filter(name -> name.startsWith(args[0].toLowerCase())).collect(Collectors.toList());
127+
List<String> matchingTypes = Stream.concat(Arrays.stream(StreamEmote.Type.values()).map(type -> type.name().toLowerCase()), Stream.of("animated", "rendereverywhere")).filter(name -> name.startsWith(args[0].toLowerCase())).collect(Collectors.toList());
112128
if (matchingTypes.size() == 1 && matchingTypes.get(0).equals(args[0]))
113129
matchingTypes = StreamUtils.singletonModifiableList(matchingTypes.get(0) + " ");
114130
return matchingTypes;

src/main/java/me/mini_bomba/streamchatmod/runnables/TwitchMessageHandler.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class TwitchMessageHandler implements Runnable {
2929
private static final char formatChar = '\u00a7';
3030
private static final String validFormats = "0123456789abcdefklmnorABCDEFKLMNORzZ";
3131
public static final Pattern urlPattern = Pattern.compile("https?://[^.\\s/]+(?:\\.[^.\\s/]+)+\\S*");
32+
public static final Pattern whitespacePattern = Pattern.compile("\\s+");
3233
private static final Pattern formatCodePattern = Pattern.compile(formatChar + "[0-9a-fA-Fk-rK-RzZ]");
3334
private static final String clipsDomain = "https://clips.twitch.tv/";
3435

@@ -52,21 +53,25 @@ private String processColorCodes(String message, boolean allowFormatting) {
5253
return message;
5354
}
5455

55-
private List<IChatComponent> processEmotes(String message) {
56+
public static List<IChatComponent> processEmotes(StreamChatMod mod, String message, String channelId) {
5657
List<IChatComponent> result = new LinkedList<>();
5758
List<String> nextComponent = new LinkedList<>();
5859
char color = 0;
5960
char format = 0;
6061
char nextColor = 0;
6162
char nextFormat = 0;
63+
Matcher whitespace = whitespacePattern.matcher(message);
64+
if (message.length() > 0 && whitespacePattern.matcher(message.substring(0, 1)).find() && whitespace.find())
65+
nextComponent.add(whitespace.group());
6266
for (String word : StringUtils.split(message, " \n\t")) {
63-
if (mod.emotes.isEmote(event.getChannel().getId(), word)) {
67+
if (mod.emotes.isEmote(channelId, word)) {
6468
if (nextComponent.size() > 0)
65-
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + (result.size() == 0 ? "" : " ") + String.join(" ", nextComponent) + " "));
69+
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + String.join("", nextComponent)));
6670
nextComponent.clear();
71+
if (whitespace.find()) nextComponent.add(whitespace.group());
6772
color = nextColor;
6873
format = nextFormat;
69-
result.add(new ChatComponentStreamEmote(mod, mod.emotes.getEmote(event.getChannel().getId(), word)));
74+
result.add(new ChatComponentStreamEmote(mod, mod.emotes.getEmote(channelId, word)));
7075
} else {
7176
Matcher formatMatcher = formatCodePattern.matcher(word);
7277
while (formatMatcher.find()) {
@@ -82,13 +87,18 @@ private List<IChatComponent> processEmotes(String message) {
8287
}
8388
}
8489
nextComponent.add(word);
90+
if (whitespace.find()) nextComponent.add(whitespace.group());
8591
}
8692
}
8793
if (nextComponent.size() > 0)
88-
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + (result.size() == 0 ? "" : " ") + String.join(" ", nextComponent)));
94+
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + String.join("", nextComponent)));
8995
return result;
9096
}
9197

98+
private List<IChatComponent> processEmotes(String message) {
99+
return processEmotes(mod, message, event.getChannel().getId());
100+
}
101+
92102
@Override
93103
public void run() {
94104
boolean showChannel = mod.config.forceShowChannelName.getBoolean() || (mod.twitch != null && mod.twitch.getChat().getChannels().size() > 1);

0 commit comments

Comments
 (0)