chore: added mod
Some checks are pending
Build and test / build-and-test (push) Waiting to run

This commit is contained in:
Yui
2024-12-01 15:30:21 -03:00
parent 4d4c8d953c
commit 354f647e54
22 changed files with 543 additions and 13 deletions

View File

@@ -0,0 +1,8 @@
package moe.yuyui.mcstreamerbot;
public class ClientProxy extends CommonProxy {
// Override CommonProxy methods here, if you want a different behaviour on the client (e.g. registering renders).
// Don't forget to call the super methods as well.
}

View File

@@ -0,0 +1,35 @@
package moe.yuyui.mcstreamerbot;
import net.minecraftforge.common.MinecraftForge;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import moe.yuyui.mcstreamerbot.events.minecraft.OnWorldJoin;
import moe.yuyui.mcstreamerbot.events.minecraft.OnWorldLeave;
public class CommonProxy {
// preInit "Run before anything else. Read your config, create blocks, items, etc, and register them with the
// GameRegistry." (Remove if not needed)
public void preInit(FMLPreInitializationEvent event) {
Config.synchronizeConfiguration(event.getSuggestedConfigurationFile());
MinecraftStreamerBotIntegration.LOG.info(Config.greeting);
MinecraftStreamerBotIntegration.LOG.info("I am MyMod at version " + Tags.VERSION);
}
// load "Do your mod setup. Build whatever data structures you care about. Register recipes." (Remove if not needed)
public void init(FMLInitializationEvent event) {
MinecraftForge.EVENT_BUS.register(new OnWorldJoin());
MinecraftForge.EVENT_BUS.register(new OnWorldLeave());
MinecraftStreamerBotIntegration.LOG.info("Loaded MCStreamerBot");
}
// postInit "Handle interaction with other mods, complete your setup based on this." (Remove if not needed)
public void postInit(FMLPostInitializationEvent event) {}
// register server commands in this event handler (Remove if not needed)
public void serverStarting(FMLServerStartingEvent event) {}
}

View File

@@ -0,0 +1,20 @@
package moe.yuyui.mcstreamerbot;
import java.io.File;
import net.minecraftforge.common.config.Configuration;
public class Config {
public static String greeting = "Hello World";
public static void synchronizeConfiguration(File configFile) {
Configuration configuration = new Configuration(configFile);
greeting = configuration.getString("greeting", Configuration.CATEGORY_GENERAL, greeting, "How shall I greet?");
if (configuration.hasChanged()) {
configuration.save();
}
}
}

View File

@@ -0,0 +1,53 @@
package moe.yuyui.mcstreamerbot;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import moe.yuyui.mcstreamerbot.common.EventListener;
@Mod(
modid = MinecraftStreamerBotIntegration.MODID,
version = Tags.VERSION,
name = "MyMod",
acceptedMinecraftVersions = "[1.7.10]")
public class MinecraftStreamerBotIntegration {
public static final String MODID = "mcstreamerbot";
public static final Logger LOG = LogManager.getLogger(MODID);
public static EventListener eventListener;
@SidedProxy(clientSide = "moe.yuyui.mcstreamerbot.ClientProxy", serverSide = "moe.yuyui.mcstreamerbot.CommonProxy")
public static CommonProxy proxy;
@Mod.EventHandler
// preInit "Run before anything else. Read your config, create blocks, items, etc, and register them with the
// GameRegistry." (Remove if not needed)
public void preInit(FMLPreInitializationEvent event) {
proxy.preInit(event);
}
@Mod.EventHandler
// load "Do your mod setup. Build whatever data structures you care about. Register recipes." (Remove if not needed)
public void init(FMLInitializationEvent event) {
proxy.init(event);
}
@Mod.EventHandler
// postInit "Handle interaction with other mods, complete your setup based on this." (Remove if not needed)
public void postInit(FMLPostInitializationEvent event) {
proxy.postInit(event);
}
@Mod.EventHandler
// register server commands in this event handler (Remove if not needed)
public void serverStarting(FMLServerStartingEvent event) {
proxy.serverStarting(event);
}
}

View File

@@ -0,0 +1,27 @@
package moe.yuyui.mcstreamerbot.common;
public class EventListener extends Thread {
private final WSServer _websocketServer;
public EventListener(WSServer websocketServer) {
this._websocketServer = websocketServer;
start();
}
@Override
public void run() {
try {
this._websocketServer.run();
System.out.println("Event Listener started");
} catch (NullPointerException e) {
System.out.println("Event Listener stopped. Websocket has failed.");
}
}
public void end() throws InterruptedException {
this._websocketServer.getConnections()
.forEach((connection) -> connection.closeConnection(1, "Server closed"));
this._websocketServer.stop();
}
}

View File

@@ -0,0 +1,138 @@
package moe.yuyui.mcstreamerbot.common;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipException;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import moe.yuyui.mcstreamerbot.common.beans.MessagePayloadBean;
import moe.yuyui.mcstreamerbot.common.enums.MessageType;
import moe.yuyui.mcstreamerbot.events.custom.OnChatMessage;
public class WSServer extends WebSocketServer {
private final Set<WebSocket> _authClients = new HashSet<>();
private byte[] _authToken = null;
public WSServer(InetSocketAddress address) {
super(address);
}
public WSServer(InetSocketAddress address, String authToken) throws NoSuchAlgorithmException {
super(address);
this._authToken = MessageDigest.getInstance("SHA-256")
.digest(authToken.getBytes(StandardCharsets.UTF_8));
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
if (_authToken != null) {
conn.send("bad girl");
return;
}
_authClients.add(conn);
System.out.println("New connection");
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
_authClients.remove(conn);
System.out.println("Connection closed");
}
@Override
public void onMessage(WebSocket conn, String message) {}
private boolean isAuthenticated(WebSocket conn) {
return _authClients.contains(conn);
}
private boolean isBearerValid(String token) {
if (_authToken == null) {
return true;
}
if (token == null) {
return false;
}
return token.equals(new String(_authToken, StandardCharsets.UTF_8));
}
public String decompressMessage(ByteBuffer byteBuffer) {
if (byteBuffer == null) {
return null;
}
try {
GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(byteBuffer.array()));
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(gzipInputStream, StandardCharsets.UTF_8));
StringBuilder builder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
} catch (ZipException e) {
System.out.println("Message is not valid");
} catch (IOException ignored) {}
return null;
}
@Override
public void onMessage(WebSocket conn, ByteBuffer byteBuffer) {
String message = decompressMessage(byteBuffer);
if (message == null) {
return;
}
Gson gson = new GsonBuilder().serializeNulls()
.create();
MessagePayloadBean payload = gson.fromJson(message, MessagePayloadBean.class);
if (!isAuthenticated(conn) || isBearerValid(payload.getToken())) {
_authClients.add(conn);
}
switch (MessageType.valueOf(payload.getType())) {
case CHAT: {
OnChatMessage.sendMessage(payload.getParts(), payload.getTimestamp(), payload.getUser());
break;
}
case COMMAND: {
break;
}
case LISTEN: {
break;
}
case IGNORE: {
break;
}
}
}
@Override
public void onError(WebSocket conn, Exception ex) {
ex.printStackTrace();
conn.close();
}
@Override
public void onStart() {
System.out.println("Server started");
}
}

View File

@@ -0,0 +1,45 @@
package moe.yuyui.mcstreamerbot.common.beans;
import com.google.gson.annotations.SerializedName;
public class EmojiBean {
@SerializedName("Emoji")
private final String _emoji;
@SerializedName("Text")
private final String _text;
@SerializedName("Image")
private final String _imagePath;
@SerializedName("StartIndex")
private final int _startIndex;
@SerializedName("EndIndex")
private final int _endIndex;
public EmojiBean(String emoji, String text, String imagePath, int startIndex, int endIndex) {
this._emoji = emoji;
this._text = text;
this._imagePath = imagePath;
this._startIndex = startIndex;
this._endIndex = endIndex;
}
public String getEmoji() {
return this._emoji;
}
public String getText() {
return this._text;
}
public String getImagePath() {
return this._imagePath;
}
public int getStartIndex() {
return this._startIndex;
}
public int getEndIndex() {
return this._endIndex;
}
}

View File

@@ -0,0 +1,26 @@
package moe.yuyui.mcstreamerbot.common.beans;
import javax.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
public class MessageBean {
@SerializedName("Text")
private final String _text;
@SerializedName("Emoji")
private final EmojiBean _emoji;
public MessageBean(String text, @Nullable EmojiBean emoji) {
this._text = text;
this._emoji = emoji;
}
public String getText() {
return this._text;
}
public EmojiBean getEmoji() {
return this._emoji;
}
}

View File

@@ -0,0 +1,48 @@
package moe.yuyui.mcstreamerbot.common.beans;
import java.util.List;
import com.google.gson.annotations.SerializedName;
public class MessagePayloadBean {
@SerializedName("Type")
private final short _type;
@SerializedName("BearerToken")
private final String _token;
@SerializedName("User")
private final UserBean _user;
@SerializedName("Parts")
private final List<MessageBean> _parts;
@SerializedName("Timestamp")
private final long _timestamp;
public MessagePayloadBean(short type, String token, UserBean user, List<MessageBean> parts, long timestamp) {
this._type = type;
this._token = token;
this._user = user;
this._parts = parts;
this._timestamp = timestamp;
}
public short getType() {
return this._type;
}
public String getToken() {
return this._token;
}
public UserBean getUser() {
return this._user;
}
public List<MessageBean> getParts() {
return this._parts;
}
public long getTimestamp() {
return this._timestamp;
}
}

View File

@@ -0,0 +1,46 @@
package moe.yuyui.mcstreamerbot.common.beans;
import com.google.gson.annotations.SerializedName;
public class UserBean {
@SerializedName("Name")
private final String _name;
@SerializedName("YoutubeId")
private final String _youtubeId;
@SerializedName("IsSubscriber")
private final boolean _isSubscriber;
@SerializedName("IsModerator")
private final boolean _isModerator;
@SerializedName("IsMember")
private final boolean _isMember;
public UserBean(String name, String youtubeId, boolean isSubscriber, boolean isModerator, boolean isMember) {
this._name = name;
this._youtubeId = youtubeId;
this._isSubscriber = isSubscriber;
this._isModerator = isModerator;
this._isMember = isMember;
}
public String getName() {
return _name;
}
public String getYoutubeId() {
return _youtubeId;
}
public boolean getIsSubscriber() {
return _isSubscriber;
}
public boolean getIsModerator() {
return _isModerator;
}
public boolean getIsMember() {
return _isMember;
}
}

View File

@@ -0,0 +1,33 @@
package moe.yuyui.mcstreamerbot.common.enums;
import java.util.HashMap;
import java.util.Map;
public enum MessageType {
CHAT(0),
COMMAND(1),
LISTEN(2),
IGNORE(3);
private final int value;
private static final Map<Integer, MessageType> map = new HashMap<>();
MessageType(int value) {
this.value = value;
}
static {
for (MessageType messageType : MessageType.values()) {
map.put(messageType.value, messageType);
}
}
public static MessageType valueOf(int value) {
return (MessageType) map.get(value);
}
public int getValue() {
return value;
}
}

View File

@@ -0,0 +1,77 @@
package moe.yuyui.mcstreamerbot.events.custom;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import moe.yuyui.mcstreamerbot.common.beans.MessageBean;
import moe.yuyui.mcstreamerbot.common.beans.UserBean;
public final class OnChatMessage {
private static final String _messageFormat = "<%s> %s%s[%s]%s: %s";
private static String buildMessage(List<MessageBean> messageParts) {
StringBuilder finalMessage = new StringBuilder();
for (MessageBean messagePart : messageParts) {
if (messagePart.getEmoji() != null) {
finalMessage.append(
messagePart.getEmoji()
.getText(),
messagePart.getEmoji()
.getStartIndex(),
messagePart.getEmoji()
.getEndIndex());
}
finalMessage.append(messagePart.getText());
}
return finalMessage.toString();
}
private static String getUserColor(UserBean user) {
if (user.getIsModerator()) {
return EnumChatFormatting.RED.toString();
}
if (user.getIsMember()) {
return EnumChatFormatting.DARK_GREEN.toString();
}
return EnumChatFormatting.RESET.toString();
}
private static char getUserBadge(UserBean user) {
if (user.getIsModerator()) {
return '⚔';
}
if (user.getIsMember()) {
return '✨';
}
return ' ';
}
public static void sendMessage(List<MessageBean> messageParts, long timestamp, UserBean user) {
if (messageParts == null) {
return;
}
final String message = buildMessage(messageParts);
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("hh:mm:ss a");
final String formattedTime = Instant.ofEpochSecond(timestamp)
.atZone(ZoneId.systemDefault())
.format(dateFormatter);
ChatComponentText chatComponentText = new ChatComponentText(
String.format(
_messageFormat,
formattedTime,
getUserColor(user),
getUserBadge(user),
user.getName(),
EnumChatFormatting.RESET.toString(),
message.replace('§', ' ')));
Minecraft.getMinecraft().ingameGUI.getChatGUI()
.printChatMessage(chatComponentText);
}
}

View File

@@ -0,0 +1,28 @@
package moe.yuyui.mcstreamerbot.events.minecraft;
import java.net.InetSocketAddress;
import net.minecraft.client.Minecraft;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import moe.yuyui.mcstreamerbot.MinecraftStreamerBotIntegration;
import moe.yuyui.mcstreamerbot.common.EventListener;
import moe.yuyui.mcstreamerbot.common.WSServer;
public class OnWorldJoin {
@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.entity == Minecraft.getMinecraft().thePlayer && Minecraft.getMinecraft()
.isIntegratedServerRunning() && event.world.isRemote) {
if (MinecraftStreamerBotIntegration.eventListener != null) {
if (MinecraftStreamerBotIntegration.eventListener.isAlive()) {
return;
}
}
MinecraftStreamerBotIntegration.eventListener = new EventListener(
new WSServer(new InetSocketAddress("127.0.0.1", 8080)));
}
}
}

View File

@@ -0,0 +1,17 @@
package moe.yuyui.mcstreamerbot.events.minecraft;
import net.minecraftforge.event.world.WorldEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import moe.yuyui.mcstreamerbot.MinecraftStreamerBotIntegration;
public class OnWorldLeave {
@SubscribeEvent
public void onPlayerLeave(WorldEvent.Unload event) {
try {
MinecraftStreamerBotIntegration.eventListener.end();
} catch (InterruptedException | NullPointerException ignored) {}
}
}