jabbot 0.1.2

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.
@@ -0,0 +1,141 @@
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
+ nick: mybot
57
+
58
+ You can also configure with Ruby:
59
+
60
+ configure do |conf|
61
+ conf.login = "my_account"
62
+ conf.nick = "mybot"
63
+ do
64
+
65
+ If you don't specify login and/or password in any of these ways, Jabbot will fail
66
+ Nick is automatically set to "jabbot" unless something different is defined
67
+ If you want you can set the Jabber Resource:
68
+ conf.resource ="mybot_resource"
69
+ Otherwise it is set to "jabbot", too
70
+
71
+ === "Routes"
72
+
73
+ Like Sinatra, and other web app frameworks, Jabbot supports "routes": patterns
74
+ to match incoming tweets and messages:
75
+
76
+ require 'jabbot'
77
+
78
+ message "time :country :city" do |message,params|
79
+ time = MyTimeService.lookup(params[:country], params[:city])
80
+ post "Time is #{time} in #{params[:city]}, #{params[:country]}"
81
+ end
82
+
83
+ You can have several "message" blocks (or "join", "leave", "query" or "subject").
84
+ Every matching block will be called.
85
+
86
+ Jabbot also supports regular expressions as routes:
87
+
88
+ require 'jabbot'
89
+
90
+ message /^time ([^\s]*) ([^\s]*)/ do |message, params|
91
+ # params is an array of matches when using regexp routes
92
+ time = MyTimeService.lookup(params[0], params[1])
93
+ post "Time is #{time} in #{params[:city]}, #{params[:country]}"
94
+ end
95
+
96
+ == Requirements
97
+
98
+ xmpp4r. You'll need atleast 0.4.
99
+ You can get it via rubygems:
100
+ gem install xmpp4r
101
+ or download it from:
102
+ http://home.gna.org/xmpp4r/
103
+
104
+ == Installation
105
+
106
+ gem sources -a http://gems.github.com # you only have to do this once
107
+ gem install badboy-jabbot
108
+
109
+ == Is it Ruby 1.9?
110
+
111
+ All tests passes even on Ruby 1.9.
112
+ Seems like it works :)
113
+
114
+ == Contributors
115
+
116
+ * Christian Johansen (cjohansen) (author of Twibot) - http://www.cjohansen.no
117
+
118
+ == License
119
+
120
+ (The MIT License)
121
+
122
+ Copyright (c) 2009 Jan-Erik Rediger (Badboy_)
123
+
124
+ Permission is hereby granted, free of charge, to any person obtaining
125
+ a copy of this software and associated documentation files (the
126
+ 'Software'), to deal in the Software without restriction, including
127
+ without limitation the rights to use, copy, modify, merge, publish,
128
+ distribute, sublicense, and/or sell copies of the Software, and to
129
+ permit persons to whom the Software is furnished to do so, subject to
130
+ the following conditions:
131
+
132
+ The above copyright notice and this permission notice shall be
133
+ included in all copies or substantial portions of the Software.
134
+
135
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
136
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
137
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
138
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
139
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
140
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
141
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "jabbot"
8
+ gem.summary = %Q{Simple framework for creating Jabber/MUC bots, inspired by Sinatra and Twibot}
9
+ gem.email = "badboy@archlinux.us"
10
+ gem.homepage = "http://github.com/badboy/jabbot"
11
+ gem.authors = ["BadBoy_"]
12
+ gem.add_dependency('xmpp4r', '>=0.4')
13
+ gem.add_development_dependency('thoughtbot-shoulda', '>=2.10.1')
14
+ gem.add_development_dependency('jeweler', '>=0.10.2')
15
+
16
+ gem.rubyforge_project = 'jabbot'
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = false
27
+ end
28
+
29
+ task :default => :test
30
+
31
+ require 'rake/rdoctask'
32
+ Rake::RDocTask.new do |rdoc|
33
+ if File.exist?('VERSION.yml')
34
+ config = YAML.load(File.read('VERSION.yml'))
35
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
36
+ else
37
+ version = ""
38
+ end
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "jabbot #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 2
@@ -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
@@ -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.1.2'
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
@@ -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 = config[:resource] || "jabbot"
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