rumpy 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.rdoc +53 -0
  2. data/lib/rumpy.rb +195 -0
  3. metadata +101 -0
data/README.rdoc ADDED
@@ -0,0 +1,53 @@
1
+ == Welcome to Rumpy
2
+
3
+ Rumpy is some kind of framework to make up your own jabber bot quickly.
4
+ It uses {ActiveRecord}[https://github.com/rails/rails/tree/master/activerecord] and {XMPP4R}[http://home.gna.org/xmpp4r/].
5
+
6
+ Our goal is 'DO NOT REINVENT THE WHEEL'.
7
+
8
+ == Features
9
+
10
+ * Forget about xmpp-related things. Just set your login & password.
11
+ * Forget about database-related things. Just set your database preferences.
12
+ * Write logic using ActiveRecord and callback functions.
13
+
14
+ == Getting started
15
+
16
+ * Rumpy uses 3 configs:
17
+ database.yml :: Your bot's database preferences.
18
+ lang.yml :: Your bot's responces. Append to existing keys more answers and use them like @lang['someanswer]. **There must be at least 3 keys : hello /used when somebody add bot/, stranger /when somebody trying talk to bot without authorization/, authorized /when bot get authorized/.
19
+ xmpp.yml :: Your bot's account settings.
20
+ Look at Examples section to see this configs.
21
+ * Implement your ActiveRecord models in one directory. You have to implement at least one model, for users.
22
+ * Prepare your database.
23
+ * Start writing your class:
24
+ * Mix in Rumpy::Bot module:
25
+ include Rumpy::Bot
26
+ * Define 3 instance variables:
27
+ @models_path :: Path to directory, containing all ruby files with models.
28
+ @config_path :: Path to directory, containing all ruby config files
29
+ @main_model :: Symbol, that stands for main model. If your main model is, e.g. User, set @main_model to :user
30
+ @pid_file :: Optional variable, sets location of the file to which pid of detached process will be saved. If not set, it equals NameOfYourBotClass.downcase + '.pid'.
31
+ @errfile :: Optional variable, sets location of the file for errors. If not set, standard error stream will be used.
32
+ * Write 3 methods:
33
+ backend_func() -> [[receiver, message]*] :: This optional method is running all the time in the loop. Returns array of pairs [receiver, message].
34
+ parser_func(msg) -> pars_result :: This method parses any incoming message and returs results.
35
+ do_func(usermodel, pars_results) -> msg :: This method uses results from parser_func, doing some stuff with model of user, from whom message was received. Returns message to be send to this user
36
+ * Run bot:
37
+ You can run your bot without detaching:
38
+ Rumpy.run YourBotClassName
39
+ Or with detaching:
40
+ To start your bot:
41
+ Rumpy.start YourBotClassName
42
+ To stop it:
43
+ Rumpy.stop YourBotClassName
44
+
45
+ == Example
46
+
47
+ Look at {CuteBot}[https://github.com/MPogoda/CuteBot], {yatodo}[https://github.com/MPogoda/yatodo], {Noty}[https://github.com/Ximik/Noty].
48
+
49
+ Feel free to contact us about any questions related to Rumpy.
50
+
51
+ == License
52
+
53
+ Rumpy is released under the MIT license.
data/lib/rumpy.rb ADDED
@@ -0,0 +1,195 @@
1
+ require 'rubygems'
2
+ require 'xmpp4r/client'
3
+ require 'xmpp4r/roster'
4
+ require 'active_record'
5
+ require 'active_record/validations'
6
+
7
+ module Rumpy
8
+
9
+ def self.start(botclass)
10
+ bot = botclass.new
11
+ pf = pid_file bot
12
+ return false if File.exist? pf
13
+ pid = fork do
14
+ bot.start
15
+ end
16
+ Process.detach pid
17
+ File.open(pf, 'w') do |file|
18
+ file.puts pid
19
+ end
20
+ true
21
+ end
22
+
23
+ def self.stop(botclass)
24
+ pf = pid_file botclass.new
25
+ return false unless File.exist? pf
26
+ begin
27
+ File.open(pf) do |file|
28
+ Process.kill :TERM, file.gets.strip.to_i
29
+ end
30
+ ensure
31
+ File.unlink pf
32
+ end
33
+ true
34
+ end
35
+
36
+ def self.run(botclass)
37
+ botclass.new.start
38
+ end
39
+
40
+ def self.pid_file(bot)
41
+ pid_file = bot.pid_file
42
+ pid_file = bot.class.to_s.downcase + '.pid' if pid_file.nil?
43
+ pid_file
44
+ end
45
+
46
+ module Bot
47
+ attr_reader :pid_file
48
+
49
+ def start
50
+ @err_file = if @err_file then
51
+ File.open(@err_file, 'w')
52
+ else
53
+ $stderr
54
+ end
55
+ Signal.trap :TERM do |signo|
56
+ @err_file.close
57
+ exit
58
+ end
59
+
60
+ init
61
+ connect
62
+ clear_users
63
+ start_subscription_callback
64
+ start_message_callback
65
+ @client.send Jabber::Presence.new
66
+ Thread.new do
67
+ begin
68
+ loop do
69
+ backend_func().each do |result|
70
+ send_msg *result
71
+ end
72
+ end
73
+ rescue => e
74
+ $err_file.puts e.inspect
75
+ $err_file.puts e.backtrace
76
+ end
77
+ end if self.respond_to? :backend_func
78
+ Thread.stop
79
+ end
80
+
81
+ private
82
+
83
+ def init
84
+
85
+ xmppconfig = YAML::load_file @config_path + '/xmpp.yml'
86
+ @lang = YAML::load_file @config_path + '/lang.yml'
87
+ @jid = Jabber::JID.new xmppconfig['jid']
88
+ @password = xmppconfig['password']
89
+ @client = Jabber::Client.new @jid
90
+
91
+ if @models_path then
92
+ dbconfig = YAML::load_file @config_path + '/database.yml'
93
+ ActiveRecord::Base.establish_connection dbconfig
94
+ Dir[@models_path].each do |file|
95
+ self.class.require file
96
+ end
97
+ end
98
+
99
+ @main_model = Object.const_get @main_model.to_s.capitalize
100
+ def @main_model.find_by_jid(jid)
101
+ super jid.strip.to_s
102
+ end
103
+
104
+ @mutexes = Hash.new do |h, k|
105
+ h[k] = Mutex.new
106
+ end
107
+ end
108
+
109
+ def connect
110
+ @client.connect
111
+ @client.auth @password
112
+ @roster = Jabber::Roster::Helper.new @client
113
+ @roster.wait_for_roster
114
+ end
115
+
116
+ def clear_users
117
+ @main_model.find_each do |user|
118
+ items = @roster.find user.jid
119
+ user.destroy if items.count != 1
120
+ end
121
+ @roster.items.each do |jid, item|
122
+ user = @main_model.find_by_jid jid
123
+ if user.nil? then
124
+ item.remove
125
+ next
126
+ elsif item.subscription != :both then
127
+ item.remove
128
+ user.destroy
129
+ end
130
+ end
131
+ end
132
+
133
+ def start_subscription_callback
134
+ @roster.add_subscription_request_callback do |item, presence|
135
+ jid = presence.from
136
+ @roster.accept_subscription jid
137
+ @client.send Jabber::Presence.new.set_type(:subscribe).set_to(jid)
138
+ send_msg jid, @lang['hello']
139
+ end
140
+ @roster.add_subscription_callback do |item, presence|
141
+ case presence.type
142
+ when :unsubscribed, :unsubscribe
143
+ item.remove
144
+ remove_jid item.jid
145
+ when :subscribed
146
+ add_jid item.jid
147
+ send_msg item.jid, @lang['authorized']
148
+ end
149
+ end
150
+ end
151
+
152
+ def start_message_callback
153
+ @client.add_message_callback do |msg|
154
+ begin
155
+ if msg.type != :error and msg.body and msg.from then
156
+ if user = @main_model.find_by_jid(msg.from) then
157
+ pars_results = parser_func msg.body
158
+
159
+ message = ""
160
+ @mutexes[user.jid].synchronize do
161
+ message = do_func user, pars_results
162
+ end
163
+ send_msg msg.from, message
164
+ else
165
+ send_msg msg.from, @lang['stranger']
166
+ items = @roster.find msg.from.strip.to_s
167
+ items.first.last.remove unless items.empty?
168
+ end
169
+ end
170
+ rescue => e
171
+ @err_file.puts e.inspect
172
+ @err_file.puts e.backtrace
173
+ end
174
+ end
175
+ end
176
+
177
+ def send_msg(destination, text)
178
+ return if destination.nil? or text.nil?
179
+ msg = Jabber::Message.new destination, text
180
+ msg.type = :chat
181
+ @client.send msg
182
+ end
183
+
184
+ def add_jid(jid)
185
+ user = @main_model.new
186
+ user.jid = jid.strip.to_s
187
+ user.save
188
+ end
189
+
190
+ def remove_jid(jid)
191
+ user = @main_model.find_by_jid jid
192
+ user.destroy unless user.nil?
193
+ end
194
+ end
195
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rumpy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 43
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 8
10
+ version: 0.9.8
11
+ platform: ruby
12
+ authors:
13
+ - Tsokurov A.G.
14
+ - Pogoda M.V.
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-17 00:00:00 +03:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">"
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 3
33
+ - 0
34
+ version: "3.0"
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: xmpp4r
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 1
46
+ segments:
47
+ - 0
48
+ - 5
49
+ version: "0.5"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Rumpy is some kind of framework to make up your own jabber bot quickly.
53
+ email:
54
+ - mpogoda@lavabit.com
55
+ - me@ximik.net
56
+ executables: []
57
+
58
+ extensions: []
59
+
60
+ extra_rdoc_files:
61
+ - README.rdoc
62
+ files:
63
+ - lib/rumpy.rb
64
+ - README.rdoc
65
+ has_rdoc: true
66
+ homepage: https://github.com/Ximik/Rumpy
67
+ licenses:
68
+ - MIT
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --main
72
+ - README.rdoc
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.3.7
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Rumpy == jabber bot framework
100
+ test_files: []
101
+