summaryrefslogtreecommitdiff
path: root/src/main/java/dev/plutorocks/IrcClient.java
diff options
context:
space:
mode:
authorplutorocks <>2026-02-24 19:49:08 -0500
committerplutorocks <>2026-02-24 19:49:08 -0500
commitd270f94eb002937677becaca638ec089f2294c42 (patch)
tree8e8ae9e02324cc2a07240e2e6e4314b787089743 /src/main/java/dev/plutorocks/IrcClient.java
parentd23825b05ec9cba95429c6ef693f469e99e37b36 (diff)
feat: add IRC
Diffstat (limited to 'src/main/java/dev/plutorocks/IrcClient.java')
-rw-r--r--src/main/java/dev/plutorocks/IrcClient.java199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/main/java/dev/plutorocks/IrcClient.java b/src/main/java/dev/plutorocks/IrcClient.java
new file mode 100644
index 0000000..f3abd40
--- /dev/null
+++ b/src/main/java/dev/plutorocks/IrcClient.java
@@ -0,0 +1,199 @@
+package dev.plutorocks;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.io.*;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+public class IrcClient {
+
+ private final String host;
+ private final int port;
+ private final String channel;
+ private final String nick;
+
+ private volatile boolean running = false;
+ private volatile boolean connected = false;
+
+ private Socket socket;
+ private BufferedWriter writer;
+
+ public IrcClient(String host, int port, String channel, String nick) {
+ this.host = host;
+ this.port = port;
+ this.channel = channel.startsWith("#") ? channel : "#" + channel;
+ this.nick = nick;
+ }
+
+ /**
+ * start the IRC thread.
+ */
+ public void connect() {
+ if (running) return;
+ running = true;
+
+ Thread thread = new Thread(this::runLoop, "MinecraftIRC-Thread");
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public boolean isConnected() {
+ return connected;
+ }
+
+ public void disconnect() {
+ running = false;
+ try {
+ if (socket != null && !socket.isClosed()) {
+ socket.close();
+ }
+ } catch (IOException ignored) {}
+ connected = false;
+ }
+
+ private void runLoop() {
+ try (Socket sock = new Socket(host, port);
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8));
+ BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
+
+ this.socket = sock;
+ this.writer = writer;
+ this.connected = true;
+
+ sendRaw("NICK " + nick);
+ sendRaw("USER " + nick + " 0 * :" + nick);
+
+ sendRaw("JOIN " + channel);
+
+ sendClientChat(
+ Text.literal("[IRC] ").formatted(Formatting.AQUA)
+ .append(Text.literal("Connected to " + host + " " + channel + " as " + nick)
+ .formatted(Formatting.GRAY))
+ );
+
+ String line;
+ while (running && (line = reader.readLine()) != null) {
+ handleLine(line);
+ }
+
+ if (running) {
+ sendClientChat(
+ Text.literal("[IRC] ").formatted(Formatting.AQUA)
+ .append(Text.literal("Disconnected from server. Use /irc connect to reconnect.")
+ .formatted(Formatting.GRAY))
+ );
+ }
+
+ } catch (IOException e) {
+ sendClientChat(
+ Text.literal("[IRC] ").formatted(Formatting.AQUA)
+ .append(Text.literal("Connection error: " + e.getMessage()
+ + " (use /irc connect to try again)")
+ .formatted(Formatting.GRAY))
+ );
+ } finally {
+ connected = false;
+ running = false;
+ }
+ }
+
+ private void handleLine(String line) {
+ // respond to server PINGs
+ if (line.startsWith("PING")) {
+ String payload = line.length() > 5 ? line.substring(5) : "";
+ sendRaw("PONG " + payload);
+ return;
+ }
+
+ // only care about PRIVMSG
+ if (!line.contains(" PRIVMSG ")) {
+ return;
+ }
+
+ int prefixEnd = line.indexOf(' ');
+ if (!line.startsWith(":") || prefixEnd <= 1) {
+ return;
+ }
+
+ String prefix = line.substring(1, prefixEnd);
+
+ String nick = prefix;
+ int bang = prefix.indexOf('!');
+ if (bang != -1) {
+ nick = prefix.substring(0, bang);
+ }
+
+ String[] split = line.split(" :", 2);
+ if (split.length < 2) {
+ return;
+ }
+
+ String trailing = split[1];
+ String commandPart = split[0];
+
+ String[] cmdParts = commandPart.split(" ");
+ if (cmdParts.length < 3) {
+ return;
+ }
+
+ String target = cmdParts[2];
+
+ if (!target.equalsIgnoreCase(this.channel)
+ && !target.equalsIgnoreCase(this.nick)) {
+ return;
+ }
+
+ MutableText prefixText = Text.literal("[IRC] ")
+ .formatted(Formatting.AQUA);
+ MutableText nickText = Text.literal("<" + nick + "> ")
+ .formatted(Formatting.WHITE);
+ MutableText msgText = Text.literal(trailing)
+ .formatted(Formatting.WHITE);
+
+ sendClientChat(prefixText.append(nickText).append(msgText));
+ }
+
+ /**
+ * sends a message to the configured channel
+ */
+ public void sendChannelMessage(String message) {
+ if (!connected) return;
+ sendRaw("PRIVMSG " + channel + " :" + message);
+ }
+
+ /**
+ * low-level raw IRC send
+ */
+ private synchronized void sendRaw(String line) {
+ if (writer == null) return;
+ try {
+ writer.write(line);
+ writer.write("\r\n");
+ writer.flush();
+ } catch (IOException e) {
+ sendClientChat(
+ Text.literal("[IRC] ").formatted(Formatting.AQUA)
+ .append(Text.literal("Send error: " + e.getMessage())
+ .formatted(Formatting.GRAY))
+ );
+ disconnect();
+ }
+ }
+
+ private void sendClientChat(Text text) {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client == null) return;
+
+ client.execute(() -> {
+ if (client.inGameHud != null) {
+ client.inGameHud.getChatHud().addMessage(text);
+ }
+ });
+ }
+} \ No newline at end of file