Class: Kirby

Inherits:
Object
  • Object
show all
Defined in:
lib/kirby.rb

Overview

In-channel commands:

>> CODE

evaluate code in IRB.

reset_irb

get a clean IRB session.

add_svn [repository_url]

watch an SVN repository.

add_atom [atom_feed_url]

watch an atom feed, such as a Git repository

To remove a repository, manually kill the bot and delete the line from nick.svns or nick.atoms in the bot’s working directory. Then restart the bot.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Kirby

Make a new Kirby. Will not connect to the server until you call connect().



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/kirby.rb', line 18

def initialize(opts = {})

  # Defaults
  path = File.expand_path(".").to_s
  nick = opts[:nick] || config[:nick] || "kirby-dev"

  @config ||= {
    :svns => "#{path}/#{nick}.svns",
    :atoms => "#{path}/#{nick}.atoms",
    :pidfile => "#{path}/#{nick}.pid",
    :nick => nick,
    :channel => 'kirby-dev',
    :server => "irc.freenode.org",
    :delicious_user => nil,
    :delicious_pass => nil,
    :silent => false,
    :log => false,
    :logfile => "#{path}/#{nick}.log",
    :time_format => '%Y/%m/%d %H:%M:%S',
    :debug => false
  }
  
  # Nicely merge current options
  opts.each do |key, value|
    config[key] = value if value
  end
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



15
16
17
# File 'lib/kirby.rb', line 15

def config
  @config
end

Instance Method Details

#connectObject

Connect to the IRC server.



60
61
62
63
64
65
66
# File 'lib/kirby.rb', line 60

def connect    
  log "Connecting"
  @socket = TCPSocket.new(config[:server], 6667)
  write "USER #{config[:nick]} #{config[:nick]} #{config[:nick]} :#{config[:nick]}"
  write "NICK #{config[:nick]}"
  write "JOIN ##{config[:channel]}"
end

#listenObject

The event loop. Waits for socket traffic, and then responds to it. The server sends PING every 3 minutes, which means we don’t need a separate thread to check for svn updates. All we do is wake on ping (or channel talking).



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/kirby.rb', line 69

def listen
  @socket.each do |line|
    puts "GOT: #{line.inspect}" if config[:debug]
    poll if !config[:silent]
    case line.strip
      when /^PING/
        write line.sub("PING", "PONG")[0..-3]
      when /^ERROR/, /KICK ##{config[:channel]} #{config[:nick]} / 
        restart unless line =~ /PRIVMSG/
      when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:\001ACTION (.+)\001/
        log "* #{$1} #{$2}"
      when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:(.+)/
        nick, msg = $1, $2          
        log "<#{nick}> #{msg}"
        if !config[:silent]
          case msg
            when /^>>\s*(.+)/ then try $1
            when /^#{config[:nick]}:/ 
              ["Usage:",  "  '>> CODE': evaluate code in IRB", "  'reset_irb': get a clean IRB session", "  'add_svn [repository_url]': watch an SVN repository", "  'add_atom [atom_feed_url]': watch an atom feed, such as a Git repository"].each {|s| say s}
            when /^reset_irb/ then reset_irb
            when /^add_svn (.+?)(\s|\r|\n|$)/ then @svns[$1] = 0 and say @svns.inspect
            when /^add_atom (.+?)(\s|\r|\n|$)/ then @atoms[$1] = '' and say @atoms.inspect
            when /(http(s|):\/\/.*?)(\s|\r|\n|$)/ then post($1) if config[:delicious_pass] 
          end
        end
    end
  end
end

#log(s) ⇒ Object

Write a string to the log, if the logfile is open.



106
107
108
109
110
111
112
113
114
# File 'lib/kirby.rb', line 106

def log s
  # Open log, if necessary
  if config[:log]
    puts "LOG: #{s}" if config[:debug]
    File.open(config[:logfile], 'a') do |f|
      f.puts "#{Time.now.strftime(config[:time_format])} #{s}"
    end
  end
end

#pollObject

Look for SVN changes. Note that Rubyforge polls much better if you use the http:// protocol instead of the svn:// protocol for your repository.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/kirby.rb', line 144

def poll
  return unless (Time.now - $last_poll > 60 rescue true)
  $last_poll = Time.now    
  @svns.each do |repo, last|
    puts "POLL: #{repo}" if config[:debug]
    (Hpricot(`svn log #{repo} -rHEAD:#{last} --limit 10 --xml`)/:logentry).reverse[1..-1].each do |ci|
      @svns[repo] = rev = ci.attributes['revision'].to_i
      project = repo.split(/\.|\//).reject do |path| 
        ['trunk', 'rubyforge', 'svn', 'org', 'com', 'net', 'http:', nil].include? path
      end.last
      say "Commit #{rev} to #{project || repo} by #{(ci/:author).text}: #{(ci/:msg).text}"
    end rescue nil
  end
  File.open(config[:svns], 'w') {|f| f.puts YAML.dump(@svns)}
  
  @atoms.each do |feed, last|
    puts "POLL: #{feed}" if config[:debug]
    begin
      e = (Hpricot(open(feed))/:entry).first
      @atoms[feed] = link = e.at("link")['href']
      say "Commit #{link} by #{((e/:author)/:name).text}: #{(e/:title).text}" unless link == last
    rescue
    end
  end
  File.open(config[:atoms], 'w') {|f| f.puts YAML.dump(@atoms)}
end

#post(url) ⇒ Object

Post a url to the del.i cio.us account.



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/kirby.rb', line 172

def post url
  Timeout.timeout(60) do
    puts "POST: #{url}" if config[:debug]

    tags = (Hpricot(open("http://del.icio.us/url/check?url=#{CGI.escape(url)}"))/
    '#top-tags'/'li')[0..10].map do |li| 
      (li/'span').innerHTML[/(.*?)<em/, 1]
    end.join(" ")
    puts "POST-TAGS: #{tags}" if config[:debug]
    
    description = begin
      Timeout.timeout(5) do 
        (((Hpricot(open(url))/:title).first.innerHTML or url) rescue url)
      end
    rescue Timeout::Error
      puts "POST: URL timeout" if config[:debug]
      url
    end
    
    query = { :url => url, :description => description, :tags => tags, :replace => 'yes' }

    http = Net::HTTP.new('api.del.icio.us', 443)         
    http.use_ssl = true      
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    response = http.start do |http|
      post_url = '/v1/posts/add?' + query.map {|k,v| "#{k}=#{CGI.escape(v)}"}.join('&')
      puts "POST: post url #{post_url}" if config[:debug]
      req = Net::HTTP::Get.new(post_url, {"User-Agent" => "Kirby"})
      req.basic_auth config[:delicious_user], config[:delicious_pass]
      http.request(req)
    end.body

    puts "POST: #{response.inspect}" if config[:debug]
  end
rescue Exception => e
  puts "POST: #{e.inspect}" if config[:debug]
end

#reset_irbObject

Get a new irb session.



130
131
132
133
# File 'lib/kirby.rb', line 130

def reset_irb
  say "Began new irb session"
  @session = try_eval("!INIT!IRB!")
end

#restartObject

Connect and reconnect to the server



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/kirby.rb', line 47

def restart    
  log "Restarting"
  puts config.inspect if config[:debug]
    
  @svns = (YAML.load_file config[:svns] rescue {})
  @atoms  = (YAML.load_file config[:atoms] rescue {})

  @socket.close if @socket
  connect
  listen
end

#say(s) ⇒ Object

Say something in the channel.



123
124
125
126
127
# File 'lib/kirby.rb', line 123

def say s
  write "PRIVMSG ##{config[:channel]} :#{s[0..450]}"
  log "<#{config[:nick]}> #{s}"
  sleep 1
end

#try(s) ⇒ Object

Eval a piece of code in the irb environment.



117
118
119
120
# File 'lib/kirby.rb', line 117

def try s
  reset_irb unless @session
  try_eval(s).select{|e| e !~ /^\s+from .+\:\d+(\:|$)/}.each {|e| say e} rescue say "session error"
end

#try_eval(s) ⇒ Object

Inner loop of the try method.



136
137
138
139
140
141
# File 'lib/kirby.rb', line 136

def try_eval s
  reset_irb and return [] if s.strip == "exit"
  result = open("http://tryruby.hobix.com/irb?cmd=#{CGI.escape(s)}", 
          {'Cookie' => "_session_id=#{@session}"}).read
  result[/^Your session has been closed/] ? (reset_irb and try_eval s) : result.split("\n")
end

#write(s) ⇒ Object

Send a raw string to the server.

Raises:

  • (RuntimeError)


99
100
101
102
103
# File 'lib/kirby.rb', line 99

def write s
  raise RuntimeError, "No socket" unless @socket
  @socket.puts s += "\r\n"
  puts "WROTE: #{s.inspect}" if config[:debug]
end