jabbot 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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