badboy-jabbot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +135 -0
- data/VERSION.yml +4 -0
- data/lib/hash.rb +8 -0
- data/lib/jabbot.rb +84 -0
- data/lib/jabbot/bot.rb +244 -0
- data/lib/jabbot/config.rb +102 -0
- data/lib/jabbot/handlers.rb +116 -0
- data/lib/jabbot/macros.rb +85 -0
- data/lib/jabbot/message.rb +7 -0
- data/test/test_bot.rb +126 -0
- data/test/test_config.rb +81 -0
- data/test/test_handler.rb +191 -0
- data/test/test_hash.rb +34 -0
- data/test/test_helper.rb +38 -0
- metadata +97 -0
data/README.rdoc
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
= Jabbot
|
2
|
+
Official URL: http://github.com/badboy/jabbot/tree/master
|
3
|
+
Jan-Erik Rediger (BadBoy_) (http://badboy.pytalhost.de)
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Jabbot is a Ruby microframework for creating Jabber/MUC
|
8
|
+
bots, heavily inspired by Sinatra and Twibot.
|
9
|
+
I modified the code of Twibot to fit my needs.
|
10
|
+
The original Twibot code is located at:
|
11
|
+
http://github.com/cjohansen/twibot/tree/master
|
12
|
+
|
13
|
+
A big thank you to Christian Johansen, who wrote the code for Twibot.
|
14
|
+
Jabbot is heavily based on his code.
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
=== Simple example
|
19
|
+
|
20
|
+
require 'jabbot'
|
21
|
+
|
22
|
+
# Receive messages, and post them publicly
|
23
|
+
#
|
24
|
+
message do |message, params|
|
25
|
+
post message.text
|
26
|
+
end
|
27
|
+
|
28
|
+
# Respond to query if they come from the right crowd
|
29
|
+
# post "message" => "user" is just some syntax sugar
|
30
|
+
# post "message", "user" will work to
|
31
|
+
query :from => [:cjno, :irbno] do |message, params|
|
32
|
+
post "#{message.user} I agree" => message.user
|
33
|
+
end
|
34
|
+
|
35
|
+
# Listen in and log tweets
|
36
|
+
# (you can use "message :all" too ;)
|
37
|
+
message do |message, params|
|
38
|
+
MyApp.log_message(message)
|
39
|
+
end
|
40
|
+
|
41
|
+
=== Running the bot
|
42
|
+
|
43
|
+
To run the bot, simply do:
|
44
|
+
|
45
|
+
ruby bot.rb
|
46
|
+
|
47
|
+
=== Configuration
|
48
|
+
|
49
|
+
Jabbot looks for a configuration file in ./config/bot.yml. It should contain
|
50
|
+
atleast:
|
51
|
+
|
52
|
+
login: jabber_login
|
53
|
+
password: jabber_password
|
54
|
+
channel: channel_to_join
|
55
|
+
server: server_to_connect_to
|
56
|
+
|
57
|
+
You can also configure with Ruby:
|
58
|
+
|
59
|
+
configure do |conf|
|
60
|
+
conf.login = "my_account"
|
61
|
+
do
|
62
|
+
|
63
|
+
If you don't specify login and/or password in any of these ways, Jabbot will fail
|
64
|
+
|
65
|
+
=== "Routes"
|
66
|
+
|
67
|
+
Like Sinatra, and other web app frameworks, Jabbot supports "routes": patterns
|
68
|
+
to match incoming tweets and messages:
|
69
|
+
|
70
|
+
require 'jabbot'
|
71
|
+
|
72
|
+
message "time :country :city" do |message,params|
|
73
|
+
time = MyTimeService.lookup(params[:country], params[:city])
|
74
|
+
post "Time is #{time} in #{params[:city]}, #{params[:country]}"
|
75
|
+
end
|
76
|
+
|
77
|
+
You can have several "message" blocks (or "join", "leave", "query" or "subject").
|
78
|
+
Every matching block will be called.
|
79
|
+
|
80
|
+
Jabbot also supports regular expressions as routes:
|
81
|
+
|
82
|
+
require 'jabbot'
|
83
|
+
|
84
|
+
message /^time ([^\s]*) ([^\s]*)/ do |message, params|
|
85
|
+
# params is an array of matches when using regexp routes
|
86
|
+
time = MyTimeService.lookup(params[0], params[1])
|
87
|
+
post "Time is #{time} in #{params[:city]}, #{params[:country]}"
|
88
|
+
end
|
89
|
+
|
90
|
+
== Requirements
|
91
|
+
|
92
|
+
xmpp4r. You'll need atleast 0.4.
|
93
|
+
You can get it via rubygems:
|
94
|
+
gem install xmpp4r
|
95
|
+
or download it from:
|
96
|
+
http://home.gna.org/xmpp4r/
|
97
|
+
|
98
|
+
== Installation
|
99
|
+
|
100
|
+
gem sources -a http://gems.github.com # you only have to do this once
|
101
|
+
gem install badboy-jabbot
|
102
|
+
|
103
|
+
== Is it Ruby 1.9?
|
104
|
+
|
105
|
+
All tests passes even on Ruby 1.9.
|
106
|
+
Seems like it works :)
|
107
|
+
|
108
|
+
== Contributors
|
109
|
+
|
110
|
+
* Christian Johansen (cjohansen) (author of Twibot) - http://www.cjohansen.no
|
111
|
+
|
112
|
+
== License
|
113
|
+
|
114
|
+
(The MIT License)
|
115
|
+
|
116
|
+
Copyright (c) 2009 Jan-Erik Rediger (Badboy_)
|
117
|
+
|
118
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
119
|
+
a copy of this software and associated documentation files (the
|
120
|
+
'Software'), to deal in the Software without restriction, including
|
121
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
122
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
123
|
+
permit persons to whom the Software is furnished to do so, subject to
|
124
|
+
the following conditions:
|
125
|
+
|
126
|
+
The above copyright notice and this permission notice shall be
|
127
|
+
included in all copies or substantial portions of the Software.
|
128
|
+
|
129
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
130
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
131
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
132
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
133
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
134
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
135
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/VERSION.yml
ADDED
data/lib/hash.rb
ADDED
data/lib/jabbot.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'xmpp4r'
|
3
|
+
require 'xmpp4r/muc/helper/simplemucclient'
|
4
|
+
#require 'xmpp4r/muc/helper/mucclient'
|
5
|
+
require 'xmpp4r/version/helper/simpleresponder'
|
6
|
+
require 'yaml'
|
7
|
+
require File.join(File.dirname(__FILE__), 'hash')
|
8
|
+
|
9
|
+
module Jabbot
|
10
|
+
|
11
|
+
# :stopdoc:
|
12
|
+
VERSION = '0.0.1'
|
13
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
14
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
15
|
+
# :startdoc:
|
16
|
+
|
17
|
+
# Returns the version string for the library.
|
18
|
+
#
|
19
|
+
def self.version
|
20
|
+
VERSION
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the library path for the module. If any arguments are given,
|
24
|
+
# they will be joined to the end of the libray path using
|
25
|
+
# <tt>File.join</tt>.
|
26
|
+
#
|
27
|
+
def self.libpath( *args )
|
28
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the lpath for the module. If any arguments are given,
|
32
|
+
# they will be joined to the end of the path using
|
33
|
+
# <tt>File.join</tt>.
|
34
|
+
#
|
35
|
+
def self.path( *args )
|
36
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Utility method used to require all files ending in .rb that lie in the
|
40
|
+
# directory below this file that has the same name as the filename passed
|
41
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
42
|
+
# the _filename_ does not have to be equivalent to the directory.
|
43
|
+
#
|
44
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
45
|
+
dir ||= File.basename(fname, '.*')
|
46
|
+
search_me = File.expand_path(File.join(File.dirname(fname), dir, '**', '*.rb'))
|
47
|
+
Dir.glob(search_me).sort.each {|rb| require rb }
|
48
|
+
end
|
49
|
+
|
50
|
+
@@app_file = lambda do
|
51
|
+
ignore = [
|
52
|
+
/lib\/twibot.*\.rb/, # Library
|
53
|
+
/\(.*\)/, # Generated code
|
54
|
+
/custom_require\.rb/ # RubyGems require
|
55
|
+
]
|
56
|
+
|
57
|
+
path = caller.map { |line| line.split(/:\d/, 2).first }.find do |file|
|
58
|
+
next if ignore.any? { |pattern| file =~ pattern }
|
59
|
+
file
|
60
|
+
end
|
61
|
+
|
62
|
+
path || $0
|
63
|
+
end.call
|
64
|
+
|
65
|
+
#
|
66
|
+
# File name of the application file. Inspired by Sinatra
|
67
|
+
#
|
68
|
+
def self.app_file
|
69
|
+
@@app_file
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Runs application if application file is the script being executed
|
74
|
+
#
|
75
|
+
def self.run?
|
76
|
+
self.app_file == $0
|
77
|
+
end
|
78
|
+
|
79
|
+
end # module Jabbot
|
80
|
+
|
81
|
+
Thread.abort_on_exception = true
|
82
|
+
Jabbot.require_all_libs_relative_to(__FILE__)
|
83
|
+
|
84
|
+
# EOF
|
data/lib/jabbot/bot.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'macros')
|
3
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'handlers')
|
4
|
+
|
5
|
+
module Jabbot
|
6
|
+
#
|
7
|
+
# Main bot "controller" class
|
8
|
+
#
|
9
|
+
class Bot
|
10
|
+
include Jabbot::Handlers
|
11
|
+
attr_reader :client
|
12
|
+
attr_reader :user
|
13
|
+
|
14
|
+
Message = Struct.new(:user, :text, :time) do
|
15
|
+
def to_s
|
16
|
+
"#{user}: #{text}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(options = nil)
|
21
|
+
@conf = nil
|
22
|
+
@config = options || Jabbot::Config.default << Jabbot::FileConfig.new
|
23
|
+
@log = nil
|
24
|
+
@abort = false
|
25
|
+
@user = []
|
26
|
+
|
27
|
+
rescue Exception => krash
|
28
|
+
raise SystemExit.new(krash.message)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# connect to Jabber and join channel
|
33
|
+
#
|
34
|
+
def connect
|
35
|
+
@jid = Jabber::JID.new(login)
|
36
|
+
@mucjid = Jabber::JID.new("#{channel}@#{server}")
|
37
|
+
|
38
|
+
if @jid.node.nil?
|
39
|
+
raise "Your Jabber ID must contain a user name and therefore contain one @ character."
|
40
|
+
elsif @jid.resource
|
41
|
+
raise "If you intend to set a custom resource, put that in the right text field. Remove the slash!"
|
42
|
+
elsif @mucjid.node.nil?
|
43
|
+
raise "Please set a room name, e.g. myroom@conference.jabber.org"
|
44
|
+
elsif @mucjid.resource
|
45
|
+
raise "The MUC room must not contain a resource. Remove the slash!"
|
46
|
+
else
|
47
|
+
@jid.resource = "jabbot1"
|
48
|
+
@mucjid.resource = (config[:nick]||="jabbot")
|
49
|
+
@user << config[:nick]
|
50
|
+
end
|
51
|
+
|
52
|
+
@client = Jabber::Client.new(@jid)
|
53
|
+
@connected = true
|
54
|
+
begin
|
55
|
+
@client.connect
|
56
|
+
@client.auth(password)
|
57
|
+
@muc = Jabber::MUC::SimpleMUCClient.new(@client)
|
58
|
+
muc_handlers.call(@muc)
|
59
|
+
@muc.join(@mucjid)
|
60
|
+
rescue => errmsg
|
61
|
+
STDERR.write "#{errmsg.class}\n#{errmsg}, #{errmsg.backtrace.join("\n")}"
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Run application
|
68
|
+
#
|
69
|
+
def run!
|
70
|
+
puts "Jabbot #{Jabbot::VERSION} imposing as #{login} on #{channel}@#{server}"
|
71
|
+
|
72
|
+
trap(:INT) do
|
73
|
+
puts "\nAnd it's a wrap. See ya soon!"
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
connect
|
78
|
+
poll
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# just a lame infinite loop to keep the bot alive while he is connected
|
83
|
+
# :)
|
84
|
+
#
|
85
|
+
def poll
|
86
|
+
while connected?
|
87
|
+
break unless connected?
|
88
|
+
sleep 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# still connected?
|
94
|
+
#
|
95
|
+
def connected?
|
96
|
+
@connected
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# close connection
|
101
|
+
#
|
102
|
+
def close
|
103
|
+
if connected?
|
104
|
+
@connected = false
|
105
|
+
client.close
|
106
|
+
end
|
107
|
+
end
|
108
|
+
alias_method :quit, :close
|
109
|
+
|
110
|
+
#
|
111
|
+
# send message
|
112
|
+
# alternative: send query to user
|
113
|
+
#
|
114
|
+
def send_message(msg, to=nil)
|
115
|
+
@muc.say(msg.to_s, to)
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# defines what to do on different actions
|
120
|
+
#
|
121
|
+
def muc_handlers
|
122
|
+
Proc.new do |muc|
|
123
|
+
muc.on_message do |time, nick, text|
|
124
|
+
if time.nil?
|
125
|
+
begin
|
126
|
+
dispatch_messages(:message, [Message.new(nick, text, Time.now)]) unless nick == config[:nick]
|
127
|
+
rescue Exception => boom
|
128
|
+
log.fatal boom.inspect
|
129
|
+
log.fatal boom.backtrace[0..5].join("\n")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
muc.on_private_message do |time, nick, text|
|
135
|
+
if time.nil?
|
136
|
+
begin
|
137
|
+
dispatch_messages(:private, [Message.new(nick, text, Time.now)]) unless nick == config[:nick]
|
138
|
+
rescue Exception => boom
|
139
|
+
log.fatal boom.inspect
|
140
|
+
log.fatal boom.backtrace[0..5].join("\n")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
muc.on_join do |time, nick|
|
146
|
+
unless @user.include? nick
|
147
|
+
@user << nick
|
148
|
+
end
|
149
|
+
if time.nil?
|
150
|
+
begin
|
151
|
+
dispatch_messages(:join, [Message.new(nick, "join", Time.now)]) unless nick == config[:nick]
|
152
|
+
rescue Exception => boom
|
153
|
+
log.fatal boom.inspect
|
154
|
+
log.fatal boom.backtrace[0..5].join("\n")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
muc.on_leave do |time, nick|
|
160
|
+
@user.delete(nick)
|
161
|
+
if time.nil?
|
162
|
+
begin
|
163
|
+
dispatch_messages(:leave, [Message.new(nick, "leave", Time.now)])
|
164
|
+
rescue Exception => boom
|
165
|
+
log.fatal boom.inspect
|
166
|
+
log.fatal boom.backtrace[0..5].join("\n")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
muc.on_subject do |time, nick, subject|
|
172
|
+
if time.nil?
|
173
|
+
begin
|
174
|
+
dispatch_messages(:subject, [Message.new(nick, subject, Time.now)])
|
175
|
+
rescue Exception => boom
|
176
|
+
log.fatal boom.inspect
|
177
|
+
log.fatal boom.backtrace[0..5].join("\n")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# not working
|
183
|
+
#muc.on_self_leave do |*args|
|
184
|
+
# p args
|
185
|
+
#end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Dispatch a collection of messages
|
191
|
+
#
|
192
|
+
def dispatch_messages(type, messages)
|
193
|
+
messages.each { |message| dispatch(type, message) }
|
194
|
+
messages.length
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# Return logger instance
|
199
|
+
#
|
200
|
+
def log
|
201
|
+
return @log if @log
|
202
|
+
os = config[:log_file] ? File.open(config[:log_file], "a") : $stdout
|
203
|
+
@log = Logger.new(os)
|
204
|
+
@log.level = Logger.const_get(config[:log_level] ? config[:log_level].upcase : "INFO")
|
205
|
+
@log
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Configure bot
|
210
|
+
#
|
211
|
+
def configure
|
212
|
+
yield @config
|
213
|
+
@conf = nil
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
#
|
218
|
+
# Map configuration settings
|
219
|
+
#
|
220
|
+
def method_missing(name, *args, &block)
|
221
|
+
return super unless config.key?(name)
|
222
|
+
|
223
|
+
self.class.send(:define_method, name) { config[name] }
|
224
|
+
config[name]
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Return configuration
|
229
|
+
#
|
230
|
+
def config
|
231
|
+
return @conf if @conf
|
232
|
+
@conf = @config.to_hash
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Expose DSL
|
238
|
+
include Jabbot::Macros
|
239
|
+
|
240
|
+
# Run bot if macros has been used
|
241
|
+
at_exit do
|
242
|
+
raise $! if $!
|
243
|
+
@@bot.run! if run?
|
244
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Jabbot
|
4
|
+
#
|
5
|
+
# Jabbot configuration. Use either Jabbot::CliConfig.new or
|
6
|
+
# JabbotFileConfig.new setup a new bot from either command line or file
|
7
|
+
# (respectively). Configurations can be chained so they override each other:
|
8
|
+
#
|
9
|
+
# config = Jabbot::FileConfig.new
|
10
|
+
# config << Jabbot::CliConfig.new
|
11
|
+
# config.to_hash
|
12
|
+
#
|
13
|
+
# The preceding example will create a configuration which is based on a
|
14
|
+
# configuration file but have certain values overridden from the command line.
|
15
|
+
# This can be used for instance to store everything but the Twitter account
|
16
|
+
# password in your configuration file. Then you can just provide the password
|
17
|
+
# when running the bot.
|
18
|
+
#
|
19
|
+
class Config
|
20
|
+
attr_reader :settings
|
21
|
+
|
22
|
+
DEFAULT = {
|
23
|
+
:log_level => "info",
|
24
|
+
:log_file => nil,
|
25
|
+
:login => nil,
|
26
|
+
:password => nil,
|
27
|
+
:daemonize => false # not yet used!
|
28
|
+
}
|
29
|
+
|
30
|
+
def initialize(settings = {})
|
31
|
+
@configs = []
|
32
|
+
@settings = settings
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Add a configuration object to override given settings
|
37
|
+
#
|
38
|
+
def add(config)
|
39
|
+
@configs << config
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :<<, :add
|
44
|
+
|
45
|
+
#
|
46
|
+
# Makes it possible to access configuration settings as attributes
|
47
|
+
#
|
48
|
+
def method_missing(name, *args, &block)
|
49
|
+
regex = /=$/
|
50
|
+
attr_name = name.to_s.sub(regex, '').to_sym
|
51
|
+
return super if name == attr_name && !@settings.key?(attr_name)
|
52
|
+
|
53
|
+
if name != attr_name
|
54
|
+
@settings[attr_name] = args.first
|
55
|
+
end
|
56
|
+
|
57
|
+
@settings[attr_name]
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Merges configurations and returns a hash with all options
|
62
|
+
#
|
63
|
+
def to_hash
|
64
|
+
hash = {}.merge(@settings)
|
65
|
+
@configs.each { |conf| hash.merge!(conf.to_hash) }
|
66
|
+
hash
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.default
|
70
|
+
Config.new({}.merge(DEFAULT))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Configuration from files
|
76
|
+
#
|
77
|
+
class FileConfig < Config
|
78
|
+
|
79
|
+
#
|
80
|
+
# Accepts a stream or a file to read configuration from
|
81
|
+
# Default is to read configuration from ./config/bot.yml
|
82
|
+
#
|
83
|
+
# If a stream is passed it is not closed from within the method
|
84
|
+
#
|
85
|
+
def initialize(fos = File.expand_path("config/bot.yml"))
|
86
|
+
stream = fos.is_a?(String) ? File.open(fos, "r") : fos
|
87
|
+
|
88
|
+
begin
|
89
|
+
config = YAML.load(stream.read)
|
90
|
+
config.symbolize_keys! if config
|
91
|
+
rescue Exception => err
|
92
|
+
puts err.message
|
93
|
+
puts "Unable to load configuration, aborting"
|
94
|
+
exit
|
95
|
+
ensure
|
96
|
+
stream.close if fos.is_a?(String)
|
97
|
+
end
|
98
|
+
|
99
|
+
super config.is_a?(Hash) ? config : {}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Jabbot
|
2
|
+
module Handlers
|
3
|
+
#
|
4
|
+
# Add a handler for this bot
|
5
|
+
#
|
6
|
+
def add_handler(type, handler)
|
7
|
+
handlers[type] << handler
|
8
|
+
handler
|
9
|
+
end
|
10
|
+
|
11
|
+
def dispatch(type, message)
|
12
|
+
handlers[type].each { |handler| handler.dispatch(message) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def handlers
|
16
|
+
@handlers ||= {
|
17
|
+
:message => [],
|
18
|
+
:private => [],
|
19
|
+
:join => [],
|
20
|
+
:subject => [],
|
21
|
+
:leave => []
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def handlers=(hash)
|
26
|
+
@handlers = hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# A Handler object is an object which can handle a direct message, tweet or
|
32
|
+
# at reply.
|
33
|
+
#
|
34
|
+
class Handler
|
35
|
+
def initialize(pattern = nil, options = {}, &blk)
|
36
|
+
if pattern.is_a?(Hash)
|
37
|
+
options = pattern
|
38
|
+
pattern = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
@options = options
|
42
|
+
@options[:from].collect! { |s| s.to_s } if @options[:from] && @options[:from].is_a?(Array)
|
43
|
+
@options[:from] = [@options[:from].to_s] if @options[:from] && [String, Symbol].include?(@options[:from].class)
|
44
|
+
@handler = nil
|
45
|
+
@handler = block_given? ? blk : nil
|
46
|
+
self.pattern = pattern
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Parse pattern string and set options
|
51
|
+
#
|
52
|
+
def pattern=(pattern)
|
53
|
+
return if pattern.nil? || pattern == ""
|
54
|
+
|
55
|
+
if pattern == :all
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
if pattern.is_a?(Regexp)
|
60
|
+
@options[:pattern] = pattern
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
words = pattern.split.collect { |s| s.strip } # Get all words in pattern
|
65
|
+
@options[:tokens] = words.inject([]) do |sum, token| # Find all tokens, ie :symbol :like :names
|
66
|
+
next sum unless token =~ /^:.*/ # Don't process regular words
|
67
|
+
sym = token.sub(":", "").to_sym # Turn token string into symbol, ie ":token" => :token
|
68
|
+
regex = @options[sym] || '[^\s]+' # Fetch regex if configured, else use any character but space matching
|
69
|
+
pattern.sub!(/(^|\s)#{token}(\s|$)/, '\1(' + regex.to_s + ')\2') # Make sure regex captures named switch
|
70
|
+
sum << sym
|
71
|
+
end
|
72
|
+
|
73
|
+
@options[:pattern] = /#{pattern}(\s.+)?/
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Determines if this handler is suited to handle an incoming message
|
78
|
+
#
|
79
|
+
def recognize?(message)
|
80
|
+
return false if @options[:pattern] && message.text !~ @options[:pattern] # Pattern check
|
81
|
+
|
82
|
+
users = @options[:from] ? @options[:from] : nil
|
83
|
+
return false if users && !users.include?(message.user) # Check allowed senders
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Process message to build params hash and pass message along with params of
|
89
|
+
# to +handle+
|
90
|
+
#
|
91
|
+
def dispatch(message)
|
92
|
+
return unless recognize?(message)
|
93
|
+
@params = {}
|
94
|
+
|
95
|
+
if @options[:pattern] && @options[:tokens]
|
96
|
+
matches = message.text.match(@options[:pattern])
|
97
|
+
@options[:tokens].each_with_index { |token, i| @params[token] = matches[i+1] }
|
98
|
+
@params[:text] = (matches[@options[:tokens].length+1] || "").strip
|
99
|
+
elsif @options[:pattern] && !@options[:tokens]
|
100
|
+
@params = message.text.match(@options[:pattern]).to_a[1..-1] || []
|
101
|
+
else
|
102
|
+
@params[:text] = message.text
|
103
|
+
end
|
104
|
+
|
105
|
+
return handle(message, @params)
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Handle a message. Calls the internal Proc with the message and the params
|
110
|
+
# hash as parameters.
|
111
|
+
#
|
112
|
+
def handle(message, params)
|
113
|
+
@handler.call(message, params) if @handler
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Jabbot
|
2
|
+
@@prompt = false
|
3
|
+
|
4
|
+
def self.prompt=(p)
|
5
|
+
@@prompt = f
|
6
|
+
end
|
7
|
+
|
8
|
+
module Macros
|
9
|
+
def self.included(mod)
|
10
|
+
@@bot = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure(&blk)
|
14
|
+
bot.configure(&blk)
|
15
|
+
end
|
16
|
+
|
17
|
+
def message(pattern = nil, options = {}, &blk)
|
18
|
+
add_handler(:message, pattern, options, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def query(pattern = nil, options = {}, &blk)
|
22
|
+
add_handler(:private, pattern, options, &blk)
|
23
|
+
end
|
24
|
+
alias_method :private_message, :query
|
25
|
+
|
26
|
+
def join(options = {}, &blk)
|
27
|
+
add_handler(:join, /\Ajoin\Z/, options, &blk)
|
28
|
+
end
|
29
|
+
|
30
|
+
def leave(options = {}, &blk)
|
31
|
+
add_handler(:leave, /\Aleave\Z/, options, &blk)
|
32
|
+
end
|
33
|
+
|
34
|
+
def subject(pattern = nil, options = {}, &blk)
|
35
|
+
add_handler(:subject, pattern, options, &blk)
|
36
|
+
end
|
37
|
+
alias_method :topic, :subject
|
38
|
+
|
39
|
+
def client
|
40
|
+
bot.client
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
bot.close
|
45
|
+
end
|
46
|
+
alias_method :quit, :close
|
47
|
+
|
48
|
+
def user
|
49
|
+
bot.user
|
50
|
+
end
|
51
|
+
|
52
|
+
def post(msg, to=nil)
|
53
|
+
if msg.is_a?(Hash) && msg.keys.size == 1
|
54
|
+
to = msg.values.first
|
55
|
+
msg = msg.keys.first
|
56
|
+
end
|
57
|
+
bot.send_message(msg, to)
|
58
|
+
end
|
59
|
+
|
60
|
+
def run?
|
61
|
+
!@@bot.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def add_handler(type, pattern, options, &blk)
|
66
|
+
bot.add_handler(type, Jabbot::Handler.new(pattern, options, &blk))
|
67
|
+
end
|
68
|
+
|
69
|
+
def bot
|
70
|
+
return @@bot unless @@bot.nil?
|
71
|
+
|
72
|
+
begin
|
73
|
+
@@bot = Jabbot::Bot.new nil
|
74
|
+
rescue Exception
|
75
|
+
@@bot = Jabbot::Bot.new(Jabbot::Config.default)
|
76
|
+
end
|
77
|
+
|
78
|
+
@@bot
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.bot=(bot)
|
82
|
+
@@bot = bot
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/test/test_bot.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class TestBot < Test::Unit::TestCase
|
5
|
+
should "not raise errors when initialized" do
|
6
|
+
assert_nothing_raised do
|
7
|
+
Jabbot::Bot.new Jabbot::Config.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
should "raise errors when initialized without config file" do
|
12
|
+
assert_raise SystemExit do
|
13
|
+
Jabbot::Bot.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
should "not raise error on initialize when config file exists" do
|
18
|
+
if File.exists?("config")
|
19
|
+
FileUtils.rm("config/bot.yml")
|
20
|
+
else
|
21
|
+
FileUtils.mkdir("config")
|
22
|
+
end
|
23
|
+
|
24
|
+
File.open("config/bot.yml", "w") { |f| f.puts "" }
|
25
|
+
|
26
|
+
assert_nothing_raised do
|
27
|
+
Jabbot::Bot.new
|
28
|
+
end
|
29
|
+
|
30
|
+
FileUtils.rm_rf("config")
|
31
|
+
end
|
32
|
+
|
33
|
+
should "provide configuration settings as methods" do
|
34
|
+
bot = Jabbot::Bot.new Jabbot::Config.new(:login => "jabbot")
|
35
|
+
assert_equal "jabbot", bot.login
|
36
|
+
end
|
37
|
+
|
38
|
+
should "return logger instance" do
|
39
|
+
bot = Jabbot::Bot.new(Jabbot::Config.default << Jabbot::Config.new)
|
40
|
+
assert bot.log.is_a?(Logger)
|
41
|
+
end
|
42
|
+
|
43
|
+
should "respect configured log level" do
|
44
|
+
bot = Jabbot::Bot.new(Jabbot::Config.new(:log_level => "info"))
|
45
|
+
assert_equal Logger::INFO, bot.log.level
|
46
|
+
|
47
|
+
bot = Jabbot::Bot.new(Jabbot::Config.new(:log_level => "warn"))
|
48
|
+
assert_equal Logger::WARN, bot.log.level
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class TestBotMacros < Test::Unit::TestCase
|
53
|
+
should "provide configure macro" do
|
54
|
+
assert respond_to?(:configure)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "yield configuration" do
|
58
|
+
Jabbot::Macros.bot = Jabbot::Bot.new Jabbot::Config.default
|
59
|
+
|
60
|
+
conf = nil
|
61
|
+
assert_nothing_raised { configure { |c| conf = c } }
|
62
|
+
assert conf.is_a?(Jabbot::Config)
|
63
|
+
end
|
64
|
+
|
65
|
+
should "add handler" do
|
66
|
+
Jabbot::Macros.bot = Jabbot::Bot.new Jabbot::Config.default
|
67
|
+
|
68
|
+
handler = add_handler(:message, ":command", :from => :cjno)
|
69
|
+
assert handler.is_a?(Jabbot::Handler), handler.class
|
70
|
+
end
|
71
|
+
|
72
|
+
should "provide client macro" do
|
73
|
+
assert respond_to?(:client)
|
74
|
+
end
|
75
|
+
|
76
|
+
should "provide user macro" do
|
77
|
+
assert respond_to?(:user)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class TestBotHandlers < Test::Unit::TestCase
|
82
|
+
|
83
|
+
should "include handlers" do
|
84
|
+
bot = Jabbot::Bot.new(Jabbot::Config.new)
|
85
|
+
|
86
|
+
assert_not_nil bot.handlers
|
87
|
+
assert_not_nil bot.handlers[:message]
|
88
|
+
assert_not_nil bot.handlers[:private]
|
89
|
+
assert_not_nil bot.handlers[:join]
|
90
|
+
assert_not_nil bot.handlers[:leave]
|
91
|
+
assert_not_nil bot.handlers[:subject]
|
92
|
+
end
|
93
|
+
|
94
|
+
should "add handler" do
|
95
|
+
bot = Jabbot::Bot.new(Jabbot::Config.new)
|
96
|
+
bot.add_handler :message, Jabbot::Handler.new
|
97
|
+
assert_equal 1, bot.handlers[:message].length
|
98
|
+
|
99
|
+
bot.add_handler :message, Jabbot::Handler.new
|
100
|
+
assert_equal 2, bot.handlers[:message].length
|
101
|
+
|
102
|
+
bot.add_handler :private, Jabbot::Handler.new
|
103
|
+
assert_equal 1, bot.handlers[:private].length
|
104
|
+
|
105
|
+
bot.add_handler :private, Jabbot::Handler.new
|
106
|
+
assert_equal 2, bot.handlers[:private].length
|
107
|
+
|
108
|
+
bot.add_handler :join, Jabbot::Handler.new
|
109
|
+
assert_equal 1, bot.handlers[:join].length
|
110
|
+
|
111
|
+
bot.add_handler :join, Jabbot::Handler.new
|
112
|
+
assert_equal 2, bot.handlers[:join].length
|
113
|
+
|
114
|
+
bot.add_handler :leave, Jabbot::Handler.new
|
115
|
+
assert_equal 1, bot.handlers[:leave].length
|
116
|
+
|
117
|
+
bot.add_handler :leave, Jabbot::Handler.new
|
118
|
+
assert_equal 2, bot.handlers[:leave].length
|
119
|
+
|
120
|
+
bot.add_handler :subject, Jabbot::Handler.new
|
121
|
+
assert_equal 1, bot.handlers[:subject].length
|
122
|
+
|
123
|
+
bot.add_handler :subject, Jabbot::Handler.new
|
124
|
+
assert_equal 2, bot.handlers[:subject].length
|
125
|
+
end
|
126
|
+
end
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
class TestConfig < Test::Unit::TestCase
|
5
|
+
should "default configuration be a hash" do
|
6
|
+
assert_not_nil Jabbot::Config::DEFAULT
|
7
|
+
assert Jabbot::Config::DEFAULT.is_a?(Hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "initialize with no options" do
|
11
|
+
assert_hashes_equal({}, Jabbot::Config.new.settings)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return config from add" do
|
15
|
+
config = Jabbot::Config.new
|
16
|
+
assert_equal config, config.add(Jabbot::Config.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
should "alias add to <<" do
|
20
|
+
config = Jabbot::Config.new
|
21
|
+
assert config.respond_to?(:<<)
|
22
|
+
assert config << Jabbot::Config.new
|
23
|
+
end
|
24
|
+
|
25
|
+
should "mirror method_missing as config getters" do
|
26
|
+
config = Jabbot::Config.default << Jabbot::Config.new
|
27
|
+
assert_equal Jabbot::Config::DEFAULT[:password], config.password
|
28
|
+
assert_equal Jabbot::Config::DEFAULT[:login], config.login
|
29
|
+
end
|
30
|
+
|
31
|
+
should "mirror missing methods as config setters" do
|
32
|
+
config = Jabbot::Config.default << Jabbot::Config.new
|
33
|
+
assert_equal Jabbot::Config::DEFAULT[:login], config.login
|
34
|
+
|
35
|
+
val = "jabbot"
|
36
|
+
config.login = val+'!'
|
37
|
+
assert_not_equal Jabbot::Config::DEFAULT[:login], config.login
|
38
|
+
assert_equal val+'!', config.login
|
39
|
+
end
|
40
|
+
|
41
|
+
should "not override default hash" do
|
42
|
+
config = Jabbot::Config.default
|
43
|
+
hash = Jabbot::Config::DEFAULT
|
44
|
+
|
45
|
+
config.login = "jabbot"
|
46
|
+
config.password = "secret"
|
47
|
+
|
48
|
+
assert_hashes_not_equal Jabbot::Config::DEFAULT, config.to_hash
|
49
|
+
assert_hashes_equal hash, Jabbot::Config::DEFAULT
|
50
|
+
end
|
51
|
+
|
52
|
+
should "return merged configuration from to_hash" do
|
53
|
+
config = Jabbot::Config.new
|
54
|
+
config.login = "jabbot"
|
55
|
+
config.password = "secret"
|
56
|
+
|
57
|
+
config2 = Jabbot::Config.new({})
|
58
|
+
config2.login = "not_jabbot2"
|
59
|
+
config << config2
|
60
|
+
options = config.to_hash
|
61
|
+
|
62
|
+
assert_equal "secret", options[:password]
|
63
|
+
assert_equal "not_jabbot2", options[:login]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class TestFileConfig < Test::Unit::TestCase
|
68
|
+
should "subclass config for file config" do
|
69
|
+
assert Jabbot::FileConfig.new(StringIO.new).is_a?(Jabbot::Config)
|
70
|
+
end
|
71
|
+
|
72
|
+
should "read settings from stream" do
|
73
|
+
config = Jabbot::FileConfig.new(StringIO.new <<-YAML)
|
74
|
+
login: jabbot
|
75
|
+
password: secret
|
76
|
+
YAML
|
77
|
+
|
78
|
+
assert_equal "jabbot", config.login
|
79
|
+
assert_equal "secret", config.password
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
|
2
|
+
|
3
|
+
class TestHandler < Test::Unit::TestCase
|
4
|
+
context "pattern writer" do
|
5
|
+
should "abort on empty values" do
|
6
|
+
handler = Jabbot::Handler.new
|
7
|
+
|
8
|
+
handler.pattern = nil
|
9
|
+
assert_nil handler.instance_eval { @options[:pattern] }
|
10
|
+
assert_nil handler.instance_eval { @options[:tokens] }
|
11
|
+
|
12
|
+
handler.pattern = ""
|
13
|
+
assert_nil handler.instance_eval { @options[:pattern] }
|
14
|
+
assert_nil handler.instance_eval { @options[:tokens] }
|
15
|
+
end
|
16
|
+
|
17
|
+
should "turn regular pattern into regex" do
|
18
|
+
handler = Jabbot::Handler.new
|
19
|
+
handler.pattern = "command"
|
20
|
+
|
21
|
+
assert_equal(/command(\s.+)?/, handler.instance_eval { @options[:pattern] })
|
22
|
+
assert_equal 0, handler.instance_eval { @options[:tokens] }.length
|
23
|
+
end
|
24
|
+
|
25
|
+
should "convert single named switch to regex" do
|
26
|
+
handler = Jabbot::Handler.new
|
27
|
+
handler.pattern = ":command"
|
28
|
+
|
29
|
+
assert_equal(/([^\s]+)(\s.+)?/, handler.instance_eval { @options[:pattern] })
|
30
|
+
assert_equal 1, handler.instance_eval { @options[:tokens] }.length
|
31
|
+
assert_equal :command, handler.instance_eval { @options[:tokens].first }
|
32
|
+
end
|
33
|
+
|
34
|
+
should "convert several named switches to regexen" do
|
35
|
+
handler = Jabbot::Handler.new
|
36
|
+
handler.pattern = ":command fixed_word :subcommand"
|
37
|
+
|
38
|
+
assert_equal(/([^\s]+) fixed_word ([^\s]+)(\s.+)?/, handler.instance_eval { @options[:pattern] })
|
39
|
+
assert_equal 2, handler.instance_eval { @options[:tokens] }.length
|
40
|
+
assert_equal :command, handler.instance_eval { @options[:tokens].first }
|
41
|
+
assert_equal :subcommand, handler.instance_eval { @options[:tokens][1] }
|
42
|
+
end
|
43
|
+
|
44
|
+
should "convert several named switches to regexen specified by options" do
|
45
|
+
handler = Jabbot::Handler.new(":time :hour", :hour => /\d\d/)
|
46
|
+
|
47
|
+
assert_equal(/([^\s]+) ((?-mix:\d\d))(\s.+)?/, handler.instance_eval { @options[:pattern] })
|
48
|
+
assert_equal 2, handler.instance_eval { @options[:tokens] }.length
|
49
|
+
assert_equal :time, handler.instance_eval { @options[:tokens].first }
|
50
|
+
assert_equal :hour, handler.instance_eval { @options[:tokens][1] }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
should "recognize empty pattern" do
|
55
|
+
handler = Jabbot::Handler.new
|
56
|
+
message = message "cjno", "A jabber message"
|
57
|
+
|
58
|
+
assert handler.recognize?(message)
|
59
|
+
end
|
60
|
+
|
61
|
+
should "recognize empty pattern and allowed user" do
|
62
|
+
handler = Jabbot::Handler.new "", :from => "cjno"
|
63
|
+
message = message "cjno", "A jabber message"
|
64
|
+
assert handler.recognize?(message)
|
65
|
+
|
66
|
+
handler = Jabbot::Handler.new "", :from => ["cjno", "irbno"]
|
67
|
+
assert handler.recognize?(message)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "not recognize empty pattern and disallowed user" do
|
71
|
+
handler = Jabbot::Handler.new "", :from => "irbno"
|
72
|
+
message = message "cjno", "A jabber message"
|
73
|
+
assert !handler.recognize?(message)
|
74
|
+
|
75
|
+
handler = Jabbot::Handler.new "", :from => ["irbno", "satan"]
|
76
|
+
assert !handler.recognize?(message)
|
77
|
+
end
|
78
|
+
|
79
|
+
should "recognize fixed pattern and no user" do
|
80
|
+
handler = Jabbot::Handler.new "time"
|
81
|
+
message = message "cjno", "time oslo norway"
|
82
|
+
assert handler.recognize?(message)
|
83
|
+
end
|
84
|
+
|
85
|
+
should "recognize dynamic pattern and no user" do
|
86
|
+
handler = Jabbot::Handler.new "time :city :country"
|
87
|
+
message = message "cjno", "time oslo norway"
|
88
|
+
assert handler.recognize?(message)
|
89
|
+
end
|
90
|
+
|
91
|
+
should "not recognize dynamic pattern and no user" do
|
92
|
+
handler = Jabbot::Handler.new "time :city :country"
|
93
|
+
message = message "cjno", "oslo norway what is the time?"
|
94
|
+
assert !handler.recognize?(message)
|
95
|
+
end
|
96
|
+
|
97
|
+
should "recognize fixed pattern and user" do
|
98
|
+
handler = Jabbot::Handler.new "time", :from => ["cjno", "irbno"]
|
99
|
+
message = message "cjno", "time oslo norway"
|
100
|
+
assert handler.recognize?(message)
|
101
|
+
end
|
102
|
+
|
103
|
+
should "recognize dynamic pattern and user" do
|
104
|
+
handler = Jabbot::Handler.new "time :city :country", :from => ["cjno", "irbno"]
|
105
|
+
message = message "cjno", "time oslo norway"
|
106
|
+
assert handler.recognize?(message)
|
107
|
+
end
|
108
|
+
|
109
|
+
should "not recognize dynamic pattern and user" do
|
110
|
+
handler = Jabbot::Handler.new "time :city :country", :from => ["cjno", "irbno"]
|
111
|
+
message = message "dude", "time oslo norway"
|
112
|
+
assert !handler.recognize?(message)
|
113
|
+
end
|
114
|
+
|
115
|
+
should "recognize symbol users" do
|
116
|
+
handler = Jabbot::Handler.new "time :city :country", :from => [:cjno, :irbno]
|
117
|
+
message = message "dude", "time oslo norway"
|
118
|
+
assert !handler.recognize?(message)
|
119
|
+
|
120
|
+
message = message("cjno", "time oslo norway")
|
121
|
+
assert handler.recognize?(message)
|
122
|
+
end
|
123
|
+
|
124
|
+
should "recognize messages from allowed users" do
|
125
|
+
handler = Jabbot::Handler.new :from => [:cjno, :irbno]
|
126
|
+
message = message "cjno", "time oslo norway"
|
127
|
+
assert handler.recognize?(message)
|
128
|
+
end
|
129
|
+
|
130
|
+
should "not recognize messages from unallowed users with capital screen names" do
|
131
|
+
handler = Jabbot::Handler.new :from => [:cjno, :irbno]
|
132
|
+
message = message "Cjno", "time oslo norway"
|
133
|
+
assert !handler.recognize?(message)
|
134
|
+
end
|
135
|
+
|
136
|
+
should "accept options as only argument" do
|
137
|
+
handler = Jabbot::Handler.new :from => :cjno
|
138
|
+
assert_equal(['cjno'], handler.instance_eval { @options[:from] })
|
139
|
+
assert_nil handler.instance_eval { @options[:pattern] }
|
140
|
+
end
|
141
|
+
|
142
|
+
should "provide parameters in params hash" do
|
143
|
+
handler = Jabbot::Handler.new("time :city :country", :from => ["cjno", "irbno"]) do |message, params|
|
144
|
+
assert_equal "oslo", params[:city]
|
145
|
+
assert_equal "norway", params[:country]
|
146
|
+
end
|
147
|
+
|
148
|
+
message = message "cjno", "time oslo norway"
|
149
|
+
assert handler.recognize?(message)
|
150
|
+
handler.dispatch(message)
|
151
|
+
end
|
152
|
+
|
153
|
+
should "call constructor block from handle" do
|
154
|
+
handler = Jabbot::Handler.new("time :city :country", :from => ["cjno", "irbno"]) do |message, params|
|
155
|
+
raise "Boom!"
|
156
|
+
end
|
157
|
+
|
158
|
+
assert_raise(RuntimeError) do
|
159
|
+
handler.handle(nil, nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
should "recognize regular expressions" do
|
164
|
+
handler = Jabbot::Handler.new /(?:what|where) is (.*)/i
|
165
|
+
message = message "dude", "Where is this shit?"
|
166
|
+
assert handler.recognize?(message)
|
167
|
+
|
168
|
+
message = message "dude", "How is this shit?"
|
169
|
+
assert !handler.recognize?(message)
|
170
|
+
end
|
171
|
+
|
172
|
+
should "recognize regular expressions from specific users" do
|
173
|
+
handler = Jabbot::Handler.new /(?:what|where) is (.*)/i, :from => "cjno"
|
174
|
+
message = message "dude", "Where is this shit?"
|
175
|
+
assert !handler.recognize?(message)
|
176
|
+
|
177
|
+
message = message "cjno", "Where is this shit?"
|
178
|
+
assert handler.recognize?(message)
|
179
|
+
end
|
180
|
+
|
181
|
+
should "provide parameters as arrays when matching regular expressions" do
|
182
|
+
handler = Jabbot::Handler.new(/time ([^\s]*) ([^\s]*)/) do |message, params|
|
183
|
+
assert_equal "oslo", params[0]
|
184
|
+
assert_equal "norway", params[1]
|
185
|
+
end
|
186
|
+
|
187
|
+
message = message "cjno", "time oslo norway"
|
188
|
+
assert handler.recognize?(message)
|
189
|
+
handler.dispatch(message)
|
190
|
+
end
|
191
|
+
end
|
data/test/test_hash.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) unless defined?(Jabbot)
|
2
|
+
|
3
|
+
class TestHash < Test::Unit::TestCase
|
4
|
+
should "convert string keys to symbols" do
|
5
|
+
hash = { "one" => 1, "two" => 2 }
|
6
|
+
hash.symbolize_keys!
|
7
|
+
|
8
|
+
assert_equal 1, hash[:one]
|
9
|
+
assert_equal 2, hash[:two]
|
10
|
+
assert_nil hash["one"]
|
11
|
+
assert_nil hash["two"]
|
12
|
+
end
|
13
|
+
|
14
|
+
should "convert string keys and preserve symbol keys" do
|
15
|
+
hash = { "one" => 1, :two => 2 }
|
16
|
+
hash.symbolize_keys!
|
17
|
+
|
18
|
+
assert_equal 1, hash[:one]
|
19
|
+
assert_equal 2, hash[:two]
|
20
|
+
assert_nil hash["one"]
|
21
|
+
assert_nil hash["two"]
|
22
|
+
end
|
23
|
+
|
24
|
+
should "convert hashes recursively" do
|
25
|
+
hash = { "one" => 1, :two => { "three" => 3, "four" => 4 } }
|
26
|
+
hash.symbolize_keys!
|
27
|
+
|
28
|
+
assert_equal 1, hash[:one]
|
29
|
+
assert_equal 3, hash[:two][:three]
|
30
|
+
assert_equal 4, hash[:two][:four]
|
31
|
+
assert_nil hash["one"]
|
32
|
+
assert_nil hash["two"]
|
33
|
+
end
|
34
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
if RUBY_VERSION =~ /1\.9/
|
3
|
+
Gem.all_load_paths
|
4
|
+
gem "thoughtbot-shoulda"
|
5
|
+
gem "xmpp4r"
|
6
|
+
end
|
7
|
+
require 'test/unit'
|
8
|
+
require 'shoulda'
|
9
|
+
require File.join(File.dirname(__FILE__), '../lib/jabbot')
|
10
|
+
|
11
|
+
module Test::Unit::Assertions
|
12
|
+
def assert_hashes_equal(expected, actual, message = nil)
|
13
|
+
full_message = build_message(message, <<EOT, expected.inspect, actual.inspect)
|
14
|
+
<?> expected but was
|
15
|
+
<?>.
|
16
|
+
EOT
|
17
|
+
assert_block(full_message) do
|
18
|
+
break false if expected.keys.length != actual.keys.length
|
19
|
+
expected.keys.all? { |k| expected[k] == actual[k] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def assert_hashes_not_equal(expected, actual, message = nil)
|
24
|
+
full_message = build_message(message, <<EOT, expected.inspect, actual.inspect)
|
25
|
+
<?> expected but was
|
26
|
+
<?>.
|
27
|
+
EOT
|
28
|
+
assert_block(full_message) do
|
29
|
+
break false if expected.keys.length != actual.keys.length
|
30
|
+
expected.keys.any? { |k| expected[k] != actual[k] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Message = Struct.new(:user, :text, :time)
|
36
|
+
def message(user, text)
|
37
|
+
Message.new(user, text, Time.now)
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: badboy-jabbot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- BadBoy_
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-29 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: xmpp4r
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.4"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thoughtbot-shoulda
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.10.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: technicalpickles-jeweler
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.10.2
|
44
|
+
version:
|
45
|
+
description:
|
46
|
+
email: badboy@archlinux.us
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.rdoc
|
53
|
+
files:
|
54
|
+
- README.rdoc
|
55
|
+
- VERSION.yml
|
56
|
+
- lib/jabbot
|
57
|
+
- lib/jabbot/config.rb
|
58
|
+
- lib/jabbot/handlers.rb
|
59
|
+
- lib/jabbot/message.rb
|
60
|
+
- lib/jabbot/macros.rb
|
61
|
+
- lib/jabbot/bot.rb
|
62
|
+
- lib/jabbot.rb
|
63
|
+
- lib/hash.rb
|
64
|
+
- test/test_helper.rb
|
65
|
+
- test/test_bot.rb
|
66
|
+
- test/test_hash.rb
|
67
|
+
- test/test_config.rb
|
68
|
+
- test/test_handler.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://github.com/badboy/jabbot
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options:
|
73
|
+
- --inline-source
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.2.0
|
93
|
+
signing_key:
|
94
|
+
specification_version: 2
|
95
|
+
summary: Simple framework for creating Jabber/MUC bots, inspired by Sinatra and Twibot
|
96
|
+
test_files: []
|
97
|
+
|