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
Signed by: yui
GPG Key ID: F368D23A0ABA04B4
22 changed files with 543 additions and 13 deletions

Binary file not shown.

5
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################

View File

@ -17,5 +17,5 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.27'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.29'
}

3
src/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
src/.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="temurin-1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

10
src/.idea/modules.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/main/main.iml" filepath="$PROJECT_DIR$/main/main.iml" />
<module fileurl="file://$PROJECT_DIR$/main/java/main2.iml" filepath="$PROJECT_DIR$/main/java/main2.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/src.iml" filepath="$PROJECT_DIR$/.idea/src.iml" />
</modules>
</component>
</project>

6
src/.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -1,4 +1,4 @@
package com.myname.mymodid;
package moe.yuyui.mcstreamerbot;
public class ClientProxy extends CommonProxy {

View File

@ -1,9 +1,13 @@
package com.myname.mymodid;
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 {
@ -12,12 +16,16 @@ public class CommonProxy {
public void preInit(FMLPreInitializationEvent event) {
Config.synchronizeConfiguration(event.getSuggestedConfigurationFile());
MyMod.LOG.info(Config.greeting);
MyMod.LOG.info("I am MyMod at version " + Tags.VERSION);
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) {}
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) {}

View File

@ -1,4 +1,4 @@
package com.myname.mymodid;
package moe.yuyui.mcstreamerbot;
import java.io.File;

View File

@ -1,4 +1,4 @@
package com.myname.mymodid;
package moe.yuyui.mcstreamerbot;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -9,14 +9,21 @@ 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 = MyMod.MODID, version = Tags.VERSION, name = "MyMod", acceptedMinecraftVersions = "[1.7.10]")
public class MyMod {
@Mod(
modid = MinecraftStreamerBotIntegration.MODID,
version = Tags.VERSION,
name = "MyMod",
acceptedMinecraftVersions = "[1.7.10]")
public class MinecraftStreamerBotIntegration {
public static final String MODID = "mymodid";
public static final String MODID = "mcstreamerbot";
public static final Logger LOG = LogManager.getLogger(MODID);
@SidedProxy(clientSide = "com.myname.mymodid.ClientProxy", serverSide = "com.myname.mymodid.CommonProxy")
public static EventListener eventListener;
@SidedProxy(clientSide = "moe.yuyui.mcstreamerbot.ClientProxy", serverSide = "moe.yuyui.mcstreamerbot.CommonProxy")
public static CommonProxy proxy;
@Mod.EventHandler

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) {}
}
}