Like almost 2.5 million others, my recent productivity has taken a hit due to the game Minecraft.
I have a home server set up to play with roommates and friends.
Funnily enough, Minecraft turned into a frequent medium of communication for us.
To take part in this when not logged into minecraft, and to get notifications to when friends were playing, I set out to make a bridge between minecraft and IRC.
The result is mcirc, a simple minecraft to irc gateway. It tails the minecraft log looking for player joins quits and messages, and relays them to the IRC server. This script requires that there is a FIFO for input to the server.
It is worth noting that this probably not the best minecraft <-> irc bridge which exists, but perhaps the simplest, and runs on a vanilla minecraft server. There are several which function as bukkit plugins:
1
2
3
.
Usage is
ruby irc.rb -s irc.freenode.org -c balmoralmc -f minecraft/console.pipe -l minecraft/server.log
The script itself is very small and largely command line parsing
#!/usr/bin/env ruby
require 'socket'
require 'uri'
class MinecraftIrcBot
def initialize(options)
uri = URI.parse("irc://#{options[:server]}")
@channel = options[:channel]
@socket = TCPSocket.open(uri.host, uri.port || 6667)
@mclog = IO.popen("tail -f -n0 '#{options[:log]}'", "r")
@name = options[:name]
@pipe = options[:pipe]
say "NICK #{@name}"
say "USER #{@name} 0 * #{@name}"
say "JOIN ##{@channel}"
end
def say(msg)
puts msg
@socket.puts msg
end
def say_to_chan(msg)
say "PRIVMSG ##{@channel} :#{msg}"
end
def say_to_minecraft(msg)
msg = "say #{msg}"
puts msg
File.open(@pipe, "w") do |console|
console.puts msg
end
end
def run
loop do
read, write, error = IO.select([@socket, @mclog])
if read.include? @socket
msg = @socket.gets
# connection lost
return unless msg
case msg.strip
when /^PING :(.+)$/i
say "PONG #{$1}"
when /^:(.+?)!.+?@.+?\sPRIVMSG\s.+?\s:(.+)$/i
say_to_minecraft("<#{$1}> #{$2}")
end
end
if read.include? @mclog
msg = @mclog.gets
msg.gsub!(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} /, '')
case msg.strip
when /^\[INFO\] ([a-z0-9]*) lost connection/i
say_to_chan("#{$1} has left")
when /^\[INFO\] ([a-z0-9]*) \[[^\]]*\] logged in/i
say_to_chan("#{$1} has joined")
when /^\[INFO\] <([a-z0-9]*)> (.*)$/i
say_to_chan("<#{$1}> #{$2}")
end
end
end
end
def quit
say "PART ##{@channel} :bye bye"
say 'QUIT'
end
end
def parse
require 'optparse'
options = {}
optparse = OptionParser.new do |opts|
opts.banner = "Usage: #{$0} options"
opts.on("-s", "--server SERVER", "IRC server") do |v|
options[:server] = v
end
opts.on("-c", "--channel CHAN", "IRC channel") do |v|
options[:channel] = v
end
opts.on("-f", "--fifo FIFO", "named pipe into minecraft server's console") do |v|
options[:pipe] = v
end
opts.on("-l", "--log LOG", "minecraft servers's log file for reading") do |v|
options[:log] = v
end
options[:name] = "mcirc"
opts.on("-n", "--name NAME", "name of the irc user") do |v|
options[:name] = v
end
end
begin
optparse.parse!
required = [:server, :channel, :pipe, :log]
required.each do |arg|
raise OptionParser::MissingArgument, arg if options[arg].nil?
end
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
puts $!.to_s
puts optparse
exit 1
end
options
end
def run
options = parse
bot = MinecraftIrcBot.new(options)
trap("INT"){ bot.quit }
bot.run
end
run