hernan43-isaac 0.2.4

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 +83 -0
  3. data/isaac.gemspec +19 -0
  4. data/lib/isaac.rb +230 -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,83 @@
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
+ # if SSL is required set this to true
16
+ c.use_ssl = false
17
+ end
18
+ That's it. Run <tt>ruby bot.rb</tt> and it will connect to the specified server.
19
+
20
+ === Connecting
21
+ After the bot has connected to the IRC server you might want to join some channels:
22
+ on :connect do
23
+ join "#awesome_channel", "#WesternBar"
24
+ end
25
+
26
+ === Responding to messages
27
+ Joining a channel and sitting idle is not much fun. Let's repeat everything being said in these channels:
28
+
29
+ on :channel do
30
+ msg channel, message
31
+ end
32
+
33
+ Notice the +channel+ and +message+ variables. Additionally +nick+ and +match+ is
34
+ available for channel-events. +nick+ being the sender of the message, +match+
35
+ being an array of captures from the regular expression:
36
+
37
+ on :channel, /^quote this: (.*)/ do
38
+ msg channel, "Quote: '#{match[0]}' by #{nick}"
39
+ end
40
+
41
+ If you want to match private messages use the +on :private+ event:
42
+
43
+ on :private, /^login (\S+) (\S+)/ do
44
+ username = match[0]
45
+ password = match[1]
46
+ # do something to authorize or whatevz.
47
+ msg nick, "Login successful!"
48
+ end
49
+
50
+ You can also pass the RegExp captures as block arguments:
51
+
52
+ on :channel, /catch this: (.*) and this: (.*)/ do |first, last|
53
+ # `first` will contain the first regexp capture,
54
+ # `last` the second.
55
+ end
56
+
57
+ === Defining helpers
58
+ Helpers should not be defined in the top level, but instead using the +helpers+-constructor:
59
+
60
+ helpers do
61
+ def rain_check(meeting)
62
+ msg nick, "Can I have a rain check on the #{meeting}?"
63
+ end
64
+ end
65
+
66
+ on :private, /date/ do
67
+ rain_check("romantic date")
68
+ end
69
+
70
+ === Errors, errors, errors
71
+ 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".
72
+
73
+ on :error, 401 do
74
+ # Do something.
75
+ end
76
+
77
+ Available variables: +nick+ and +channel+.
78
+
79
+ == Contribute
80
+ The source is hosted at GitHub: http://github.com/ichverstehe/isaac
81
+
82
+ == License
83
+ The MIT. Google it.
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "isaac"
3
+ s.version = "0.2.4"
4
+ s.date = "2009-04-01"
5
+ s.summary = "The smallish DSL for writing IRC bots"
6
+ s.email = "harry@vangberg.name"
7
+ s.homepage = "http://github.com/ichverstehe/isaac"
8
+ s.description = "Small DSL for writing IRC bots."
9
+ s.rubyforge_project = "isaac"
10
+ s.has_rdoc = true
11
+ s.authors = ["Harry Vangberg"]
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
19
+
@@ -0,0 +1,230 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+
4
+ module Isaac
5
+ VERSION = '0.2.1'
6
+
7
+ Config = Struct.new(:server, :port, :password, :use_ssl, :nick, :realname, :version, :environment, :verbose)
8
+
9
+ def self.bot
10
+ @bot ||= Bot.new
11
+ end
12
+
13
+ class Bot
14
+ attr_accessor :config, :irc, :nick, :channel, :message, :userhost, :match,
15
+ :error
16
+
17
+ def initialize(&b)
18
+ @events = {}
19
+ @config = Config.new("localhost", 6667, nil, false, "isaac", "Isaac", 'isaac', :production, false)
20
+
21
+ instance_eval(&b) if block_given?
22
+ end
23
+
24
+ def configure(&b)
25
+ b.call(@config)
26
+ end
27
+
28
+ def on(event, match=//, &block)
29
+ match = match.to_s if match.is_a? Integer
30
+ (@events[event] ||= []) << [Regexp.new(match), block]
31
+ end
32
+
33
+ def helpers(&b)
34
+ instance_eval(&b)
35
+ end
36
+
37
+ def halt
38
+ throw :halt
39
+ end
40
+
41
+ def raw(command)
42
+ @irc.message(command)
43
+ end
44
+
45
+ def msg(recipient, text)
46
+ raw("PRIVMSG #{recipient} :#{text}")
47
+ end
48
+
49
+ def join(*channels)
50
+ channels.each {|channel| raw("JOIN #{channel}")}
51
+ end
52
+
53
+ def part(*channels)
54
+ channels.each {|channel| raw("PART #{channel}")}
55
+ end
56
+
57
+ def topic(channel, text)
58
+ raw("TOPIC #{channel} :#{text}")
59
+ end
60
+
61
+ def start
62
+ puts "Connecting to #{@config.server}:#{@config.port}" unless @config.environment == :test
63
+ @irc = IRC.new(self, @config)
64
+ @irc.connect
65
+ end
66
+
67
+ def dispatch(event, env={})
68
+ self.nick, self.userhost, self.channel, self.error =
69
+ env[:nick], env[:userhost], env[:channel], env[:error]
70
+ self.message = env[:message] || ""
71
+
72
+ if handler = find(event, message)
73
+ regexp, block = *handler
74
+ self.match = message.match(regexp).captures
75
+ invoke block
76
+ end
77
+ end
78
+
79
+ private
80
+ def find(type, message)
81
+ if events = @events[type]
82
+ events.detect {|regexp,_| message.match(regexp)}
83
+ end
84
+ end
85
+
86
+ def invoke(block)
87
+ mc = class << self; self; end
88
+ mc.send :define_method, :__isaac_event_handler, &block
89
+
90
+ bargs = case block.arity <=> 0
91
+ when -1; match
92
+ when 0; []
93
+ when 1; match[0..block.arity-1]
94
+ end
95
+
96
+ catch(:halt) { __isaac_event_handler(*bargs) }
97
+ end
98
+ end
99
+
100
+ class IRC
101
+ def initialize(bot, config)
102
+ @bot, @config = bot, config
103
+ @transfered = 0
104
+ @registration = []
105
+ end
106
+
107
+ def connect
108
+ @socket = TCPSocket.new(@config.server, @config.port)
109
+ # if the user has opted to use SSL
110
+ # convert the socket to an SSL encrypted socket
111
+ if @config.use_ssl
112
+ @socket = OpenSSL::SSL::SSLSocket.new(@socket)
113
+ @socket.connect
114
+ end
115
+
116
+ @queue = Queue.new(@socket, @bot.config.server)
117
+
118
+ message "PASS #{@config.password}" if @config.password
119
+ message "NICK #{@config.nick}"
120
+ message "USER #{@config.nick} 0 * :#{@config.realname}"
121
+
122
+ @queue.lock
123
+
124
+ while line = @socket.gets
125
+ parse line
126
+ end
127
+ end
128
+
129
+ def parse(input)
130
+ puts "<< #{input}" if @bot.config.verbose
131
+ case input.chomp
132
+ when /^:\S+ 00([1-4])/
133
+ @registration << $1.to_i
134
+ if registered?
135
+ @queue.unlock
136
+ @bot.dispatch(:connect)
137
+ end
138
+ when /^:(\S+)!\S+ PRIVMSG \S+ :?\001VERSION\001/
139
+ message "NOTICE #{$1} :\001VERSION #{@bot.config.version}\001"
140
+ when /^PING (\S+)/
141
+ @queue.unlock
142
+ message "PONG #{$1}"
143
+ when /^:(\S+)!(\S+) PRIVMSG (\S+) :?(.*)/
144
+ env = { :nick => $1, :userhost => $2, :channel => $3, :message => $4 }
145
+ type = env[:channel].match(/^#/) ? :channel : :private
146
+ @bot.dispatch(type, env)
147
+ when /^:\S+ ([4-5]\d\d) \S+ (\S+)/
148
+ env = {:error => $1.to_i, :message => $1, :nick => $2, :channel => $2}
149
+ @bot.dispatch(:error, env)
150
+ when /^:\S+ PONG/
151
+ @queue.unlock
152
+ end
153
+ end
154
+
155
+ def registered?
156
+ ([1,2,3,4] - @registration).empty?
157
+ end
158
+
159
+ def message(msg)
160
+ @queue << msg
161
+ end
162
+ end
163
+
164
+ class Queue
165
+ def initialize(socket, server)
166
+ # We need server for pinging us out of an excess flood
167
+ @socket, @server = socket, server
168
+ @queue, @lock, @transfered = [], false, 0
169
+ end
170
+
171
+ def lock
172
+ @lock = true
173
+ end
174
+
175
+ def unlock
176
+ @lock, @transfered = false, 0
177
+ invoke
178
+ end
179
+
180
+ def <<(message)
181
+ @queue << message
182
+ invoke
183
+ end
184
+
185
+ private
186
+ def message_to_send?
187
+ !@lock && !@queue.empty?
188
+ end
189
+
190
+ def transfered_after_next_send
191
+ @transfered + @queue.first.size + 1 # the 1 is for \n
192
+ end
193
+
194
+ def exceed_limit?
195
+ transfered_after_next_send > 1472
196
+ end
197
+
198
+ def lock_and_ping
199
+ lock
200
+ @socket.puts "PING :#{@server}"
201
+ end
202
+
203
+ def invoke
204
+ while message_to_send?
205
+ if exceed_limit?
206
+ lock_and_ping; break
207
+ else
208
+ @transfered = transfered_after_next_send
209
+ @socket.puts @queue.shift
210
+ # puts ">> #{msg}" if @bot.config.verbose
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ %w(configure helpers on).each do |method|
218
+ eval(<<-EOF)
219
+ def #{method}(*args, &block)
220
+ Isaac.bot.#{method}(*args, &block)
221
+ end
222
+ EOF
223
+ end
224
+
225
+ at_exit do
226
+ unless defined?(Test::Unit)
227
+ raise $! if $!
228
+ Isaac.bot.start
229
+ end
230
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hernan43-isaac
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ platform: ruby
6
+ authors:
7
+ - Harry Vangberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-01 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Small DSL for writing IRC bots.
17
+ email: harry@vangberg.name
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/ichverstehe/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
+