145 lines
3.6 KiB
Ruby
Executable File
145 lines
3.6 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
require 'open3'
|
|
require 'socket'
|
|
require 'openssl'
|
|
require 'timeout'
|
|
require 'base64'
|
|
|
|
# configurable environment variables
|
|
nick = ENV['OUR_NICK'] || 'our'
|
|
channels = ENV['OUR_CHANNELS'] || '#tildetown,#bots'
|
|
prefix = ENV['OUR_PREFIX'] || "#{nick}/"
|
|
cmds_dir = ENV['OUR_CMDS_DIR'] || '/town/our'
|
|
server = ENV['OUR_SERVER'] || 'localhost'
|
|
use_ssl = ENV['OUR_USE_SSL'] == "true" || false
|
|
port = ENV['OUR_IRC_PORT'] || 6667
|
|
sasl_user = ENV['OUR_SASL_USER'] || nil
|
|
sasl_pass = ENV['OUR_SASL_PASS'] || nil
|
|
|
|
module IRC
|
|
|
|
class User
|
|
attr_accessor :s
|
|
|
|
def initialize(addr:, port:, nick:, use_ssl: false, sasl_user: nil, sasl_pass: nil)
|
|
@hooks = []
|
|
|
|
sock = TCPSocket.open addr, port.to_s
|
|
if use_ssl
|
|
puts "connecting with SSL"
|
|
ctx = OpenSSL::SSL::SSLContext.new
|
|
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
|
@s = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
|
|
socket.sync_close = true
|
|
socket.connect
|
|
end
|
|
else
|
|
puts "connecting without SSL"
|
|
@s = sock
|
|
end
|
|
|
|
if sasl_user && sasl_pass
|
|
puts "connecting with SASL"
|
|
s.puts "CAP REQ :sasl"
|
|
s.puts "USER #{nick} m455.casa 1 :beep boop"
|
|
s.puts "NICK #{nick}"
|
|
s.puts "AUTHENTICATE PLAIN"
|
|
plain_auth = Base64.encode64("#{sasl_user}\0#{sasl_user}\0#{sasl_pass}")
|
|
s.puts "AUTHENTICATE #{plain_auth}"
|
|
s.puts "CAP END"
|
|
else
|
|
puts "connecting without SASL"
|
|
s.puts "USER #{nick} m455.casa 1 :beep boop"
|
|
s.puts "NICK #{nick}"
|
|
end
|
|
|
|
hook do |m|
|
|
next unless m.cmd == 'PING'
|
|
raw "PING #{nick}"
|
|
end
|
|
end
|
|
|
|
def raw msg
|
|
@s.puts msg
|
|
end
|
|
|
|
def join chan
|
|
raw "JOIN #{chan}"
|
|
end
|
|
|
|
def privmsg target, msg
|
|
raw "PRIVMSG #{target} :#{msg}"
|
|
end
|
|
|
|
def hook &h
|
|
@hooks << h
|
|
end
|
|
|
|
def loop
|
|
while line = s.gets
|
|
msg = Message.new line
|
|
puts "S: #{msg.raw}"
|
|
@hooks.each{|h| h.call(msg)}
|
|
end
|
|
end
|
|
end
|
|
|
|
class Message
|
|
attr_accessor :prefix, :cmd, :args, :raw
|
|
# TODO custom constructor
|
|
def initialize msg
|
|
msg = msg.delete_suffix "\r\n"
|
|
@raw = msg
|
|
@prefix = nil
|
|
@prefix, msg = msg[1..].split(' ', 2) if msg[0] == ':'
|
|
@cmd, msg = msg.split(' ', 2)
|
|
|
|
@args = []
|
|
while msg and not msg.empty?
|
|
if msg[0] == ':'
|
|
@args << msg[1..]
|
|
break
|
|
end
|
|
s, msg = msg.split(' ', 2)
|
|
@args << s
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
puts "starting"
|
|
i = IRC::User.new(addr: server, port: port, nick: nick, use_ssl: use_ssl, sasl_user: sasl_user, sasl_pass: sasl_pass)
|
|
channels.split(',').each { |channel| i.join channel }
|
|
i.hook do |msg|
|
|
next unless msg.cmd == 'PRIVMSG'
|
|
target, content = msg.args
|
|
next unless content.delete_prefix! prefix
|
|
cmd, args = content.split(' ', 2)
|
|
cmd = "#{cmds_dir}/#{cmd}"
|
|
args ||= ''
|
|
next unless File.exists? cmd
|
|
if not File.executable? cmd
|
|
i.privmsg target, "#{cmd} isn't executable. try chmod +x"
|
|
next
|
|
end
|
|
begin
|
|
Open3.popen2e("#{__dir__}/wrap_it.sh", cmd, args, msg.prefix, target) do |_, stdout, wait_thread|
|
|
out = nil
|
|
Timeout::timeout(3) do
|
|
out = stdout.gets # only interested in the first line of output
|
|
stdout.gets until stdout.eof? # make sure process finishes in time allotted
|
|
end
|
|
i.privmsg target, out if out
|
|
rescue Timeout::Error
|
|
Process.kill("KILL", wait_thread.pid)
|
|
i.privmsg target, "[our.rb] command timed out"
|
|
end
|
|
rescue Exception => e
|
|
i.privmsg target, "[our.rb] #{e.to_s}"
|
|
end
|
|
next true
|
|
end
|
|
i.loop
|