galador-isaac 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE +6 -0
  2. data/README.rdoc +81 -0
  3. data/isaac.gemspec +18 -0
  4. data/lib/isaac.rb +240 -0
  5. metadata +58 -0
data/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ ------------------------------------------------------------------------------
2
+ "THE BEER-WARE LICENSE" (Revision 42):
3
+ <ichverstehe@gmail.com> wrote this file. As long as you retain this notice you
4
+ can do whatever you want with this stuff. If we meet some day, and you think
5
+ this stuff is worth it, you can buy me a beer in return.
6
+ ------------------------------------------------------------------------------
@@ -0,0 +1,81 @@
1
+ = Isaac - the smallish DSL for writing IRC bots
2
+
3
+ == Features
4
+ * Wraps parsing of incoming messages and raw IRC commands in simple constructs.
5
+ * Hides all the ugly regular expressions of matching IRC commands. Leaves only the essentials for you to match.
6
+ * Takes care of dull stuff such as replying to PING-messages and avoiding excess flood.
7
+
8
+ == Getting started
9
+ An Isaac-bot needs a few basics:
10
+ require 'isaac'
11
+ configure do |c|
12
+ c.nick = "AwesomeBot"
13
+ c.server = "irc.freenode.net"
14
+ c.port = 6667
15
+ end
16
+ That's it. Run <tt>ruby bot.rb</tt> and it will connect to the specified server.
17
+
18
+ === Connecting
19
+ After the bot has connected to the IRC server you might want to join some channels:
20
+ on :connect do
21
+ join "#awesome_channel", "#WesternBar"
22
+ end
23
+
24
+ === Responding to messages
25
+ Joining a channel and sitting idle is not much fun. Let's repeat everything being said in these channels:
26
+
27
+ on :channel do
28
+ msg channel, message
29
+ end
30
+
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:
34
+
35
+ on :channel, /^quote this: (.*)/ do
36
+ msg channel, "Quote: '#{match[0]}' by #{nick}"
37
+ end
38
+
39
+ If you want to match private messages use the +on :private+ event:
40
+
41
+ on :private, /^login (\S+) (\S+)/ do
42
+ username = match[0]
43
+ password = match[1]
44
+ # do something to authorize or whatevz.
45
+ msg nick, "Login successful!"
46
+ end
47
+
48
+ You can also pass the RegExp captures as block arguments:
49
+
50
+ on :channel, /catch this: (.*) and this: (.*)/ do |first, last|
51
+ # `first` will contain the first regexp capture,
52
+ # `last` the second.
53
+ end
54
+
55
+ === Defining helpers
56
+ Helpers should not be defined in the top level, but instead using the +helpers+-constructor:
57
+
58
+ helpers do
59
+ def rain_check(meeting)
60
+ msg nick, "Can I have a rain check on the #{meeting}?"
61
+ end
62
+ end
63
+
64
+ on :private, /date/ do
65
+ rain_check("romantic date")
66
+ end
67
+
68
+ === Errors, errors, errors
69
+ Errors, as specified by RFC 1459, can be reacted upon as well. If you e.g. try to send a message to a non-existant nick you will get error 401: "No such nick/channel".
70
+
71
+ on :error, 401 do
72
+ # Do something.
73
+ end
74
+
75
+ Available variables: +nick+ and +channel+.
76
+
77
+ == Contribute
78
+ The source is hosted at GitHub: http://github.com/ichverstehe/isaac
79
+
80
+ == License
81
+ The MIT. Google it.
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "isaac"
3
+ s.version = "0.2.6"
4
+ s.date = "2009-07-12"
5
+ s.summary = "The smallish DSL for writing IRC bots"
6
+ s.email = "bryan@galador.org"
7
+ s.homepage = "http://github.com/galador/isaac"
8
+ s.description = "Small DSL for writing IRC bots."
9
+ s.rubyforge_project = "isaac"
10
+ s.has_rdoc = true
11
+ s.authors = ["Bryan H."]
12
+ s.files = ["README.rdoc",
13
+ "LICENSE",
14
+ "isaac.gemspec",
15
+ "lib/isaac.rb"]
16
+ s.rdoc_options = ["--main", "README.rdoc"]
17
+ s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
18
+ end
@@ -0,0 +1,240 @@
1
+ require 'socket'
2
+
3
+ module Isaac
4
+ VERSION = '0.2.1'
5
+
6
+ Config = Struct.new(:server, :port, :ssl, :password, :nick, :realname, :version, :environment, :verbose)
7
+
8
+ def self.bot
9
+ @bot ||= Bot.new
10
+ end
11
+
12
+ class Bot
13
+ attr_accessor :config, :irc, :nick, :channel, :message, :userhost, :match,
14
+ :error
15
+
16
+ def initialize(&b)
17
+ @events = {}
18
+ @config = Config.new("localhost", 6667, false, nil, "isaac", "Isaac", 'isaac', :production, false)
19
+
20
+ instance_eval(&b) if block_given?
21
+ end
22
+
23
+ def configure(&b)
24
+ b.call(@config)
25
+ end
26
+
27
+ def on(event, match=//, &block)
28
+ match = match.to_s if match.is_a? Integer
29
+ (@events[event] ||= []) << [Regexp.new(match), block]
30
+ end
31
+
32
+ def helpers(&b)
33
+ instance_eval(&b)
34
+ end
35
+
36
+ def halt
37
+ throw :halt
38
+ end
39
+
40
+ def raw(command)
41
+ @irc.message(command)
42
+ end
43
+
44
+ def msg(recipient, text)
45
+ raw("PRIVMSG #{recipient} :#{text}")
46
+ end
47
+
48
+ def join(*channels)
49
+ channels.each {|channel| raw("JOIN #{channel}")}
50
+ end
51
+
52
+ def part(*channels)
53
+ channels.each {|channel| raw("PART #{channel}")}
54
+ end
55
+
56
+ def topic(channel, text)
57
+ raw("TOPIC #{channel} :#{text}")
58
+ end
59
+
60
+ def quit(message=nil)
61
+ command = message ? "QUIT :#{message}" : "QUIT"
62
+ raw command
63
+ end
64
+
65
+ def start
66
+ puts "Connecting to #{@config.server}:#{@config.port}" unless @config.environment == :test
67
+ @irc = IRC.new(self, @config)
68
+ @irc.connect
69
+ end
70
+
71
+ def dispatch(event, env={})
72
+ self.nick, self.userhost, self.channel, self.error =
73
+ env[:nick], env[:userhost], env[:channel], env[:error]
74
+ self.message = env[:message] || ""
75
+
76
+ if handler = find(event, message)
77
+ regexp, block = *handler
78
+ self.match = message.match(regexp).captures
79
+ invoke block
80
+ end
81
+ end
82
+
83
+ private
84
+ def find(type, message)
85
+ if events = @events[type]
86
+ events.detect {|regexp,_| message.match(regexp)}
87
+ end
88
+ end
89
+
90
+ def invoke(block)
91
+ mc = class << self; self; end
92
+ mc.send :define_method, :__isaac_event_handler, &block
93
+
94
+ bargs = case block.arity <=> 0
95
+ when -1; match
96
+ when 0; []
97
+ when 1; match[0..block.arity-1]
98
+ end
99
+
100
+ catch(:halt) { __isaac_event_handler(*bargs) }
101
+ end
102
+ end
103
+
104
+ class IRC
105
+ def initialize(bot, config)
106
+ @bot, @config = bot, config
107
+ @transfered = 0
108
+ @registration = []
109
+ end
110
+
111
+ def connect
112
+ tcpsocket = TCPSocket.open(@config.server, @config.port)
113
+ if @config.ssl
114
+ require 'openssl'
115
+ ssl_context = OpenSSL::SSL::SSLContext.new()
116
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
117
+ @socket = OpenSSL::SSL::SSLSocket.new(tcpsocket, ssl_context)
118
+ @socket.sync = true
119
+ @socket.connect
120
+ else
121
+ @socket = tcpsocket
122
+ end
123
+
124
+ @queue = Queue.new(@socket, @bot.config.server)
125
+ message "PASS #{@config.password}" if @config.password
126
+ message "NICK #{@config.nick}"
127
+ message "USER #{@config.nick} 0 * :#{@config.realname}"
128
+ @queue.lock
129
+
130
+ while line = @socket.gets
131
+ parse line
132
+ end
133
+ end
134
+
135
+ def parse(input)
136
+ puts "<< #{input}" if @bot.config.verbose
137
+ case input.chomp
138
+ when /(^:\S+ )?00([1-4])/
139
+ @registration << $2.to_i
140
+ if registered?
141
+ @queue.unlock
142
+ @bot.dispatch(:connect)
143
+ end
144
+ when /(^:(\S+)!\S+ )?PRIVMSG \S+ :?\001VERSION\001/
145
+ message "NOTICE #{$2} :\001VERSION #{@bot.config.version}\001"
146
+ when /^PING (\S+)/
147
+ @queue.unlock
148
+ message "PONG #{$1}"
149
+ when /(^:(\S+)!(\S+) )?PRIVMSG (\S+) :?(.*)/
150
+ env = { :nick => $2, :userhost => $3, :channel => $4, :message => $5 }
151
+ type = env[:channel].match(/^#/) ? :channel : :private
152
+ @bot.dispatch(type, env)
153
+ when /(^:\S+ )?([4-5]\d\d) \S+ (\S+)/
154
+ env = {:error => $2.to_i, :message => $2, :nick => $3, :channel => $3}
155
+ @bot.dispatch(:error, env)
156
+ when /(^:\S+ )?PONG/
157
+ @queue.unlock
158
+ end
159
+ end
160
+
161
+ def registered?
162
+ ([1,2,3,4] - @registration).empty?
163
+ end
164
+
165
+ def message(msg)
166
+ @queue << msg
167
+ end
168
+ end
169
+
170
+ class Queue
171
+ def initialize(socket, server)
172
+ # We need server for pinging us out of an excess flood
173
+ @socket, @server = socket, server
174
+ @queue, @lock, @transfered = [], false, 0
175
+ end
176
+
177
+ def lock
178
+ @lock = true
179
+ end
180
+
181
+ def unlock
182
+ @lock, @transfered = false, 0
183
+ invoke
184
+ end
185
+
186
+ def <<(message)
187
+ @queue << message
188
+ invoke
189
+ end
190
+
191
+ private
192
+ def message_to_send?
193
+ !@lock && !@queue.empty?
194
+ end
195
+
196
+ def transfered_after_next_send
197
+ @transfered + @queue.first.size + 2 # the 2 is for \r\n
198
+ end
199
+
200
+ def exceed_limit?
201
+ transfered_after_next_send > 1472
202
+ end
203
+
204
+ def lock_and_ping
205
+ lock
206
+ @socket.print "PING :#{@server}\r\n"
207
+ end
208
+
209
+ def next_message
210
+ @queue.shift.to_s.chomp + "\r\n"
211
+ end
212
+
213
+ def invoke
214
+ while message_to_send?
215
+ if exceed_limit?
216
+ lock_and_ping; break
217
+ else
218
+ @transfered = transfered_after_next_send
219
+ @socket.print next_message
220
+ # puts ">> #{msg}" if @bot.config.verbose
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ %w(configure helpers on).each do |method|
228
+ eval(<<-EOF)
229
+ def #{method}(*args, &block)
230
+ Isaac.bot.#{method}(*args, &block)
231
+ end
232
+ EOF
233
+ end
234
+
235
+ at_exit do
236
+ unless defined?(Test::Unit)
237
+ raise $! if $!
238
+ Isaac.bot.start
239
+ end
240
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: galador-isaac
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.6
5
+ platform: ruby
6
+ authors:
7
+ - Bryan H.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Small DSL for writing IRC bots.
17
+ email: bryan@galador.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - README.rdoc
27
+ - LICENSE
28
+ - isaac.gemspec
29
+ - lib/isaac.rb
30
+ has_rdoc: true
31
+ homepage: http://github.com/galador/isaac
32
+ post_install_message:
33
+ rdoc_options:
34
+ - --main
35
+ - README.rdoc
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: "0"
43
+ version:
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ requirements: []
51
+
52
+ rubyforge_project: isaac
53
+ rubygems_version: 1.2.0
54
+ signing_key:
55
+ specification_version: 2
56
+ summary: The smallish DSL for writing IRC bots
57
+ test_files: []
58
+