diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README | 25 | ||||
| -rw-r--r-- | config.scm | 39 | ||||
| -rwxr-xr-x | wrapper.scm | 146 |
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/ @@ -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)))) |
