isaac 0.0.2 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -16
- data/isaac.gemspec +3 -3
- data/lib/isaac.rb +138 -200
- metadata +4 -4
data/README.rdoc
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
= Isaac - the smallish DSL for writing IRC bots
|
2
|
-
You want to create an IRC bot quickly? Then Isaac is you. It will be. At some point, at least. But you shall be welcome to try it out and help me extend and beautify it. Be aware, the code is not stellar by any measure, most likely it is very crude and a large portion of the IRC standard has not been implemented, simply because I haven't needed it yet. Oh, and a lot of concepts were borrowed from Sinatra (http://sinatrarb.com). Thanks.
|
3
2
|
|
4
3
|
== Features
|
5
4
|
* Wraps parsing of incoming messages and raw IRC commands in simple constructs.
|
@@ -9,7 +8,7 @@ You want to create an IRC bot quickly? Then Isaac is you. It will be. At some po
|
|
9
8
|
== Getting started
|
10
9
|
An Isaac-bot needs a few basics:
|
11
10
|
require 'isaac'
|
12
|
-
|
11
|
+
configure do |c|
|
13
12
|
c.nick = "AwesomeBot"
|
14
13
|
c.server = "irc.freenode.net"
|
15
14
|
c.port = 6667
|
@@ -25,21 +24,23 @@ After the bot has connected to the IRC server you might want to join some channe
|
|
25
24
|
=== Responding to messages
|
26
25
|
Joining a channel and sitting idle is not much fun. Let's repeat everything being said in these channels:
|
27
26
|
|
28
|
-
on :channel
|
27
|
+
on :channel do
|
29
28
|
msg channel, message
|
30
29
|
end
|
31
30
|
|
32
|
-
Notice the +channel+ and +message+ variables. Additionally +nick+ and +match+ is
|
31
|
+
Notice the +channel+ and +message+ variables. Additionally +nick+ and +match+ is
|
32
|
+
available for channel-events. +nick+ being the sender of the message, +match+
|
33
|
+
being an array of captures from the regular expression:
|
33
34
|
|
34
35
|
on :channel, /^quote this: (.*)/ do
|
35
|
-
msg channel, "Quote: '#{match[
|
36
|
+
msg channel, "Quote: '#{match[0]}' by #{nick}"
|
36
37
|
end
|
37
38
|
|
38
39
|
If you want to match private messages use the +on :private+ event:
|
39
40
|
|
40
41
|
on :private, /^login (\S+) (\S+)/ do
|
41
|
-
username = match[
|
42
|
-
password = match[
|
42
|
+
username = match[0]
|
43
|
+
password = match[1]
|
43
44
|
# do something to authorize or whatevz.
|
44
45
|
msg nick, "Login successful!"
|
45
46
|
end
|
@@ -66,7 +67,7 @@ Errors, as specified by RFC 1459, can be reacted upon as well. If you e.g. try t
|
|
66
67
|
|
67
68
|
Available variables: +nick+ and +channel+.
|
68
69
|
|
69
|
-
=== Send commands from outside an event
|
70
|
+
=== Send commands from outside an event (not implemented in Shaft atm)
|
70
71
|
You might want to send messages, join channels etc. without it strictly being the result of an on()-event, e.g. send a message every time a RSS feed is updated or whatever. You can use +Isaac.execute+ for that, and all your normal commands, +msg+, +join+, +topic+ etc. will be available:
|
71
72
|
|
72
73
|
class K
|
@@ -84,11 +85,4 @@ You might want to send messages, join channels etc. without it strictly being th
|
|
84
85
|
The source is hosted at GitHub: http://github.com/ichverstehe/isaac
|
85
86
|
|
86
87
|
== License
|
87
|
-
|
88
|
-
"THE BEER-WARE LICENSE" (Revision 42):
|
89
|
-
<ichverstehe@gmail.com> wrote this file. As long as you retain this notice you
|
90
|
-
can do whatever you want with this stuff. If we meet some day, and you think
|
91
|
-
this stuff is worth it, you can buy me a beer in return.
|
92
|
-
------------------------------------------------------------------------------
|
93
|
-
|
94
|
-
|
88
|
+
The MIT. Google it.
|
data/isaac.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "isaac"
|
3
|
-
s.version = "0.
|
4
|
-
s.date = "
|
3
|
+
s.version = "0.2.2"
|
4
|
+
s.date = "2009-02-23"
|
5
5
|
s.summary = "The smallish DSL for writing IRC bots"
|
6
|
-
s.email = "
|
6
|
+
s.email = "harry@vangberg.name"
|
7
7
|
s.homepage = "http://github.com/ichverstehe/isaac"
|
8
8
|
s.description = "Small DSL for writing IRC bots."
|
9
9
|
s.rubyforge_project = "isaac"
|
data/lib/isaac.rb
CHANGED
@@ -1,245 +1,183 @@
|
|
1
1
|
require 'socket'
|
2
|
+
|
2
3
|
module Isaac
|
3
|
-
|
4
|
-
def self.app
|
5
|
-
@app ||= Application.new
|
6
|
-
end
|
4
|
+
VERSION = '0.2.1'
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def self.execute(params={}, &block)
|
13
|
-
app.execute(params, &block)
|
6
|
+
Config = Struct.new(:server, :port, :password, :nick, :realname, :version, :environment, :verbose)
|
7
|
+
|
8
|
+
def self.bot
|
9
|
+
@bot ||= Bot.new
|
14
10
|
end
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
class Application
|
20
|
-
def initialize #:nodoc:
|
21
|
-
@events = Hash.new {|k,v| k[v] = []}
|
22
|
-
end
|
23
|
-
|
24
|
-
# This is plain stupid. Might be useful for logging or something later on.
|
25
|
-
def start #:nodoc:
|
26
|
-
puts " ==== Starting Isaac ==== "
|
27
|
-
loop { connect }
|
28
|
-
puts " ==== Ending Isaac ==== "
|
29
|
-
end
|
30
|
-
|
31
|
-
# Configure the bot:
|
32
|
-
# config do |c|
|
33
|
-
# c.server = "irc.freenode.net"
|
34
|
-
# c.nick = "AwesomeBot"
|
35
|
-
# c.port = 6667
|
36
|
-
# c.realname = "James Dean"
|
37
|
-
# c.username = "jdean"
|
38
|
-
# c.verbose = true
|
39
|
-
# end
|
40
|
-
def config(&block)
|
41
|
-
@config = Config.new('isaac_bot', 'irc.freenode.net', 6667, 'isaac', 'isaac', false)
|
42
|
-
block.call(@config)
|
43
|
-
@config
|
44
|
-
end
|
45
|
-
|
46
|
-
# Methods defined inside the helpers-block will be available to on()-events at execution time.
|
47
|
-
def helpers(&block)
|
48
|
-
EventContext.class_eval(&block)
|
49
|
-
end
|
50
|
-
|
51
|
-
# on()-events responds to certain actions. Depending on +type+ certain local variables are available: +nick+, +channel+, +message+ and in particular +match+, which contains a MatchData object returned by the given regular expression.
|
52
|
-
#
|
53
|
-
# * Do something after connection has been established, e.g. join channels.
|
54
|
-
# on :connect do
|
55
|
-
# join "#awesome_channel", "#lee_marvin_fans"
|
56
|
-
# end
|
57
|
-
# * Respond to private messages matching a given regular expression.
|
58
|
-
# on :private, /^echo (.*)/ do
|
59
|
-
# msg nick, "You said '#{match[1]}!"
|
60
|
-
# end
|
61
|
-
# * Respond to messages matching a given regular expression send to a channel.
|
62
|
-
# on :channel, /quote/ do
|
63
|
-
# msg channel, "#{nick} requested a quote: 'Smoking, a subtle form a suicide.' - Vonnegut"
|
64
|
-
# end
|
65
|
-
# * Respond to error codes, according to the RFC.
|
66
|
-
# on :error, 401 do
|
67
|
-
# # Execute this if you try to send a message to a non-existing nick/channel.
|
68
|
-
# end
|
69
|
-
def on(type, match=nil, &block)
|
70
|
-
@events[type] << e = Event.new(match, block)
|
71
|
-
return e
|
72
|
-
end
|
73
|
-
|
74
|
-
def execute(params={}, &block) #:nodoc:
|
75
|
-
event = Event.new(:dsl, block)
|
76
|
-
@queue << event.invoke(params)
|
77
|
-
end
|
78
|
-
|
79
|
-
def event(type, matcher)
|
80
|
-
@events[type].detect do |e|
|
81
|
-
type == :error ? matcher == e.match : matcher =~ e.match
|
82
|
-
end
|
83
|
-
end
|
12
|
+
class Bot
|
13
|
+
attr_accessor :config, :irc, :nick, :channel, :message, :userhost, :match,
|
14
|
+
:error
|
84
15
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@queue = Queue.new(@irc)
|
92
|
-
@queue << "NICK #{@config.nick}"
|
93
|
-
@queue << "USER #{@config.username} foobar foobar :#{@config.realname}"
|
94
|
-
@queue << @events[:connect].first.invoke if @events[:connect].first
|
95
|
-
|
96
|
-
while line = @irc.gets
|
97
|
-
handle line
|
98
|
-
end
|
99
|
-
rescue Interrupt => e
|
100
|
-
puts "Disconnected! An error occurred: #{e.inspect}"
|
101
|
-
rescue Timeout::Error => e
|
102
|
-
puts "Timeout: #{e}"
|
103
|
-
end
|
16
|
+
def initialize(&b)
|
17
|
+
@events = {}
|
18
|
+
@config = Config.new("localhost", 6667, nil, "isaac", "Isaac", 'isaac', :production, false)
|
19
|
+
|
20
|
+
instance_eval(&b) if block_given?
|
104
21
|
end
|
105
22
|
|
106
|
-
|
107
|
-
|
108
|
-
puts "
|
23
|
+
def start
|
24
|
+
puts "========================================="
|
25
|
+
puts "Connecting to #{@config.server}:#{@config.port}"
|
26
|
+
@irc = IRC.new(self, @config)
|
27
|
+
@irc.connect
|
28
|
+
puts "========================================="
|
29
|
+
end
|
109
30
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
type = channel.match(/^#/) ? :channel : :private
|
114
|
-
if event = event(type, message)
|
115
|
-
@queue << event.invoke(:nick => nick, :userhost => userhost, :channel => channel, :message => message)
|
116
|
-
end
|
117
|
-
when /^:\S+ ([4-5]\d\d) \S+ (\S+)/
|
118
|
-
error = $1
|
119
|
-
nick = channel = $2
|
120
|
-
if event = event(:error, error.to_i)
|
121
|
-
@queue << event.invoke(:nick => nick, :channel => channel)
|
122
|
-
end
|
123
|
-
when /^PING (\S+)/
|
124
|
-
#TODO not sure this is correct. Damned RFC.
|
125
|
-
@queue << "PONG #{$1}"
|
126
|
-
when /^:\S+ PONG \S+ :excess/
|
127
|
-
@queue.lock = false
|
128
|
-
end
|
31
|
+
def on(event, match=//, &b)
|
32
|
+
match = match.to_s if match.is_a? Integer
|
33
|
+
(@events[event] ||= []) << [Regexp.new(match), b]
|
129
34
|
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
attr_accessor :lock
|
134
|
-
def initialize(socket)
|
135
|
-
@socket = socket
|
136
|
-
@queue = []
|
137
|
-
@transfered = 0
|
138
|
-
@lock = false
|
139
|
-
transmit
|
140
|
-
end
|
141
|
-
|
142
|
-
# I luvz Rubyz
|
143
|
-
def << (msg)
|
144
|
-
# .flatten! returns nill if no modifications were made, thus we do this.
|
145
|
-
@queue = (@queue << msg).flatten
|
146
|
-
end
|
147
|
-
|
148
|
-
# To prevent excess flood no more than 1472 bytes will be sent to the
|
149
|
-
# server. When that limit is reached, @lock = true and the server will be
|
150
|
-
# PINGed. @lock will be true until a PONG is received (Application#handle).
|
151
|
-
def transmit
|
152
|
-
Thread.start { loop {
|
153
|
-
unless @lock || @queue.empty?
|
154
|
-
msg = @queue.shift
|
155
|
-
if (@transfered + msg.size) > 1472
|
156
|
-
# No honestly, :excess. The RFC is not too clear on this subject TODO
|
157
|
-
@socket.puts "PING :excess"
|
158
|
-
@lock = true
|
159
|
-
@transfered = 0
|
160
|
-
else
|
161
|
-
@socket.puts msg
|
162
|
-
@transfered += msg.size
|
163
|
-
end
|
164
|
-
end
|
165
|
-
sleep 0.1
|
166
|
-
}}
|
35
|
+
|
36
|
+
def helpers(&b)
|
37
|
+
instance_eval &b
|
167
38
|
end
|
168
|
-
end
|
169
39
|
|
170
|
-
|
171
|
-
|
172
|
-
def initialize(match, block)
|
173
|
-
@match = match
|
174
|
-
@block = block
|
40
|
+
def configure(&b)
|
41
|
+
b.call(@config)
|
175
42
|
end
|
176
43
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
44
|
+
def dispatch(event, env={})
|
45
|
+
self.nick, self.userhost, self.channel, self.error =
|
46
|
+
env[:nick], env[:userhost], env[:channel], env[:error]
|
47
|
+
self.message = env[:message] || ""
|
181
48
|
|
182
|
-
|
183
|
-
|
184
|
-
|
49
|
+
event = @events[event] && @events[event].detect do |regexp,_|
|
50
|
+
message.match(regexp)
|
51
|
+
end
|
52
|
+
|
53
|
+
if event
|
54
|
+
regexp, block = *event
|
55
|
+
self.match = message.match(regexp).captures
|
56
|
+
catch(:halt) { instance_eval(&block) }
|
57
|
+
end
|
185
58
|
end
|
186
|
-
end
|
187
59
|
|
188
|
-
|
189
|
-
|
190
|
-
def initialize(args = {})
|
191
|
-
args.each {|k,v| instance_variable_set("@#{k}",v)}
|
192
|
-
@commands = []
|
60
|
+
def halt
|
61
|
+
throw :halt
|
193
62
|
end
|
194
63
|
|
195
|
-
|
196
|
-
|
197
|
-
@commands << command
|
64
|
+
def raw(m)
|
65
|
+
@irc.message(m)
|
198
66
|
end
|
199
67
|
|
200
|
-
|
201
|
-
|
202
|
-
raw("PRIVMSG #{recipient} :#{text}")
|
68
|
+
def msg(recipient, m)
|
69
|
+
raw("PRIVMSG #{recipient} :#{m}")
|
203
70
|
end
|
204
71
|
|
205
|
-
# Join channel(s):
|
206
|
-
# join "#awesome_channel"
|
207
|
-
# join "#rollercoaster", "#j-lo"
|
208
72
|
def join(*channels)
|
209
73
|
channels.each {|channel| raw("JOIN #{channel}")}
|
210
74
|
end
|
211
75
|
|
212
|
-
# Part channel(s):
|
213
|
-
# part "#awesome_channel"
|
214
|
-
# part "#rollercoaster", "#j-lo"
|
215
76
|
def part(*channels)
|
216
77
|
channels.each {|channel| raw("PART #{channel}")}
|
217
78
|
end
|
218
79
|
|
219
|
-
|
220
|
-
|
221
|
-
comment = " :#{comment}" if comment
|
222
|
-
raw("KICK #{channel} #{nick}#{comment}")
|
80
|
+
def topic(channel, text)
|
81
|
+
raw("TOPIC #{channel} :#{text}")
|
223
82
|
end
|
83
|
+
end
|
224
84
|
|
225
|
-
|
226
|
-
def
|
227
|
-
|
85
|
+
class IRC
|
86
|
+
def initialize(bot, config)
|
87
|
+
@bot, @config = bot, config
|
88
|
+
@transfered = 0
|
89
|
+
@registration = []
|
90
|
+
@lock = false
|
91
|
+
@queue = []
|
92
|
+
end
|
93
|
+
|
94
|
+
def connect
|
95
|
+
@socket = TCPSocket.open(@config.server, @config.port)
|
96
|
+
message "PASSWORD #{@config.password}" if @config.password
|
97
|
+
message "NICK #{@config.nick}"
|
98
|
+
message "USER #{@config.nick} 0 * :#{@config.realname}"
|
99
|
+
@lock = true
|
100
|
+
|
101
|
+
# This should probably be somewhere else..
|
102
|
+
if @config.environment == :test
|
103
|
+
Thread.start {
|
104
|
+
while line = @socket.gets
|
105
|
+
parse line
|
106
|
+
end
|
107
|
+
}
|
108
|
+
else
|
109
|
+
while line = @socket.gets
|
110
|
+
parse line
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse(input)
|
116
|
+
puts "<< #{input}" if @bot.config.verbose
|
117
|
+
case input
|
118
|
+
when /^:\S+ 00([1-4])/
|
119
|
+
@registration << $1.to_i
|
120
|
+
if registered?
|
121
|
+
@lock = false
|
122
|
+
@bot.dispatch(:connect)
|
123
|
+
continue_queue
|
124
|
+
end
|
125
|
+
when /^:(\S+)!\S+ PRIVMSG \S+ :?\001VERSION\001/
|
126
|
+
message "NOTICE #{$1} :\001VERSION #{@bot.config.version}\001"
|
127
|
+
when /^PING (\S+)/
|
128
|
+
@transfered, @lock = 0, false
|
129
|
+
message "PONG #{$1}"
|
130
|
+
when /^:(\S+)!(\S+) PRIVMSG (\S+) :?(.*)/
|
131
|
+
env = { :nick => $1, :userhost => $2, :channel => $3, :message => $4 }
|
132
|
+
type = env[:channel].match(/^#/) ? :channel : :private
|
133
|
+
@bot.dispatch(type, env)
|
134
|
+
when /^:\S+ ([4-5]\d\d) \S+ (\S+)/
|
135
|
+
env = {:error => $1.to_i, :message => $1, :nick => $2, :channel => $2}
|
136
|
+
@bot.dispatch(:error, env)
|
137
|
+
when /^:\S+ PONG/
|
138
|
+
@transfered, @lock = 0, false
|
139
|
+
continue_queue
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def registered?
|
144
|
+
([1,2,3,4] - @registration).empty?
|
145
|
+
end
|
146
|
+
|
147
|
+
def message(msg)
|
148
|
+
@queue << msg
|
149
|
+
continue_queue
|
150
|
+
end
|
151
|
+
|
152
|
+
def continue_queue
|
153
|
+
# <= 1472 allows for \n
|
154
|
+
while !@lock && msg = @queue.shift
|
155
|
+
if (@transfered + msg.size) < 1472
|
156
|
+
@socket.puts msg
|
157
|
+
puts ">> #{msg}" if @bot.config.verbose
|
158
|
+
@transfered += msg.size + 1
|
159
|
+
else
|
160
|
+
@queue.unshift(msg)
|
161
|
+
@lock = true
|
162
|
+
@socket.puts "PING :#{@bot.config.server}"
|
163
|
+
break
|
164
|
+
end
|
165
|
+
end
|
228
166
|
end
|
229
167
|
end
|
230
168
|
end
|
231
169
|
|
232
|
-
|
233
|
-
%w(config helpers on).each do |method|
|
170
|
+
%w(configure helpers on).each do |method|
|
234
171
|
eval(<<-EOF)
|
235
172
|
def #{method}(*args, &block)
|
236
|
-
Isaac.
|
173
|
+
Isaac.bot.#{method}(*args, &block)
|
237
174
|
end
|
238
175
|
EOF
|
239
176
|
end
|
240
177
|
|
241
|
-
# Clever, thanks Sinatra.
|
242
178
|
at_exit do
|
243
|
-
|
244
|
-
|
179
|
+
unless defined?(Test::Unit)
|
180
|
+
raise $! if $!
|
181
|
+
Isaac.bot.start
|
182
|
+
end
|
245
183
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: isaac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harry Vangberg
|
@@ -9,12 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-02-23 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: Small DSL for writing IRC bots.
|
17
|
-
email:
|
17
|
+
email: harry@vangberg.name
|
18
18
|
executables: []
|
19
19
|
|
20
20
|
extensions: []
|
@@ -50,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements: []
|
51
51
|
|
52
52
|
rubyforge_project: isaac
|
53
|
-
rubygems_version: 1.
|
53
|
+
rubygems_version: 1.3.1
|
54
54
|
signing_key:
|
55
55
|
specification_version: 2
|
56
56
|
summary: The smallish DSL for writing IRC bots
|