badboy-jabbot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 0
data/lib/hash.rb ADDED
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ replace(inject({}) do |hash,(key,value)|
4
+ hash[key.to_sym] = value.is_a?(Hash) ? value.symbolize_keys! : value
5
+ hash
6
+ end)
7
+ end
8
+ end
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
@@ -0,0 +1,7 @@
1
+ module Jabbot
2
+ Message = Struct.new(:user, :text, :time) do
3
+ def to_s
4
+ "#{user}: #{text}"
5
+ end
6
+ end
7
+ 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
@@ -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
@@ -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
+