jxl-isaac 0.2.2

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 +88 -0
  3. data/isaac.gemspec +18 -0
  4. data/lib/isaac.rb +172 -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
+ ------------------------------------------------------------------------------
data/README.rdoc ADDED
@@ -0,0 +1,88 @@
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
+ === Defining helpers
49
+ Helpers should not be defined in the top level, but instead using the +helpers+-constructor:
50
+
51
+ helpers do
52
+ def rain_check(meeting)
53
+ msg nick, "Can I have a rain check on the #{meeting}?"
54
+ end
55
+ end
56
+
57
+ on :private, /date/ do
58
+ rain_check("romantic date")
59
+ end
60
+
61
+ === Errors, errors, errors
62
+ 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".
63
+
64
+ on :error, 401 do
65
+ # Do something.
66
+ end
67
+
68
+ Available variables: +nick+ and +channel+.
69
+
70
+ === Send commands from outside an event (not implemented in Shaft atm)
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:
72
+
73
+ class K
74
+ def smoke(brand)
75
+ Isaac.execute { msg "harryjr", "you should smoke #{brand} cigarettes" }
76
+ end
77
+ end
78
+
79
+ on :connect do
80
+ k = K.new
81
+ k.smoke("Lucky Strike")
82
+ end
83
+
84
+ == Contribute
85
+ The source is hosted at GitHub: http://github.com/ichverstehe/isaac
86
+
87
+ == License
88
+ The MIT. Google it.
data/isaac.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "isaac"
3
+ s.version = "0.2.2"
4
+ s.date = "2009-02-23"
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
data/lib/isaac.rb ADDED
@@ -0,0 +1,172 @@
1
+ require 'socket'
2
+
3
+ module Isaac
4
+ VERSION = '0.2.1'
5
+
6
+ Config = Struct.new(:server, :port, :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, nil, "isaac", "Isaac", 'isaac', :production, false)
19
+
20
+ instance_eval(&b) if block_given?
21
+ end
22
+
23
+ def start
24
+ puts "Connecting to #{@config.server}:#{@config.port}" unless @config.environment == :test
25
+ @irc = IRC.new(self, @config)
26
+ @irc.connect
27
+ end
28
+
29
+ def on(event, match=//, &b)
30
+ match = match.to_s if match.is_a? Integer
31
+ (@events[event] ||= []) << [Regexp.new(match), b]
32
+ end
33
+
34
+ def helpers(&b)
35
+ instance_eval &b
36
+ end
37
+
38
+ def configure(&b)
39
+ b.call(@config)
40
+ end
41
+
42
+ def dispatch(event, env={})
43
+ self.nick, self.userhost, self.channel, self.error =
44
+ env[:nick], env[:userhost], env[:channel], env[:error]
45
+ self.message = env[:message] || ""
46
+
47
+ event = @events[event] && @events[event].detect do |regexp,_|
48
+ message.match(regexp)
49
+ end
50
+
51
+ if event
52
+ regexp, block = *event
53
+ self.match = message.match(regexp).captures
54
+ catch(:halt) { instance_eval(&block) }
55
+ end
56
+ end
57
+
58
+ def halt
59
+ throw :halt
60
+ end
61
+
62
+ def raw(m)
63
+ @irc.message(m)
64
+ end
65
+
66
+ def msg(recipient, m)
67
+ raw("PRIVMSG #{recipient} :#{m}")
68
+ end
69
+
70
+ def join(*channels)
71
+ channels.each {|channel| raw("JOIN #{channel}")}
72
+ end
73
+
74
+ def part(*channels)
75
+ channels.each {|channel| raw("PART #{channel}")}
76
+ end
77
+
78
+ def topic(channel, text)
79
+ raw("TOPIC #{channel} :#{text}")
80
+ end
81
+ end
82
+
83
+ class IRC
84
+ def initialize(bot, config)
85
+ @bot, @config = bot, config
86
+ @transfered = 0
87
+ @registration = []
88
+ @lock = false
89
+ @queue = []
90
+ end
91
+
92
+ def connect
93
+ @socket = TCPSocket.open(@config.server, @config.port)
94
+ message "PASS #{@config.password}" if @config.password
95
+ message "NICK #{@config.nick}"
96
+ message "USER #{@config.nick} 0 * :#{@config.realname}"
97
+ @lock = true
98
+
99
+ while line = @socket.gets
100
+ parse line
101
+ end
102
+ end
103
+
104
+ def parse(input)
105
+ puts "<< #{input}" if @bot.config.verbose
106
+ case input
107
+ when /^:\S+ 00([1-4])/
108
+ @registration << $1.to_i
109
+ if registered?
110
+ @lock = false
111
+ @bot.dispatch(:connect)
112
+ continue_queue
113
+ end
114
+ when /^:(\S+)!\S+ PRIVMSG \S+ :?\001VERSION\001/
115
+ message "NOTICE #{$1} :\001VERSION #{@bot.config.version}\001"
116
+ when /^PING (\S+)/
117
+ @transfered, @lock = 0, false
118
+ message "PONG #{$1}"
119
+ when /^:(\S+)!(\S+) PRIVMSG (\S+) :?(.*)/
120
+ env = { :nick => $1, :userhost => $2, :channel => $3, :message => $4 }
121
+ type = env[:channel].match(/^#/) ? :channel : :private
122
+ @bot.dispatch(type, env)
123
+ when /^:\S+ ([4-5]\d\d) \S+ (\S+)/
124
+ env = {:error => $1.to_i, :message => $1, :nick => $2, :channel => $2}
125
+ @bot.dispatch(:error, env)
126
+ when /^:\S+ PONG/
127
+ @transfered, @lock = 0, false
128
+ continue_queue
129
+ end
130
+ end
131
+
132
+ def registered?
133
+ ([1,2,3,4] - @registration).empty?
134
+ end
135
+
136
+ def message(msg)
137
+ @queue << msg
138
+ continue_queue
139
+ end
140
+
141
+ def continue_queue
142
+ # <= 1472 allows for \n
143
+ while !@lock && msg = @queue.shift
144
+ if (@transfered + msg.size) < 1472
145
+ @socket.puts msg
146
+ puts ">> #{msg}" if @bot.config.verbose
147
+ @transfered += msg.size + 1
148
+ else
149
+ @queue.unshift(msg)
150
+ @lock = true
151
+ @socket.puts "PING :#{@bot.config.server}"
152
+ break
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ %w(configure helpers on).each do |method|
160
+ eval(<<-EOF)
161
+ def #{method}(*args, &block)
162
+ Isaac.bot.#{method}(*args, &block)
163
+ end
164
+ EOF
165
+ end
166
+
167
+ at_exit do
168
+ unless defined?(Test::Unit)
169
+ raise $! if $!
170
+ Isaac.bot.start
171
+ end
172
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jxl-isaac
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Harry Vangberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-23 00:00:00 -08: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
+