aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README25
-rw-r--r--config.scm39
-rwxr-xr-xwrapper.scm146
4 files changed, 211 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a0c4db2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+srv/
diff --git a/README b/README
new file mode 100644
index 0000000..f1ca229
--- /dev/null
+++ b/README
@@ -0,0 +1,25 @@
+this is a very scrappy thrown-together chicken scheme script, which runs a
+minecraft server as a subprocess and simultaneously connects to an irc channel,
+and then relays chat messages between the two (and also join/part messages, and
+ctcp actions (/me), and it also allows specified users to send commands from
+irc).
+
+dependencies: chicken 5, "irc" egg, "srfi-18" egg.
+
+the default "config.scm" is very very specific to my personal use-case, but it
+should be relatively easy to adapt it for your own situation. in particular,
+you'll want to change the irc-admins, irc-conn and irc-channel, and unless
+you're running an old version of minecraft, you'll also need to customise the
+log parsing regexes, since the log format has changed in later versions (the
+current one is tested on vanilla 1.6.4).
+
+also, because we're running this on an old server - it uses the "say" command
+instead of "tellraw" because tellraw didn't exist yet. you may want to edit the
+script itself ("wrapper.scm") to use tellraw to produce nicer messages?
+
+the irc bot accepts a small set of admin commands from the user(s) listed in
+irc-admins config option:
+ - `!!set-verbose` enables relaying all log output to irc (config option
+ "verbose-log-relay?")
+ - `!!unset-verbose` disables it
+ - `!!/<command>` sends a command to the minecraft server (e.g. !!/stop)
diff --git a/config.scm b/config.scm
new file mode 100644
index 0000000..fb7d22b
--- /dev/null
+++ b/config.scm
@@ -0,0 +1,39 @@
+;; what to forward to stderr
+(define emit-minecraft-log? #f)
+(define emit-irc-trace? #f)
+
+;; minecraft server
+(define server-dir "srv")
+(define server-command
+ '("java"
+ "-Dminecraft.api.session.host=https://auth.fnordmc.xyz/session"
+ "-Djava.protocol.handler.pkgs=gg.codie.mineonline.protocol"
+ "-cp" "server.jar:OnlineModeFix.jar"
+ "net.minecraft.server.MinecraftServer"
+ "--nogui"))
+
+;; irc server
+(define irc-conn (irc:connection #:server "celestine"
+ #:nick "MC"
+ #:reconnect #t))
+;; the channel needs to allow roleplay commands (i.e. mode +E)
+;; because i'm using them to spoof ingame nicks
+(define irc-channel "#minecraft")
+;; nicks allowed to use admin commands
+(define irc-admins '("winter"))
+
+;; whether to spam irc with all minecraft output
+(define verbose-log-relay? #f)
+
+;;; the following will need to be adjusted depending on the log format of the
+;;; specific minecraft version you're running
+
+(define timestamp-format "%Y-%m-%d %H:%M:%S")
+;; first capture group is the log level, second is the line content
+(define log-line-format (irregex "\\[([A-Z]+)\\] ?(.*)$"))
+;; first capture group is the username, second is the message content
+(define chat-message-format (irregex "^<([^>]+)> (.*)$"))
+(define chat-action-format (irregex "^\\* ([^ ]+) (.*)$"))
+;; first capture group is the username
+(define player-join-format (irregex "^([^ ]+) joined the game$"))
+(define player-part-format (irregex "^([^ ]+) left the game$"))
diff --git a/wrapper.scm b/wrapper.scm
new file mode 100755
index 0000000..07fb5c2
--- /dev/null
+++ b/wrapper.scm
@@ -0,0 +1,146 @@
+#!/usr/bin/env -S csi -ss
+
+(import (chicken format)
+ (chicken io)
+ (chicken irregex)
+ (chicken process)
+ (chicken process-context)
+ (chicken time posix)
+ irc
+ srfi-18
+ srfi-26)
+
+(include-relative "config.scm")
+
+
+(define (process-log-line tag message timestamp log-port control-port)
+ (when verbose-log-relay?
+ (irc:say irc-conn (sprintf "[~a] ~a" tag message) irc-channel))
+ ;; check for regular chat message
+ (let ([match (irregex-search chat-message-format message)])
+ (when (and (equal? tag "INFO") match)
+ (let ([username (irregex-match-substring match 1)]
+ [message (irregex-match-substring match 2)])
+ (irc:command irc-conn
+ (sprintf "NPC ~a ~a :~a" irc-channel username message))
+ #;(printf "<~a> ~a~n" username message))))
+ ;; check for /me message
+ (let ([match (irregex-search chat-action-format message)])
+ (when (and (equal? tag "INFO") match)
+ (let ([username (irregex-match-substring match 1)]
+ [message (irregex-match-substring match 2)])
+ (irc:command irc-conn
+ (sprintf "NPCA ~a ~a :~a" irc-channel username message))
+ #;(printf "* ~a ~a~n" username message))))
+ ;; check for joins and parts
+ (let ([match (irregex-search player-join-format message)])
+ (when (and (equal? tag "INFO") match)
+ (let ([username (irregex-match-substring match 1)])
+ (irc:say irc-conn (sprintf "8~a joined the game" username)
+ irc-channel)
+ #;(printf "~a joined~n" username))))
+ (let ([match (irregex-search player-part-format message)])
+ (when (and (equal? tag "INFO") match)
+ (let ([username (irregex-match-substring match 1)])
+ (irc:say irc-conn (sprintf "8~a left the game" username) irc-channel)
+ #;(printf "~a quit~n" (irregex-match-substring match 1))))))
+
+
+(define (each-line line log-port control-port)
+ (when (not (eof-object? line))
+ (when emit-minecraft-log?
+ (write-line line (current-error-port)))
+ (let ([timestamp (string->time line timestamp-format)]
+ [match (irregex-search log-line-format line)])
+ (when (and timestamp match)
+ (let ([log-tag (irregex-match-substring match 1)]
+ [log-message (irregex-match-substring match 2)])
+ (process-log-line log-tag log-message timestamp
+ log-port control-port))))
+ (each-line (read-line log-port) log-port control-port)))
+
+
+;; TODO: convert colour codes between each protocol
+
+
+(define (handle-admin-command control-port sender command)
+ (cond
+ [(not (member sender irc-admins))
+ (irc:say irc-conn
+ (sprintf "~a, you don't have permission to do that" sender)
+ irc-channel)]
+ [(equal? (substring command 0 1) "/")
+ (write-line (substring command 1) control-port)]
+ [(equal? command "set-verbose")
+ (set! verbose-log-relay? #t)]
+ [(equal? command "unset-verbose")
+ (set! verbose-log-relay? #f)]
+ [else
+ (irc:say irc-conn (sprintf "~a, I can't understand that command" sender)
+ irc-channel)]))
+
+
+(define (message-handler control-port msg)
+ (when emit-irc-trace?
+ (write-line (irc:message-body msg) (current-error-port)))
+ (cond
+ ;; admin commands
+ [(and (equal? (irc:message-command msg) "PRIVMSG")
+ (not (irc:extended-data? (cadr (irc:message-parameters msg))))
+ (equal? (substring (cadr (irc:message-parameters msg)) 0 2) "!!"))
+ (handle-admin-command control-port (irc:message-sender msg)
+ (substring (cadr (irc:message-parameters msg)) 2))]
+ ;; accept messages; ignore RPs (to avoid duplicating our own stuff)
+ [(and (equal? (irc:message-command msg) "PRIVMSG")
+ (not (member (caddr (irc:message-prefix msg))
+ (list "npc.fakeuser.invalid"
+ (irc:connection-nick irc-conn)))))
+ (if (irc:extended-data? (cadr (irc:message-parameters msg)))
+ ;; CTCP
+ (let ([extdata (cadr (irc:message-parameters msg))])
+ (when (equal? (irc:extended-data-tag extdata) 'ACTION)
+ (fprintf control-port "say §7[~a]§r * ~a ~a~n"
+ (irc:message-receiver msg)
+ (irc:message-sender msg)
+ (irc:extended-data-content extdata))))
+ ;; normal message
+ (fprintf control-port "say §7[~a]§r <~a> ~a~n"
+ (irc:message-receiver msg)
+ (irc:message-sender msg)
+ (cadr (irc:message-parameters msg))))]
+ ;; join/part/quit lines
+ [(equal? (irc:message-command msg) "JOIN")
+ (fprintf control-port "say §7[~a] §e~a joined the channel~n"
+ (irc:message-receiver msg)
+ (irc:message-sender msg))]
+ [(equal? (irc:message-command msg) "PART")
+ (fprintf control-port "say §7[~a] §e~a left the channel (~a)~n"
+ (irc:message-receiver msg)
+ (irc:message-sender msg)
+ (cadr (irc:message-parameters msg)))]
+ [(equal? (irc:message-command msg) "QUIT")
+ (fprintf control-port "say §7[~a] §e~a left the server (~a)~n"
+ (irc:message-receiver msg)
+ (irc:message-sender msg)
+ (cadr (irc:message-parameters msg)))])
+ #f)
+
+
+(define (main args)
+ (change-directory server-dir)
+ (let-values ([(cout ccmd pid clog) (process* (car server-command)
+ (cdr server-command))])
+ (irc:connect irc-conn)
+ (irc:join irc-conn irc-channel)
+ (irc:add-message-handler! irc-conn (cut message-handler ccmd <>))
+ (let ([irc-loop (make-thread
+ (lambda () (irc:run-message-loop irc-conn #:pong #t))
+ 'irc-loop)]
+ [log-watcher (make-thread
+ (lambda () (each-line (read-line clog) clog ccmd))
+ 'log-watcher)])
+ (thread-start! irc-loop)
+ (thread-start! log-watcher)
+ (thread-join! log-watcher) ;; terminates when the server exits
+ (irc:quit irc-conn "Minecraft server process exited")
+ (thread-join! irc-loop))))