kirby 4.1 → 4.2
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +2 -0
- data/README +13 -7
- data/bin/kirby +35 -10
- data/kirby.gemspec +4 -4
- data/lib/kirby.rb +102 -49
- metadata +3 -3
- metadata.gz.sig +2 -2
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -25,20 +25,26 @@ Kirby is considered feature-locked.
|
|
25
25
|
|
26
26
|
== Usage
|
27
27
|
|
28
|
-
|
28
|
+
To start a basic Kirby from the command line:
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
cd working/directory
|
31
|
+
kirby mynick mychannel myserver.org [delicious_user] [delicious_pass] [--options]
|
32
|
+
|
33
|
+
Logs and repository histories get saved to the working directory.
|
34
|
+
|
35
|
+
Available post-parameters are:
|
36
|
+
<tt>--daemon</tt>:: Daemonize.
|
33
37
|
<tt>--silent</tt>:: Never speak, even for errors.
|
38
|
+
<tt>--log</tt>:: Log channel chatter to a file.
|
39
|
+
<tt>--debug</tt>:: Debug mode.
|
34
40
|
|
35
|
-
See the Kirby class for commands.
|
41
|
+
See the Kirby class for in-channel commands.
|
36
42
|
|
37
43
|
== Crontab example
|
38
44
|
|
39
|
-
You can use a crontask to keep Kirby alive at all times.
|
45
|
+
You can use a crontask to keep Kirby alive at all times. For example:
|
40
46
|
|
41
|
-
* * * * * bash -c 'cd /working/directory; kirby
|
47
|
+
* * * * * bash -c 'cd /working/directory; kirby mynick mychannel irc.freenode.net --daemon --log --silent &> /dev/null'
|
42
48
|
|
43
49
|
== Reporting problems
|
44
50
|
|
data/bin/kirby
CHANGED
@@ -1,22 +1,47 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
=begin rdoc
|
4
|
-
Run <tt>kirby [nick] [channel] [server] [
|
4
|
+
Run <tt>kirby [nick] [channel] [server] [optional del.icio.us name] [optional del.icio.us password]</tt>.
|
5
5
|
|
6
|
-
Optional parameters:
|
7
|
-
<tt
|
8
|
-
<tt>-no-d</tt>:: Don't daemonize.
|
6
|
+
Optional post-parameters:
|
7
|
+
<tt>--daemon</tt>:: Daemonize.
|
9
8
|
<tt>--silent</tt>:: Never speak, even for errors.
|
9
|
+
<tt>--log</tt>:: Log channel chatter to a file.
|
10
|
+
<tt>--debug</tt>:: Debug mode.
|
10
11
|
=end
|
11
12
|
|
12
13
|
require 'rubygems'
|
13
|
-
require '
|
14
|
+
require 'daemons'
|
14
15
|
|
15
|
-
|
16
|
+
begin
|
17
|
+
require "#{File.dirname(__FILE__)}/../lib/kirby"
|
18
|
+
rescue LoadError
|
19
|
+
require 'kirby'
|
20
|
+
end
|
21
|
+
|
22
|
+
def option?(opt)
|
23
|
+
ARGV.include?("--#{opt}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def param(arg)
|
27
|
+
arg unless arg =~ /^--/
|
28
|
+
end
|
29
|
+
|
30
|
+
@kirby = Kirby.new(
|
31
|
+
:nick => param(ARGV[0]),
|
32
|
+
:channel => param(ARGV[1]),
|
33
|
+
:server => param(ARGV[2]),
|
34
|
+
:delicious_user => param(ARGV[3]),
|
35
|
+
:delicious_pass => param(ARGV[4]),
|
36
|
+
:silent => option?("silent"),
|
37
|
+
:debug => option?("debug"),
|
38
|
+
:log => option?("log")
|
39
|
+
)
|
40
|
+
|
41
|
+
pid = open(@kirby.config[:pidfile]).gets.chomp rescue nil
|
16
42
|
|
17
43
|
if !pid or `ps #{pid}`.split("\n").size < 2
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
Kirby.instance.restart
|
44
|
+
Daemons.daemonize if option? "daemon"
|
45
|
+
open(@kirby.config[:pidfile], 'w') {|f| f.puts $$}
|
46
|
+
@kirby.restart
|
22
47
|
end
|
data/kirby.gemspec
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Kirby-4.
|
2
|
+
# Gem::Specification for Kirby-4.2
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{kirby}
|
7
|
-
s.version = "4.
|
7
|
+
s.version = "4.2"
|
8
8
|
|
9
9
|
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.authors = [""]
|
13
|
-
s.date = %q{2007-12-
|
13
|
+
s.date = %q{2007-12-31}
|
14
14
|
s.default_executable = %q{kirby}
|
15
15
|
s.description = %q{A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.}
|
16
16
|
s.email = %q{}
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/kirby/}
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
s.rubyforge_project = %q{fauna}
|
23
|
-
s.rubygems_version = %q{0.
|
23
|
+
s.rubygems_version = %q{1.0.1}
|
24
24
|
s.summary = %q{A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.}
|
25
25
|
|
26
26
|
s.add_dependency(%q<hpricot>, [">= 0"])
|
data/lib/kirby.rb
CHANGED
@@ -2,60 +2,94 @@
|
|
2
2
|
|
3
3
|
=begin rdoc
|
4
4
|
In-channel commands:
|
5
|
-
<tt>>>
|
6
|
-
<tt>reset_irb</tt>::
|
7
|
-
<tt>add_svn [repository_url]</tt>::
|
5
|
+
<tt>>> CODE</tt>:: evaluate code in IRB.
|
6
|
+
<tt>reset_irb</tt>:: get a clean IRB session.
|
7
|
+
<tt>add_svn [repository_url]</tt>:: watch an SVN repository.
|
8
|
+
<tt>add_atom [atom_feed_url]</tt>:: watch an atom feed, such as a Git repository
|
9
|
+
|
10
|
+
To remove a repository, manually kill the bot and delete the line from <tt>nick.svns</tt> or <tt>nick.atoms</tt> in the bot's working directory. Then restart the bot.
|
8
11
|
=end
|
9
12
|
|
10
13
|
class Kirby
|
11
|
-
include Singleton
|
12
14
|
|
13
|
-
|
14
|
-
STORE = PATH + '/kirby.repositories'
|
15
|
-
ATOM = PATH + '/kirby.atoms'
|
16
|
-
PIDFILE = PATH + '/kirby.pid'
|
15
|
+
attr_reader :config
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
# Make a new Kirby. Will not connect to the server until you call connect().
|
18
|
+
def initialize(opts = {})
|
19
|
+
|
20
|
+
# Defaults
|
21
|
+
path = File.expand_path(".").to_s
|
22
|
+
nick = opts[:nick] || config[:nick] || "kirby-dev"
|
23
|
+
|
24
|
+
@config ||= {
|
25
|
+
:svns => "#{path}/#{nick}.svns",
|
26
|
+
:atoms => "#{path}/#{nick}.atoms",
|
27
|
+
:pidfile => "#{path}/#{nick}.pid",
|
28
|
+
:nick => nick,
|
29
|
+
:channel => 'kirby-dev',
|
30
|
+
:server => "irc.freenode.org",
|
31
|
+
:delicious_user => nil,
|
32
|
+
:delicious_pass => nil,
|
33
|
+
:silent => false,
|
34
|
+
:log => false,
|
35
|
+
:logfile => "#{path}/#{nick}.log",
|
36
|
+
:time_format => '%Y/%m/%d %H:%M:%S',
|
37
|
+
:debug => false
|
38
|
+
}
|
39
|
+
|
40
|
+
# Nicely merge current options
|
41
|
+
opts.each do |key, value|
|
42
|
+
config[key] = value if value
|
43
|
+
end
|
44
|
+
end
|
23
45
|
|
24
46
|
# Connect and reconnect to the server
|
25
|
-
def restart
|
26
|
-
|
27
|
-
|
47
|
+
def restart
|
48
|
+
log "Restarting"
|
49
|
+
puts config.inspect if config[:debug]
|
50
|
+
|
51
|
+
@svns = (YAML.load_file config[:svns] rescue {})
|
52
|
+
@atoms = (YAML.load_file config[:atoms] rescue {})
|
53
|
+
|
28
54
|
@socket.close if @socket
|
29
55
|
connect
|
30
56
|
listen
|
31
57
|
end
|
32
58
|
|
33
59
|
# Connect to the IRC server.
|
34
|
-
def connect
|
35
|
-
|
36
|
-
|
37
|
-
write "
|
38
|
-
write "
|
60
|
+
def connect
|
61
|
+
log "Connecting"
|
62
|
+
@socket = TCPSocket.new(config[:server], 6667)
|
63
|
+
write "USER #{config[:nick]} #{config[:nick]} #{config[:nick]} :#{config[:nick]}"
|
64
|
+
write "NICK #{config[:nick]}"
|
65
|
+
write "JOIN ##{config[:channel]}"
|
39
66
|
end
|
40
67
|
|
41
68
|
# The event loop. Waits for socket traffic, and then responds to it. The server sends <tt>PING</tt> 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).
|
42
69
|
def listen
|
43
70
|
@socket.each do |line|
|
44
|
-
|
45
|
-
poll
|
71
|
+
puts "GOT: #{line.inspect}" if config[:debug]
|
72
|
+
poll if !config[:silent]
|
46
73
|
case line.strip
|
47
|
-
when /^PING/
|
48
|
-
|
49
|
-
|
50
|
-
|
74
|
+
when /^PING/
|
75
|
+
write line.sub("PING", "PONG")[0..-3]
|
76
|
+
when /^ERROR/, /KICK ##{config[:channel]} #{config[:nick]} /
|
77
|
+
restart unless line =~ /PRIVMSG/
|
78
|
+
when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:\001ACTION (.+)\001/
|
79
|
+
log "* #{$1} #{$2}"
|
80
|
+
when /:(.+?)!.* PRIVMSG ##{config[:channel]} \:(.+)/
|
81
|
+
nick, msg = $1, $2
|
82
|
+
log "<#{nick}> #{msg}"
|
83
|
+
if !config[:silent]
|
51
84
|
case msg
|
52
85
|
when /^>>\s*(.+)/ then try $1.chop
|
53
|
-
when /^#{
|
86
|
+
when /^#{config[:nick]}:/
|
87
|
+
["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}
|
54
88
|
when /^reset_irb/ then reset_irb
|
55
|
-
when /^add_svn (.+?)(\s|\r|\n|$)/ then
|
56
|
-
when /^add_atom (.+?)(\s|\r|\n|$)/ then
|
57
|
-
|
58
|
-
|
89
|
+
when /^add_svn (.+?)(\s|\r|\n|$)/ then @svns[$1] = 0 and say @svns.inspect
|
90
|
+
when /^add_atom (.+?)(\s|\r|\n|$)/ then @atoms[$1] = '' and say @atoms.inspect
|
91
|
+
when /(http:\/\/.*?)(\s|\r|\n|$)/ then post($1) if config[:delicious_pass]
|
92
|
+
end
|
59
93
|
end
|
60
94
|
end
|
61
95
|
end
|
@@ -65,60 +99,78 @@ class Kirby
|
|
65
99
|
def write s
|
66
100
|
raise RuntimeError, "No socket" unless @socket
|
67
101
|
@socket.puts s += "\r\n"
|
68
|
-
|
102
|
+
puts "WROTE: #{s.inspect}" if config[:debug]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Write a string to the log, if the logfile is open.
|
106
|
+
def log s
|
107
|
+
# Open log, if necessary
|
108
|
+
if config[:log]
|
109
|
+
puts "LOG: #{s}" if config[:debug]
|
110
|
+
File.open(config[:logfile], 'a') do |f|
|
111
|
+
f.puts "#{Time.now.strftime(config[:time_format])} #{s}"
|
112
|
+
end
|
113
|
+
end
|
69
114
|
end
|
70
115
|
|
71
116
|
# Eval a piece of code in the <tt>irb</tt> environment.
|
72
117
|
def try s
|
73
|
-
reset_irb unless
|
118
|
+
reset_irb unless @session
|
74
119
|
try_eval(s).select{|e| e !~ /^\s+from .+\:\d+(\:|$)/}.each {|e| say e} rescue say "session error"
|
75
120
|
end
|
76
121
|
|
77
122
|
# Say something in the channel.
|
78
123
|
def say s
|
79
|
-
write "PRIVMSG
|
124
|
+
write "PRIVMSG ##{config[:channel]} :#{s[0..450]}"
|
125
|
+
log "<#{config[:nick]}> #{s}"
|
80
126
|
sleep 1
|
81
127
|
end
|
82
128
|
|
83
129
|
# Get a new <tt>irb</tt> session.
|
84
130
|
def reset_irb
|
85
131
|
say "Began new irb session"
|
86
|
-
|
132
|
+
@session = try_eval("!INIT!IRB!")
|
87
133
|
end
|
88
134
|
|
89
135
|
# Inner loop of the try method.
|
90
136
|
def try_eval s
|
91
137
|
reset_irb and return [] if s.strip == "exit"
|
92
138
|
result = open("http://tryruby.hobix.com/irb?cmd=#{CGI.escape(s)}",
|
93
|
-
{'Cookie' => "_session_id=#{
|
139
|
+
{'Cookie' => "_session_id=#{@session}"}).read
|
94
140
|
result[/^Your session has been closed/] ? (reset_irb and try_eval s) : result.split("\n")
|
95
141
|
end
|
96
142
|
|
97
|
-
# Look for
|
143
|
+
# Look for SVN changes. Note that Rubyforge polls much better if you use the http:// protocol instead of the svn:// protocol for your repository.
|
98
144
|
def poll
|
99
|
-
return unless (Time.now - $last_poll >
|
145
|
+
return unless (Time.now - $last_poll > 60 rescue true)
|
100
146
|
$last_poll = Time.now
|
101
|
-
|
147
|
+
@svns.each do |repo, last|
|
148
|
+
puts "POLL: #{repo}" if config[:debug]
|
102
149
|
(Hpricot(`svn log #{repo} -rHEAD:#{last} --limit 10 --xml`)/:logentry).reverse[1..-1].each do |ci|
|
103
|
-
|
104
|
-
|
150
|
+
@svns[repo] = rev = ci.attributes['revision'].to_i
|
151
|
+
project = repo.split(/\.|\//).reject do |path|
|
152
|
+
['trunk', 'rubyforge', 'svn', 'org', 'com', 'net', 'http:', nil].include? path
|
153
|
+
end.last
|
154
|
+
say "Commit #{rev} to #{project || repo} by #{(ci/:author).text}: #{(ci/:msg).text}"
|
105
155
|
end rescue nil
|
106
156
|
end
|
107
|
-
File.open(
|
157
|
+
File.open(config[:svns], 'w') {|f| f.puts YAML.dump(@svns)}
|
108
158
|
|
109
|
-
|
159
|
+
@atoms.each do |feed, last|
|
160
|
+
puts "POLL: #{feed}" if config[:debug]
|
110
161
|
begin
|
111
162
|
e = (Hpricot(open(feed))/:entry).first
|
112
|
-
|
113
|
-
say "#{
|
163
|
+
@atoms[feed] = link = e.at("link")['href']
|
164
|
+
say "Commit #{link} by #{((e/:author)/:name).text}: #{(e/:title).text}" unless link == last
|
114
165
|
rescue
|
115
166
|
end
|
116
167
|
end
|
117
|
-
File.open(
|
168
|
+
File.open(config[:atoms], 'w') {|f| f.puts YAML.dump(@atoms)}
|
118
169
|
end
|
119
170
|
|
120
171
|
# Post a url to the del.icio.us account.
|
121
172
|
def post url
|
173
|
+
puts "POST: #{url}" if config[:debug]
|
122
174
|
query = {:url => url,
|
123
175
|
:description => (((Hpricot(open(url))/:title).first.innerHTML or url) rescue url),
|
124
176
|
:tags => (Hpricot(open("http://del.icio.us/url/check?url=#{CGI.escape(url)}"))/'.alphacloud'/:a).map{|s| s.innerHTML}.join(" "),
|
@@ -126,11 +178,12 @@ class Kirby
|
|
126
178
|
begin
|
127
179
|
http = Net::HTTP.new('api.del.icio.us', 443)
|
128
180
|
http.use_ssl = true
|
129
|
-
http.start do |http|
|
181
|
+
response = http.start do |http|
|
130
182
|
req = Net::HTTP::Get.new('/v1/posts/add?' + query.map{|k,v| "#{k}=#{CGI.escape(v)}"}.join('&'))
|
131
|
-
req.basic_auth
|
183
|
+
req.basic_auth config[:delicious_user], config[:delicious_pass]
|
132
184
|
http.request(req)
|
133
185
|
end.body
|
186
|
+
puts "POST: #{response.inspect}" if config[:debug]
|
134
187
|
end
|
135
188
|
end
|
136
189
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kirby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "4.
|
4
|
+
version: "4.2"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ""
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
yZ0=
|
31
31
|
-----END CERTIFICATE-----
|
32
32
|
|
33
|
-
date: 2007-12-
|
33
|
+
date: 2007-12-31 00:00:00 -05:00
|
34
34
|
default_executable:
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
88
|
requirements: []
|
89
89
|
|
90
90
|
rubyforge_project: fauna
|
91
|
-
rubygems_version: 0.
|
91
|
+
rubygems_version: 1.0.1
|
92
92
|
signing_key:
|
93
93
|
specification_version: 2
|
94
94
|
summary: A super-clean IRC bot with sandboxed Ruby evaluation, svn watching, and link-logging to del.icio.us.
|
metadata.gz.sig
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
�
|
1
|
+
R�{p^"���p�$|Rx�(���J��%Ȗ���ia�
|
2
|
+
�
|