#!/usr/bin/env ruby require "async/io" require "async/io/stream" class Network def initialize(**opts) @endpoint = if opts[:tls] ctx = nil if opts[:cert] ctx = OpenSSL::SSL::SSLContext.new ctx.client_cert_cb = Proc.new do pem = File.open opts[:cert] do |file| file.read end [ OpenSSL::X509::Certificate.new(pem), OpenSSL::PKey::RSA.new(pem) ] end ctx.setup ctx.freeze end Async::IO::SSLEndpoint.ssl opts[:host], opts[:port], ssl_context: ctx else Async::IO::Endpoint.tcp opts[:host], opts[:port] end @callbacks = { } @nick = opts[:nick] @ident = opts[:ident] @realname = opts[:realname] yield self if block_given? end def on(event, &block) @callbacks[event.to_sym] = block end def build(*pieces) pieces[0..-2].join(" ") + " :" + pieces.last end def destroy(line) msg = { src: { }, cmd: nil, params: [ ], words: [ ] } line = line.chomp if line[0] == ?: then src, line = line.split " ", 2 from, host = src[1..].split ?@ if not host then msg[:src][:host] = from else nick, ident = from.split ?! msg[:src][:nick] = nick msg[:src][:ident] = ident msg[:src][:host] = host end end msg[:cmd], line = line.split " ", 2 until line.empty? do if line[0] == ?: then msg[:params] += [line[1..]] msg[:words] = line[1..].split " " line = "" else param, line = line.split " ", 2 line = "" if line.nil? msg[:params] += [param] end end msg end def type(*pieces) @stream.puts build(*pieces) + "\r" end def run Async do |task| @endpoint.connect do |peer| @stream = Async::IO::Stream.new peer type "NICK", @nick type "USER", @ident, ?*, ?*, @realname while line = @stream.gets if line msg = destroy line res = if @callbacks.key? msg[:cmd].downcase.to_sym @callbacks[msg[:cmd].downcase.to_sym].call msg end if respond_to? msg[:cmd].downcase and res send(msg[:cmd], msg) elsif respond_to? "code_" + msg[:cmd] and res send("code_" + msg[:cmd], msg) elsif not res puts "unhandled line: " + msg.inspect end end end end end end end settings = { host: "chat.freenode.net", port: 6697, nick: "niamh-dev", ident: "niamh", realname: "./niamh", tls: true, cert: "tmp/sparkle.pem" } bot = Network.new(**settings) do |net| net.on :notice do |msg| if msg[:src].key? :nick puts ?- + msg[:src][:nick] + ?/ + msg[:params].first + "- " + msg[:params].last else puts ?- + msg[:src][:host] + ?/ + msg[:params].first + "- " + msg[:params].last end true end net.on :privmsg do |msg| if msg[:src].key? :nick puts ?< + msg[:src][:nick] + ?/ + msg[:params].first + "> " + msg[:params].last else puts ?< + msg[:src][:host] + ?/ + msg[:params].first + "> " + msg[:params].last end true end end Async do bot.run end