Skip to content

Commit

Permalink
Add an option to render Twitch emotes in all messages.
Browse files Browse the repository at this point in the history
Use `/twitch emotes renderEverywhere` to toggle this.
This closes #8.
Also, TwitchMessageHandler.processEmotes() now preserves whitespace.
  • Loading branch information
mini-bomba committed Jan 9, 2022
1 parent b885251 commit 8048971
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/main/java/me/mini_bomba/streamchatmod/StreamChatMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.github.twitch4j.helix.domain.*;
import com.github.twitch4j.tmi.domain.Chatters;
import com.sun.net.httpserver.HttpServer;
import lombok.Getter;
import me.mini_bomba.streamchatmod.asm.hooks.FontRendererHook;
import me.mini_bomba.streamchatmod.commands.TwitchChatCommand;
import me.mini_bomba.streamchatmod.commands.TwitchCommand;
Expand Down Expand Up @@ -73,6 +74,7 @@ public class StreamChatMod {
@Nullable
private CredentialManager twitchCredentialManager = null;
@Nullable
@Getter
private String twitchUsername = null;
@Nullable
private List<String> twitchScopes = null;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/me/mini_bomba/streamchatmod/StreamConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class StreamConfig {
public final Property subOnlyFormatting;
public final Property minecraftChatPrefix;
public final Property allowMessageDeletion;
public final Property showEmotesEverywhere;
// tokens
protected final Property twitchToken;
// twitch
Expand Down Expand Up @@ -62,6 +63,7 @@ public StreamConfig(File configFile) {
subOnlyFormatting = config.get("common", "subOnlyFormatting", false);
minecraftChatPrefix = config.get("common", "minecraftChatPrefix", "!!");
allowMessageDeletion = config.get("common", "allowMessageDeletion", true);
showEmotesEverywhere = config.get("common", "showEmotesEverywhere", false);
// tokens
twitchToken = config.get("tokens", "twitch", "");
// twitch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public boolean isGlobalEmote(String name) {
}

public boolean isChannelEmote(String channelId, String name) {
if (channelId == null) return false;
return channelEmotes.containsKey(channelId) && channelEmotes.get(channelId).containsKey(name);
}

Expand Down Expand Up @@ -691,7 +692,6 @@ private TwitchBadgeGlobal(ChatBadgeSet set, ChatBadge badge) {
this.id = TwitchGlobalBadge.getBadgeId(badge);
}
}

private static class TwitchBadgeChannel {
public final ChatBadgeSet set;
public final ChatBadge badge;
Expand Down
49 changes: 47 additions & 2 deletions src/main/java/me/mini_bomba/streamchatmod/StreamEvents.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package me.mini_bomba.streamchatmod;

import com.github.twitch4j.helix.domain.User;
import me.mini_bomba.streamchatmod.commands.IDrawsChatOutline;
import me.mini_bomba.streamchatmod.events.LocalMessageEvent;
import me.mini_bomba.streamchatmod.runnables.TwitchMessageHandler;
import me.mini_bomba.streamchatmod.tweaker.TransformerField;
import me.mini_bomba.streamchatmod.utils.ChatComponentStreamEmote;
import net.minecraft.client.gui.GuiChat;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
import net.minecraftforge.client.event.ClientChatReceivedEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
Expand All @@ -15,6 +22,7 @@

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

public class StreamEvents {

Expand Down Expand Up @@ -96,13 +104,50 @@ public void onLocalMinecraftMessage(LocalMessageEvent event) {
}
event.setCanceled(true);
if (mod.twitch == null || mod.twitchSender == null || !mod.config.twitchEnabled.getBoolean() || mod.twitchSender.getChat() == null) {
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!");
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!");
return;
}
if (mod.config.twitchSelectedChannel.getString().length() == 0) {
StreamUtils.addMessage(EnumChatFormatting.RED+"The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but no channel is selected!");
StreamUtils.addMessage(EnumChatFormatting.RED + "The message was not sent anywhere: Chat mode is set to 'Redirect to Twitch', but no channel is selected!");
return;
}
mod.twitchSender.getChat().sendMessage(mod.config.twitchSelectedChannel.getString(), event.message);
}

@SubscribeEvent
public void onMessage(ClientChatReceivedEvent event) {
String channel = mod.config.twitchSelectedChannel.getString();
if (channel.length() == 0) channel = mod.getTwitchUsername();
if (mod.config.showEmotesEverywhere.getBoolean()) {
User user = channel == null ? null : mod.getTwitchUserByName(channel);
event.message = transformComponent(event.message, user == null ? null : user.getId());
}
}

public IChatComponent transformComponent(IChatComponent component, String channelId) {
IChatComponent newComponent;
if (component instanceof ChatComponentText) {
List<IChatComponent> components = TwitchMessageHandler.processEmotes(mod, component.getUnformattedTextForChat(), channelId);
components.stream().filter(c -> !(c instanceof ChatComponentStreamEmote)).forEach(c -> c.setChatStyle(component.getChatStyle().createShallowCopy()));
if (components.size() > 0) {
newComponent = components.get(0);
components.remove(0);
for (IChatComponent c : components) newComponent.appendSibling(c);
} else {
newComponent = new ChatComponentText("");
newComponent.setChatStyle(component.getChatStyle().createShallowCopy());
}
} else if (component instanceof ChatComponentTranslation) {
ChatComponentTranslation castedComponent = (ChatComponentTranslation) component;
newComponent = new ChatComponentTranslation(castedComponent.getKey(), Arrays.stream(castedComponent.getFormatArgs()).map(c -> c instanceof IChatComponent ? transformComponent((IChatComponent) c, channelId) : c).toArray());
newComponent.setChatStyle(castedComponent.getChatStyle().createShallowCopy());
} else {
newComponent = component.createCopy();
newComponent.getSiblings().clear();
}
for (IChatComponent sibling : component.getSiblings()) {
newComponent.appendSibling(transformComponent(sibling, channelId));
}
return newComponent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,36 @@ public void processSubcommand(ICommandSender sender, String[] args) throws Comma
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes " + type.name().toLowerCase())))).collect(Collectors.toList()));
components.add(new ChatComponentText(EnumChatFormatting.AQUA + "Animated emotes: " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "Enabled" : EnumChatFormatting.RED + "Disabled"))
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes animated"))));
components.add(new ChatComponentText(EnumChatFormatting.AQUA + "Render emotes everywhere: " + (mod.config.showEmotesEverywhere.getBoolean() ? EnumChatFormatting.GREEN + "Enabled" : EnumChatFormatting.RED + "Disabled"))
.setChatStyle(new ChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/twitch emotes renderEverywhere"))));
components.add(new ChatComponentText(EnumChatFormatting.GRAY + "Used internal emote slots: " + EnumChatFormatting.AQUA + StreamEmote.getEmoteCount() + EnumChatFormatting.GRAY + "/2048"));
StreamUtils.addMessages(sender, components.toArray(new IChatComponent[0]));
} else {
if (args[0].equalsIgnoreCase("animated")) {
if (args.length == 1)
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Animated emotes are currently " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "enabled" : EnumChatFormatting.RED + "eisabled"));
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Animated emotes are currently " + (mod.config.allowAnimatedEmotes.getBoolean() ? EnumChatFormatting.GREEN + "enabled" : EnumChatFormatting.RED + "disabled"));
else {
Boolean newState = StreamUtils.readStringAsBoolean(args[1]);
if (newState == null)
throw new CommandException("Invalid boolean value: " + args[1]);
else {
mod.config.allowAnimatedEmotes.set(newState);
FontRendererHook.setAllowAnimated(newState);
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Animated emotes have been " + (newState ? "enabled" : "eisabled"));
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Animated emotes have been " + (newState ? "enabled" : "disabled"));
}
}
return;
}
if (args[0].equalsIgnoreCase("renderEverywhere")) {
if (args.length == 1)
StreamUtils.addMessage(EnumChatFormatting.AQUA + "Twitch emotes are currently rendered " + (mod.config.showEmotesEverywhere.getBoolean() ? EnumChatFormatting.GREEN + "everywhere" : EnumChatFormatting.LIGHT_PURPLE + "only in Twitch chat"));
else {
Boolean newState = StreamUtils.readStringAsBoolean(args[1]);
if (newState == null)
throw new CommandException("Invalid boolean value: " + args[1]);
else {
mod.config.showEmotesEverywhere.set(newState);
StreamUtils.addMessage(EnumChatFormatting.GREEN + "Twitch emotes are now rendered " + (newState ? "everywhere" : "only in Twitch chat"));
}
}
return;
Expand Down Expand Up @@ -108,7 +124,7 @@ public void processSubcommand(ICommandSender sender, String[] args) throws Comma
@Override
public List<String> getAutocompletions(String[] args) {
if (args.length == 1) {
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());
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());
if (matchingTypes.size() == 1 && matchingTypes.get(0).equals(args[0]))
matchingTypes = StreamUtils.singletonModifiableList(matchingTypes.get(0) + " ");
return matchingTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class TwitchMessageHandler implements Runnable {
private static final char formatChar = '\u00a7';
private static final String validFormats = "0123456789abcdefklmnorABCDEFKLMNORzZ";
public static final Pattern urlPattern = Pattern.compile("https?://[^.\\s/]+(?:\\.[^.\\s/]+)+\\S*");
public static final Pattern whitespacePattern = Pattern.compile("\\s+");
private static final Pattern formatCodePattern = Pattern.compile(formatChar + "[0-9a-fA-Fk-rK-RzZ]");
private static final String clipsDomain = "https://clips.twitch.tv/";

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

private List<IChatComponent> processEmotes(String message) {
public static List<IChatComponent> processEmotes(StreamChatMod mod, String message, String channelId) {
List<IChatComponent> result = new LinkedList<>();
List<String> nextComponent = new LinkedList<>();
char color = 0;
char format = 0;
char nextColor = 0;
char nextFormat = 0;
Matcher whitespace = whitespacePattern.matcher(message);
if (message.length() > 0 && whitespacePattern.matcher(message.substring(0, 1)).find() && whitespace.find())
nextComponent.add(whitespace.group());
for (String word : StringUtils.split(message, " \n\t")) {
if (mod.emotes.isEmote(event.getChannel().getId(), word)) {
if (mod.emotes.isEmote(channelId, word)) {
if (nextComponent.size() > 0)
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + (result.size() == 0 ? "" : " ") + String.join(" ", nextComponent) + " "));
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + String.join("", nextComponent)));
nextComponent.clear();
if (whitespace.find()) nextComponent.add(whitespace.group());
color = nextColor;
format = nextFormat;
result.add(new ChatComponentStreamEmote(mod, mod.emotes.getEmote(event.getChannel().getId(), word)));
result.add(new ChatComponentStreamEmote(mod, mod.emotes.getEmote(channelId, word)));
} else {
Matcher formatMatcher = formatCodePattern.matcher(word);
while (formatMatcher.find()) {
Expand All @@ -82,13 +87,18 @@ private List<IChatComponent> processEmotes(String message) {
}
}
nextComponent.add(word);
if (whitespace.find()) nextComponent.add(whitespace.group());
}
}
if (nextComponent.size() > 0)
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + (result.size() == 0 ? "" : " ") + String.join(" ", nextComponent)));
result.add(new ChatComponentText((color != 0 ? "" + formatChar + color : "") + (format != 0 ? "" + formatChar + format : "") + String.join("", nextComponent)));
return result;
}

private List<IChatComponent> processEmotes(String message) {
return processEmotes(mod, message, event.getChannel().getId());
}

@Override
public void run() {
boolean showChannel = mod.config.forceShowChannelName.getBoolean() || (mod.twitch != null && mod.twitch.getChat().getChannels().size() > 1);
Expand Down

0 comments on commit 8048971

Please sign in to comment.