rumpy 0.9.8

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.
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
+