ln-xmpp4r 0.5

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 (215) hide show
  1. data/CHANGELOG +91 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +59 -0
  4. data/README.rdoc +113 -0
  5. data/README_ruby19.txt +43 -0
  6. data/Rakefile +252 -0
  7. data/data/doc/xmpp4r/examples/advanced/adventure/README +56 -0
  8. data/data/doc/xmpp4r/examples/advanced/adventure/adventure.rb +23 -0
  9. data/data/doc/xmpp4r/examples/advanced/adventure/adventuremuc.rb +136 -0
  10. data/data/doc/xmpp4r/examples/advanced/adventure/cube.xml +15 -0
  11. data/data/doc/xmpp4r/examples/advanced/adventure/tower.xml +69 -0
  12. data/data/doc/xmpp4r/examples/advanced/adventure/world.rb +424 -0
  13. data/data/doc/xmpp4r/examples/advanced/fileserve.conf +11 -0
  14. data/data/doc/xmpp4r/examples/advanced/fileserve.rb +346 -0
  15. data/data/doc/xmpp4r/examples/advanced/getonline.rb +56 -0
  16. data/data/doc/xmpp4r/examples/advanced/gtkmucclient.rb +315 -0
  17. data/data/doc/xmpp4r/examples/advanced/migrate.rb +88 -0
  18. data/data/doc/xmpp4r/examples/advanced/minimuc.rb +266 -0
  19. data/data/doc/xmpp4r/examples/advanced/pep-aggregator/index.xsl +235 -0
  20. data/data/doc/xmpp4r/examples/advanced/pep-aggregator/pep-aggregator.rb +147 -0
  21. data/data/doc/xmpp4r/examples/advanced/recvfile.rb +84 -0
  22. data/data/doc/xmpp4r/examples/advanced/rosterdiscovery.rb +129 -0
  23. data/data/doc/xmpp4r/examples/advanced/sendfile.conf +10 -0
  24. data/data/doc/xmpp4r/examples/advanced/sendfile.rb +72 -0
  25. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr.rb +51 -0
  26. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_jabber.rb +43 -0
  27. data/data/doc/xmpp4r/examples/advanced/shellmgr/shellmgr_test.rb +10 -0
  28. data/data/doc/xmpp4r/examples/advanced/versionpoll.rb +109 -0
  29. data/data/doc/xmpp4r/examples/advanced/xmpping.rb +146 -0
  30. data/data/doc/xmpp4r/examples/advanced/xmppingrc.sample +14 -0
  31. data/data/doc/xmpp4r/examples/basic/change_password.rb +41 -0
  32. data/data/doc/xmpp4r/examples/basic/client.rb +70 -0
  33. data/data/doc/xmpp4r/examples/basic/component.rb +11 -0
  34. data/data/doc/xmpp4r/examples/basic/echo.rb +37 -0
  35. data/data/doc/xmpp4r/examples/basic/jabbersend.rb +41 -0
  36. data/data/doc/xmpp4r/examples/basic/mass_sender.rb +68 -0
  37. data/data/doc/xmpp4r/examples/basic/muc_owner_config.rb +12 -0
  38. data/data/doc/xmpp4r/examples/basic/mucinfo.rb +41 -0
  39. data/data/doc/xmpp4r/examples/basic/mucsimplebot.rb +82 -0
  40. data/data/doc/xmpp4r/examples/basic/register.rb +42 -0
  41. data/data/doc/xmpp4r/examples/basic/remove_registration.rb +18 -0
  42. data/data/doc/xmpp4r/examples/basic/roster.rb +44 -0
  43. data/data/doc/xmpp4r/examples/basic/rosterprint.rb +50 -0
  44. data/data/doc/xmpp4r/examples/basic/rosterrename.rb +34 -0
  45. data/data/doc/xmpp4r/examples/basic/rosterwatch.rb +171 -0
  46. data/data/doc/xmpp4r/examples/basic/send_vcard.rb +67 -0
  47. data/data/doc/xmpp4r/examples/basic/tune_client.rb +56 -0
  48. data/data/doc/xmpp4r/examples/basic/tune_server.rb +58 -0
  49. data/data/doc/xmpp4r/examples/basic/versionbot.rb +75 -0
  50. data/lib/xmpp4r.rb +116 -0
  51. data/lib/xmpp4r/base64.rb +32 -0
  52. data/lib/xmpp4r/bytestreams.rb +15 -0
  53. data/lib/xmpp4r/bytestreams/helper/filetransfer.rb +319 -0
  54. data/lib/xmpp4r/bytestreams/helper/ibb/base.rb +257 -0
  55. data/lib/xmpp4r/bytestreams/helper/ibb/initiator.rb +31 -0
  56. data/lib/xmpp4r/bytestreams/helper/ibb/target.rb +54 -0
  57. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/base.rb +152 -0
  58. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/initiator.rb +86 -0
  59. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/server.rb +198 -0
  60. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/socks5.rb +65 -0
  61. data/lib/xmpp4r/bytestreams/helper/socks5bytestreams/target.rb +79 -0
  62. data/lib/xmpp4r/bytestreams/iq/bytestreams.rb +170 -0
  63. data/lib/xmpp4r/bytestreams/iq/si.rb +206 -0
  64. data/lib/xmpp4r/callbacks.rb +133 -0
  65. data/lib/xmpp4r/caps.rb +1 -0
  66. data/lib/xmpp4r/caps/c.rb +67 -0
  67. data/lib/xmpp4r/caps/helper/generator.rb +160 -0
  68. data/lib/xmpp4r/caps/helper/helper.rb +84 -0
  69. data/lib/xmpp4r/client.rb +344 -0
  70. data/lib/xmpp4r/command/helper/responder.rb +53 -0
  71. data/lib/xmpp4r/command/iq/command.rb +154 -0
  72. data/lib/xmpp4r/component.rb +103 -0
  73. data/lib/xmpp4r/connection.rb +223 -0
  74. data/lib/xmpp4r/dataforms.rb +5 -0
  75. data/lib/xmpp4r/dataforms/x/data.rb +297 -0
  76. data/lib/xmpp4r/debuglog.rb +63 -0
  77. data/lib/xmpp4r/delay.rb +5 -0
  78. data/lib/xmpp4r/delay/x/delay.rb +99 -0
  79. data/lib/xmpp4r/discovery.rb +8 -0
  80. data/lib/xmpp4r/discovery/helper/helper.rb +58 -0
  81. data/lib/xmpp4r/discovery/helper/responder.rb +165 -0
  82. data/lib/xmpp4r/discovery/iq/discoinfo.rb +211 -0
  83. data/lib/xmpp4r/discovery/iq/discoitems.rb +147 -0
  84. data/lib/xmpp4r/errors.rb +284 -0
  85. data/lib/xmpp4r/feature_negotiation.rb +5 -0
  86. data/lib/xmpp4r/feature_negotiation/iq/feature.rb +28 -0
  87. data/lib/xmpp4r/framework/base.rb +55 -0
  88. data/lib/xmpp4r/framework/bot.rb +148 -0
  89. data/lib/xmpp4r/httpbinding.rb +5 -0
  90. data/lib/xmpp4r/httpbinding/client.rb +275 -0
  91. data/lib/xmpp4r/idgenerator.rb +37 -0
  92. data/lib/xmpp4r/iq.rb +221 -0
  93. data/lib/xmpp4r/jid.rb +167 -0
  94. data/lib/xmpp4r/last.rb +2 -0
  95. data/lib/xmpp4r/last/helper/helper.rb +37 -0
  96. data/lib/xmpp4r/last/iq/last.rb +67 -0
  97. data/lib/xmpp4r/location.rb +2 -0
  98. data/lib/xmpp4r/location/helper/helper.rb +56 -0
  99. data/lib/xmpp4r/location/location.rb +179 -0
  100. data/lib/xmpp4r/message.rb +180 -0
  101. data/lib/xmpp4r/muc.rb +14 -0
  102. data/lib/xmpp4r/muc/helper/mucbrowser.rb +92 -0
  103. data/lib/xmpp4r/muc/helper/mucclient.rb +462 -0
  104. data/lib/xmpp4r/muc/helper/simplemucclient.rb +332 -0
  105. data/lib/xmpp4r/muc/iq/mucadmin.rb +23 -0
  106. data/lib/xmpp4r/muc/iq/mucadminitem.rb +20 -0
  107. data/lib/xmpp4r/muc/iq/mucowner.rb +15 -0
  108. data/lib/xmpp4r/muc/item.rb +143 -0
  109. data/lib/xmpp4r/muc/x/muc.rb +70 -0
  110. data/lib/xmpp4r/muc/x/mucuserinvite.rb +60 -0
  111. data/lib/xmpp4r/muc/x/mucuseritem.rb +36 -0
  112. data/lib/xmpp4r/presence.rb +232 -0
  113. data/lib/xmpp4r/pubsub.rb +8 -0
  114. data/lib/xmpp4r/pubsub/children/configuration.rb +86 -0
  115. data/lib/xmpp4r/pubsub/children/event.rb +49 -0
  116. data/lib/xmpp4r/pubsub/children/item.rb +35 -0
  117. data/lib/xmpp4r/pubsub/children/items.rb +53 -0
  118. data/lib/xmpp4r/pubsub/children/node_config.rb +48 -0
  119. data/lib/xmpp4r/pubsub/children/publish.rb +38 -0
  120. data/lib/xmpp4r/pubsub/children/retract.rb +41 -0
  121. data/lib/xmpp4r/pubsub/children/subscription.rb +62 -0
  122. data/lib/xmpp4r/pubsub/children/subscription_config.rb +67 -0
  123. data/lib/xmpp4r/pubsub/children/unsubscribe.rb +48 -0
  124. data/lib/xmpp4r/pubsub/helper/nodebrowser.rb +129 -0
  125. data/lib/xmpp4r/pubsub/helper/nodehelper.rb +156 -0
  126. data/lib/xmpp4r/pubsub/helper/oauth_service_helper.rb +90 -0
  127. data/lib/xmpp4r/pubsub/helper/servicehelper.rb +456 -0
  128. data/lib/xmpp4r/pubsub/iq/pubsub.rb +19 -0
  129. data/lib/xmpp4r/query.rb +15 -0
  130. data/lib/xmpp4r/reliable.rb +168 -0
  131. data/lib/xmpp4r/rexmladdons.rb +157 -0
  132. data/lib/xmpp4r/roster.rb +7 -0
  133. data/lib/xmpp4r/roster/helper/roster.rb +522 -0
  134. data/lib/xmpp4r/roster/iq/roster.rb +215 -0
  135. data/lib/xmpp4r/roster/x/roster.rb +138 -0
  136. data/lib/xmpp4r/rpc.rb +2 -0
  137. data/lib/xmpp4r/rpc/helper/client.rb +123 -0
  138. data/lib/xmpp4r/rpc/helper/server.rb +74 -0
  139. data/lib/xmpp4r/rpc/helper/xmlrpcaddons.rb +67 -0
  140. data/lib/xmpp4r/rpc/iq/rpc.rb +23 -0
  141. data/lib/xmpp4r/sasl.rb +248 -0
  142. data/lib/xmpp4r/semaphore.rb +38 -0
  143. data/lib/xmpp4r/stream.rb +599 -0
  144. data/lib/xmpp4r/streamparser.rb +85 -0
  145. data/lib/xmpp4r/test/listener_mocker.rb +118 -0
  146. data/lib/xmpp4r/tune.rb +2 -0
  147. data/lib/xmpp4r/tune/helper/helper.rb +58 -0
  148. data/lib/xmpp4r/tune/tune.rb +113 -0
  149. data/lib/xmpp4r/vcard.rb +6 -0
  150. data/lib/xmpp4r/vcard/helper/vcard.rb +84 -0
  151. data/lib/xmpp4r/vcard/iq/vcard.rb +109 -0
  152. data/lib/xmpp4r/version.rb +7 -0
  153. data/lib/xmpp4r/version/helper/responder.rb +72 -0
  154. data/lib/xmpp4r/version/helper/simpleresponder.rb +44 -0
  155. data/lib/xmpp4r/version/iq/version.rb +105 -0
  156. data/lib/xmpp4r/x.rb +37 -0
  157. data/lib/xmpp4r/xhtml.rb +1 -0
  158. data/lib/xmpp4r/xhtml/html.rb +115 -0
  159. data/lib/xmpp4r/xmpp4r.rb +20 -0
  160. data/lib/xmpp4r/xmppelement.rb +168 -0
  161. data/lib/xmpp4r/xmppstanza.rb +162 -0
  162. data/setup.rb +1586 -0
  163. data/test/bytestreams/tc_ibb.rb +188 -0
  164. data/test/bytestreams/tc_socks5bytestreams.rb +114 -0
  165. data/test/caps/tc_helper.rb +158 -0
  166. data/test/dataforms/tc_data.rb +81 -0
  167. data/test/delay/tc_xdelay.rb +51 -0
  168. data/test/discovery/tc_responder.rb +91 -0
  169. data/test/last/tc_helper.rb +75 -0
  170. data/test/lib/assert_equal_xml.rb +14 -0
  171. data/test/lib/clienttester.rb +149 -0
  172. data/test/muc/tc_muc_mucclient.rb +834 -0
  173. data/test/muc/tc_muc_simplemucclient.rb +114 -0
  174. data/test/muc/tc_mucowner.rb +50 -0
  175. data/test/pubsub/tc_helper.rb +785 -0
  176. data/test/pubsub/tc_nodeconfig.rb +61 -0
  177. data/test/pubsub/tc_subscriptionconfig.rb +41 -0
  178. data/test/reliable/tc_disconnect_cleanup.rb +334 -0
  179. data/test/reliable/tc_disconnect_exception.rb +37 -0
  180. data/test/reliable/tc_listener_mocked_test.rb +68 -0
  181. data/test/reliable/tc_reliable_connection.rb +31 -0
  182. data/test/roster/tc_helper.rb +524 -0
  183. data/test/roster/tc_iqqueryroster.rb +173 -0
  184. data/test/roster/tc_xroster.rb +73 -0
  185. data/test/rpc/tc_helper.rb +96 -0
  186. data/test/tc_callbacks.rb +129 -0
  187. data/test/tc_class_names.rb +146 -0
  188. data/test/tc_client.rb +30 -0
  189. data/test/tc_errors.rb +146 -0
  190. data/test/tc_idgenerator.rb +30 -0
  191. data/test/tc_iq.rb +113 -0
  192. data/test/tc_iqquery.rb +31 -0
  193. data/test/tc_jid.rb +204 -0
  194. data/test/tc_message.rb +131 -0
  195. data/test/tc_presence.rb +150 -0
  196. data/test/tc_rexml.rb +139 -0
  197. data/test/tc_stream.rb +167 -0
  198. data/test/tc_streamComponent.rb +95 -0
  199. data/test/tc_streamError.rb +129 -0
  200. data/test/tc_streamSend.rb +59 -0
  201. data/test/tc_streamparser.rb +125 -0
  202. data/test/tc_xmppstanza.rb +135 -0
  203. data/test/ts_xmpp4r.rb +44 -0
  204. data/test/tune/tc_helper_recv.rb +82 -0
  205. data/test/tune/tc_helper_send.rb +74 -0
  206. data/test/tune/tc_tune.rb +79 -0
  207. data/test/vcard/tc_helper.rb +49 -0
  208. data/test/vcard/tc_iqvcard.rb +62 -0
  209. data/test/version/tc_helper.rb +60 -0
  210. data/test/version/tc_iqqueryversion.rb +97 -0
  211. data/test/xhtml/tc_html.rb +41 -0
  212. data/tools/gen_requires.bash +31 -0
  213. data/tools/xmpp4r-gemspec-test.rb +11 -0
  214. data/xmpp4r.gemspec +238 -0
  215. metadata +280 -0
@@ -0,0 +1,180 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/xmppstanza'
6
+ require 'xmpp4r/x'
7
+
8
+ module Jabber
9
+ ##
10
+ # The Message class manages the <message/> stanzas,
11
+ # which is used for all messaging communication.
12
+ class Message < XMPPStanza
13
+
14
+ CHAT_STATES = %w(active composing gone inactive paused).freeze
15
+
16
+ name_xmlns 'message', 'jabber:client'
17
+ force_xmlns true
18
+
19
+ include XParent
20
+
21
+ ##
22
+ # Create a new message
23
+ # >to:: a JID or a String object to send the message to.
24
+ # >body:: the message's body
25
+ def initialize(to = nil, body = nil)
26
+ super()
27
+ if not to.nil?
28
+ set_to(to)
29
+ end
30
+ if !body.nil?
31
+ add_element(REXML::Element.new("body").add_text(body))
32
+ end
33
+ end
34
+
35
+ ##
36
+ # Get the type of the Message stanza
37
+ #
38
+ # The following Symbols are allowed:
39
+ # * :chat
40
+ # * :error
41
+ # * :groupchat
42
+ # * :headline
43
+ # * :normal
44
+ # result:: [Symbol] or nil
45
+ def type
46
+ case super
47
+ when 'chat' then :chat
48
+ when 'error' then :error
49
+ when 'groupchat' then :groupchat
50
+ when 'headline' then :headline
51
+ when 'normal' then :normal
52
+ else nil
53
+ end
54
+ end
55
+
56
+ ##
57
+ # Set the type of the Message stanza (see Message#type for details)
58
+ # v:: [Symbol] or nil
59
+ def type=(v)
60
+ case v
61
+ when :chat then super('chat')
62
+ when :error then super('error')
63
+ when :groupchat then super('groupchat')
64
+ when :headline then super('headline')
65
+ when :normal then super('normal')
66
+ else super(nil)
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Set the type of the Message stanza (chaining-friendly)
72
+ # v:: [Symbol] or nil
73
+ def set_type(v)
74
+ self.type = v
75
+ self
76
+ end
77
+
78
+ ##
79
+ # Returns the message's body, or nil.
80
+ # This is the message's plain-text content.
81
+ def body
82
+ first_element_text('body')
83
+ end
84
+
85
+ ##
86
+ # Sets the message's body
87
+ #
88
+ # b:: [String] body to set
89
+ def body=(b)
90
+ replace_element_text('body', b)
91
+ end
92
+
93
+ ##
94
+ # Sets the message's body
95
+ #
96
+ # b:: [String] body to set
97
+ # return:: [REXML::Element] self for chaining
98
+ def set_body(b)
99
+ self.body = b
100
+ self
101
+ end
102
+
103
+ ##
104
+ # sets the message's subject
105
+ #
106
+ # s:: [String] subject to set
107
+ def subject=(s)
108
+ replace_element_text('subject', s)
109
+ end
110
+
111
+ ##
112
+ # sets the message's subject
113
+ #
114
+ # s:: [String] subject to set
115
+ # return:: [REXML::Element] self for chaining
116
+ def set_subject(s)
117
+ self.subject = s
118
+ self
119
+ end
120
+
121
+ ##
122
+ # Returns the message's subject, or nil
123
+ def subject
124
+ first_element_text('subject')
125
+ end
126
+
127
+ ##
128
+ # sets the message's thread
129
+ # s:: [String] thread to set
130
+ def thread=(s)
131
+ delete_elements('thread')
132
+ replace_element_text('thread', s) unless s.nil?
133
+ end
134
+
135
+ ##
136
+ # gets the message's thread (chaining-friendly)
137
+ # Please note that this are not [Thread] but a [String]-Identifier to track conversations
138
+ # s:: [String] thread to set
139
+ def set_thread(s)
140
+ self.thread = s
141
+ self
142
+ end
143
+
144
+ ##
145
+ # Returns the message's thread, or nil
146
+ def thread
147
+ first_element_text('thread')
148
+ end
149
+
150
+ ##
151
+ # Returns the current chat state, or nil if no chat state is set
152
+ def chat_state
153
+ each_elements(*CHAT_STATES) { |el| return el.name.to_sym }
154
+ return nil
155
+ end
156
+
157
+ ##
158
+ # Sets the chat state :active, :composing, :gone, :inactive, :paused
159
+ def chat_state=(s)
160
+ s = s.to_s
161
+ raise InvalidChatState, "Chat state must be one of #{CHAT_STATES.join(', ')}" unless CHAT_STATES.include?(s)
162
+ CHAT_STATES.each { |state| delete_elements(state) }
163
+ add_element(REXML::Element.new(s).add_namespace('http://jabber.org/protocol/chatstates'))
164
+ end
165
+
166
+ ##
167
+ # Sets the message's chat state
168
+ def set_chat_state(s)
169
+ self.state = s
170
+ self
171
+ end
172
+
173
+ CHAT_STATES.each do |state|
174
+ define_method("#{state}?") do
175
+ chat_state == state.to_sym
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,14 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/dataforms'
6
+ require 'xmpp4r/muc/x/muc'
7
+ require 'xmpp4r/muc/x/mucuserinvite'
8
+ require 'xmpp4r/muc/x/mucuseritem'
9
+ require 'xmpp4r/muc/iq/mucowner'
10
+ require 'xmpp4r/muc/iq/mucadmin'
11
+ require 'xmpp4r/muc/iq/mucadminitem'
12
+ require 'xmpp4r/muc/helper/mucbrowser'
13
+ require 'xmpp4r/muc/helper/mucclient'
14
+ require 'xmpp4r/muc/helper/simplemucclient'
@@ -0,0 +1,92 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/discovery'
6
+
7
+ module Jabber
8
+ module MUC
9
+ ##
10
+ # The MUCBrowser helper can be used to discover
11
+ # Multi-User-Chat components via Service Discovery
12
+ #
13
+ # See JEP 0045 sections 6.1. and 6.2.
14
+ #
15
+ # Usage of its functions should be threaded as
16
+ # responses can take a while
17
+ class MUCBrowser
18
+ ##
19
+ # Initialize a new MUCBrowser helper
20
+ def initialize(stream)
21
+ @stream = stream
22
+ end
23
+
24
+ ##
25
+ # Retrieve the name of a MUC component,
26
+ # depending upon whether the target entity supports
27
+ # the MUC protocol.
28
+ #
29
+ # A return-value of nil does *not* mean that the entity
30
+ # does not exist or does not support Service Discovery!
31
+ # nil just means that this is not a MUC-compliant service.
32
+ #
33
+ # Throws an ServerError when receiving
34
+ # <tt><iq type='error'/></tt>
35
+ # jid:: [JID] Target entity (set only domain!)
36
+ # return:: [String] or [nil]
37
+ def muc_name(jid)
38
+ iq = Iq.new(:get, jid)
39
+ iq.from = @stream.jid # Enable components to use this
40
+ iq.add(Discovery::IqQueryDiscoInfo.new)
41
+
42
+ res = nil
43
+
44
+ @stream.send_with_id(iq) do |answer|
45
+ if answer.type == :result
46
+ answer.query.each_element('feature') { |feature|
47
+ # Look if the component has a MUC or Groupchat feature
48
+ if feature.var == 'http://jabber.org/protocol/muc' or feature.var == 'gc-1.0'
49
+ # If so, get the identity
50
+ if answer.query.first_element('identity')
51
+ res = answer.query.first_element('identity').iname
52
+ end
53
+ end
54
+ }
55
+ true
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ res
62
+ end
63
+
64
+ ##
65
+ # Retrieve the existing rooms of a MUC component
66
+ #
67
+ # The resulting Hash contains pairs of room JID and room name
68
+ #
69
+ # Usage:
70
+ # my_mucbrowse_helper.muc_rooms('conference.jabber.org').each { |jid,name| ... }
71
+ #
72
+ # Throws an exception when receiving <tt><iq type='error'/></tt>
73
+ # jid:: [JID] Target entity (set only domain!)
74
+ # return:: [Hash]
75
+ def muc_rooms(jid)
76
+ iq = Iq.new(:get, jid)
77
+ iq.from = @stream.jid # Enable components to use this
78
+ iq.add(Discovery::IqQueryDiscoItems.new)
79
+
80
+ rooms = {}
81
+
82
+ @stream.send_with_id(iq) do |answer|
83
+ answer.query.each_element('item') { |item|
84
+ rooms[item.jid] = item.iname
85
+ }
86
+ end
87
+
88
+ rooms
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,462 @@
1
+ # =XMPP4R - XMPP Library for Ruby
2
+ # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option.
3
+ # Website::http://home.gna.org/xmpp4r/
4
+
5
+ require 'xmpp4r/muc/x/muc'
6
+ require 'xmpp4r/muc/iq/mucowner'
7
+ require 'xmpp4r/muc/iq/mucadmin'
8
+ require 'xmpp4r/dataforms'
9
+
10
+ module Jabber
11
+ module MUC
12
+ ##
13
+ # The MUCClient Helper handles low-level stuff of the
14
+ # Multi-User Chat (JEP 0045).
15
+ #
16
+ # Use one instance per room.
17
+ #
18
+ # Note that one client cannot join a single room multiple
19
+ # times. At least the clients' resources must be different.
20
+ # This is a protocol design issue. But don't consider it as
21
+ # a bug, it is just a clone-preventing feature.
22
+ class MUCClient
23
+ ##
24
+ # Sender JID, set this to use MUCClient from Components
25
+ # my_jid:: [JID] Defaults to nil
26
+ attr_accessor :my_jid
27
+
28
+ ##
29
+ # MUC room roster
30
+ # roster:: [Hash] of [String] Nick => [Presence]
31
+ attr_reader :roster
32
+
33
+ ##
34
+ # MUC JID
35
+ # jid:: [JID] room@component/nick
36
+ attr_reader :jid
37
+
38
+ ##
39
+ # Initialize a MUCClient
40
+ #
41
+ # Call MUCClient#join *after* you have registered your
42
+ # callbacks to avoid reception of stanzas after joining
43
+ # and before registration of callbacks.
44
+ # stream:: [Stream] to operate on
45
+ def initialize(stream)
46
+ # Attributes initialization
47
+ @stream = stream
48
+ @my_jid = nil
49
+ @jid = nil
50
+ @roster = {}
51
+ @roster_lock = Mutex.new
52
+
53
+ @active = false
54
+
55
+ @join_cbs = CallbackList.new
56
+ @leave_cbs = CallbackList.new
57
+ @presence_cbs = CallbackList.new
58
+ @message_cbs = CallbackList.new
59
+ @private_message_cbs = CallbackList.new
60
+ end
61
+
62
+ ##
63
+ # Join a room
64
+ #
65
+ # This registers its own callbacks on the stream
66
+ # provided to initialize and sends initial presence
67
+ # to the room. May throw ServerError if joining
68
+ # fails.
69
+ # jid:: [JID] room@component/nick
70
+ # password:: [String] Optional password
71
+ # return:: [MUCClient] self (chain-able)
72
+ def join(jid, password=nil)
73
+ if active?
74
+ raise "MUCClient already active"
75
+ end
76
+
77
+ @jid = (jid.kind_of?(JID) ? jid : JID.new(jid))
78
+ activate
79
+
80
+ # Joining
81
+ pres = Presence.new
82
+ pres.to = @jid
83
+ pres.from = @my_jid
84
+ xmuc = XMUC.new
85
+ xmuc.password = password
86
+ pres.add(xmuc)
87
+
88
+ # We don't use Stream#send_with_id here as it's unknown
89
+ # if the MUC component *always* uses our stanza id.
90
+ error = nil
91
+ @stream.send(pres) { |r|
92
+ if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
93
+ # Error from room
94
+ error = r.error
95
+ true
96
+ # type='unavailable' may occur when the MUC kills our previous instance,
97
+ # but all join-failures should be type='error'
98
+ elsif r.from == jid and r.kind_of?(Presence) and r.type != :unavailable
99
+ # Our own presence reflected back - success
100
+ if r.x(XMUCUser) and (i = r.x(XMUCUser).items.first)
101
+ @affiliation = i.affiliation # we're interested in if it's :owner
102
+ @role = i.role # :moderator ?
103
+ end
104
+
105
+ handle_presence(r, false)
106
+ true
107
+ else
108
+ # Everything else
109
+ false
110
+ end
111
+ }
112
+
113
+ if error
114
+ deactivate
115
+ raise ServerError.new(error)
116
+ end
117
+
118
+ self
119
+ end
120
+
121
+ ##
122
+ # Exit the room
123
+ #
124
+ # * Sends presence with type='unavailable' with an optional
125
+ # reason in <tt><status/></tt>,
126
+ # * then waits for a reply from the MUC component (will be
127
+ # processed by leave-callbacks),
128
+ # * then deletes callbacks from the stream.
129
+ # reason:: [String] Optional custom exit message
130
+ def exit(reason=nil)
131
+ unless active?
132
+ raise "MUCClient hasn't yet joined"
133
+ end
134
+
135
+ pres = Presence.new
136
+ pres.type = :unavailable
137
+ pres.to = jid
138
+ pres.from = @my_jid
139
+ pres.status = reason if reason
140
+ @stream.send(pres) { |r|
141
+ Jabber::debuglog "exit: #{r.to_s.inspect}"
142
+ if r.kind_of?(Presence) and r.type == :unavailable and r.from == jid
143
+ @leave_cbs.process(r)
144
+ true
145
+ else
146
+ false
147
+ end
148
+ }
149
+
150
+ deactivate
151
+
152
+ self
153
+ end
154
+
155
+ ##
156
+ # Is the MUC client active?
157
+ #
158
+ # This is false after initialization,
159
+ # true after joining and
160
+ # false after exit/kick
161
+ def active?
162
+ @active
163
+ end
164
+
165
+ ##
166
+ # The MUCClient's own nick
167
+ # (= resource)
168
+ # result:: [String] Nickname
169
+ def nick
170
+ @jid ? @jid.resource : nil
171
+ end
172
+
173
+ ##
174
+ # Change nick
175
+ #
176
+ # Threading is, again, suggested. This method waits for two
177
+ # <presence/> stanzas, one indicating unavailabilty of the old
178
+ # transient JID, one indicating availability of the new
179
+ # transient JID.
180
+ #
181
+ # If the service denies nick-change, ServerError will be raised.
182
+ def nick=(new_nick)
183
+ unless active?
184
+ raise "MUCClient not active"
185
+ end
186
+
187
+ new_jid = JID.new(@jid.node, @jid.domain, new_nick)
188
+
189
+ # Joining
190
+ pres = Presence.new
191
+ pres.to = new_jid
192
+ pres.from = @my_jid
193
+
194
+ error = nil
195
+ # Keeping track of the two stanzas enables us to process stanzas
196
+ # which don't arrive in the order specified by JEP-0045
197
+ presence_unavailable = false
198
+ presence_available = false
199
+ # We don't use Stream#send_with_id here as it's unknown
200
+ # if the MUC component *always* uses our stanza id.
201
+ @stream.send(pres) { |r|
202
+ if from_room?(r.from) and r.kind_of?(Presence) and r.type == :error
203
+ # Error from room
204
+ error = r.error
205
+ elsif r.from == @jid and r.kind_of?(Presence) and r.type == :unavailable and
206
+ r.x and r.x.kind_of?(XMUCUser) and r.x.status_code == 303
207
+ # Old JID is offline, but wait for the new JID and let stanza be handled
208
+ # by the standard callback
209
+ presence_unavailable = true
210
+ handle_presence(r)
211
+ elsif r.from == new_jid and r.kind_of?(Presence) and r.type != :unavailable
212
+ # Our own presence reflected back - success
213
+ presence_available = true
214
+ handle_presence(r)
215
+ end
216
+
217
+ if error or (presence_available and presence_unavailable)
218
+ true
219
+ else
220
+ false
221
+ end
222
+ }
223
+
224
+ if error
225
+ raise ServerError.new(error)
226
+ end
227
+
228
+ # Apply new JID
229
+ @jid = new_jid
230
+ end
231
+
232
+ ##
233
+ # The room name
234
+ # (= node)
235
+ # result:: [String] Room name
236
+ def room
237
+ @jid ? @jid.node : nil
238
+ end
239
+
240
+ ##
241
+ # Send a stanza to the room
242
+ #
243
+ # If stanza is a Jabber::Message, <tt>stanza.type</tt> will be
244
+ # automatically set to :groupchat if directed to room or :chat
245
+ # if directed to participant.
246
+ # stanza:: [XMPPStanza] to send
247
+ # to:: [String] Stanza destination recipient, or room if +nil+
248
+ def send(stanza, to=nil)
249
+ if stanza.kind_of? Message
250
+ stanza.type = to ? :chat : :groupchat
251
+ end
252
+ stanza.from = @my_jid
253
+ stanza.to = JID.new(jid.node, jid.domain, to)
254
+ @stream.send(stanza)
255
+ end
256
+
257
+ ##
258
+ # Add a callback for <presence/> stanzas indicating availability
259
+ # of a MUC participant
260
+ #
261
+ # This callback will *not* be called for initial presences when
262
+ # a client joins a room, but only for the presences afterwards.
263
+ #
264
+ # The callback will be called from MUCClient#handle_presence with
265
+ # one argument: the <presence/> stanza.
266
+ # Note that this stanza will have been already inserted into
267
+ # MUCClient#roster.
268
+ def add_join_callback(prio = 0, ref = nil, &block)
269
+ @join_cbs.add(prio, ref, block)
270
+ end
271
+
272
+ ##
273
+ # Add a callback for <presence/> stanzas indicating unavailability
274
+ # of a MUC participant
275
+ #
276
+ # The callback will be called with one argument: the <presence/> stanza.
277
+ #
278
+ # Note that this is called just *before* the stanza is removed from
279
+ # MUCClient#roster, so it is still possible to see the last presence
280
+ # in the given block.
281
+ #
282
+ # If the presence's origin is your MUC JID, the MUCClient will be
283
+ # deactivated *afterwards*.
284
+ def add_leave_callback(prio = 0, ref = nil, &block)
285
+ @leave_cbs.add(prio, ref, block)
286
+ end
287
+
288
+ ##
289
+ # Add a callback for a <presence/> stanza which is neither a join
290
+ # nor a leave. This will be called when a room participant simply
291
+ # changes his status.
292
+ def add_presence_callback(prio = 0, ref = nil, &block)
293
+ @presence_cbs.add(prio, ref, block)
294
+ end
295
+
296
+ ##
297
+ # Add a callback for <message/> stanza directed to the whole room.
298
+ #
299
+ # See MUCClient#add_private_message_callback for private messages
300
+ # between MUC participants.
301
+ def add_message_callback(prio = 0, ref = nil, &block)
302
+ @message_cbs.add(prio, ref, block)
303
+ end
304
+
305
+ ##
306
+ # Add a callback for <message/> stanza with type='chat'.
307
+ #
308
+ # These stanza are normally not broadcasted to all room occupants
309
+ # but are some sort of private messaging.
310
+ def add_private_message_callback(prio = 0, ref = nil, &block)
311
+ @private_message_cbs.add(prio, ref, block)
312
+ end
313
+
314
+ ##
315
+ # Does this JID belong to that room?
316
+ # jid:: [JID]
317
+ # result:: [true] or [false]
318
+ def from_room?(jid)
319
+ @jid.strip == jid.strip
320
+ end
321
+
322
+ private
323
+
324
+ ##
325
+ # call_join_cbs:: [Bool] Do not call them if we receive initial presences from room
326
+ def handle_presence(pres, call_join_cbs=true) # :nodoc:
327
+ if pres.type == :unavailable or pres.type == :error
328
+ @leave_cbs.process(pres)
329
+ @roster_lock.synchronize {
330
+ @roster.delete(pres.from.resource)
331
+ }
332
+
333
+ if pres.from == jid and !(pres.x and pres.x.kind_of?(XMUCUser) and pres.x.status_code == 303)
334
+ deactivate
335
+ end
336
+ else
337
+ is_join = ! @roster.has_key?(pres.from.resource)
338
+ @roster_lock.synchronize {
339
+ @roster[pres.from.resource] = pres
340
+ }
341
+ if is_join
342
+ @join_cbs.process(pres) if call_join_cbs
343
+ else
344
+ @presence_cbs.process(pres)
345
+ end
346
+ end
347
+ end
348
+
349
+ def handle_message(msg) # :nodoc:
350
+ if msg.type == :chat
351
+ @private_message_cbs.process(msg)
352
+ else # type == :groupchat or anything else
353
+ @message_cbs.process(msg)
354
+ end
355
+ end
356
+
357
+ def activate # :nodoc:
358
+ @active = true
359
+
360
+ # Callbacks
361
+ @stream.add_presence_callback(150, self) { |presence|
362
+ if from_room?(presence.from)
363
+ handle_presence(presence)
364
+ true
365
+ else
366
+ false
367
+ end
368
+ }
369
+
370
+ @stream.add_message_callback(150, self) { |message|
371
+ if from_room?(message.from)
372
+ handle_message(message)
373
+ true
374
+ else
375
+ false
376
+ end
377
+ }
378
+ end
379
+
380
+ def deactivate # :nodoc:
381
+ @active = false
382
+ @jid = nil
383
+
384
+ # Callbacks
385
+ @stream.delete_presence_callback(self)
386
+ @stream.delete_message_callback(self)
387
+ end
388
+
389
+ public
390
+ def owner?
391
+ @affiliation == :owner
392
+ end
393
+
394
+ ##
395
+ # Use this method to configure a MUC room of which you are the owner.
396
+ #
397
+ # options:: [Hash] where keys are the features of the room you wish
398
+ # to configure. See http://www.xmpp.org/extensions/xep-0045.html#registrar-formtype-owner
399
+ def configure(options={})
400
+ get_room_configuration
401
+ submit_room_configuration(options)
402
+ end
403
+
404
+ def get_room_configuration
405
+ raise 'You are not the owner' unless owner?
406
+
407
+ iq = Iq.new(:get, jid.strip)
408
+ iq.from = my_jid
409
+ iq.add(IqQueryMUCOwner.new)
410
+
411
+ fields = []
412
+
413
+ @stream.send_with_id(iq) do |answer|
414
+ raise "Configuration not possible for this room" unless answer.query && answer.query.x(Dataforms::XData)
415
+
416
+ answer.query.x(Dataforms::XData).fields.each do |field|
417
+ if (var = field.attributes['var'])
418
+ fields << var
419
+ end
420
+ end
421
+ end
422
+
423
+ fields
424
+ end
425
+
426
+ def submit_room_configuration(options)
427
+ # fill out the reply form
428
+ iq = Iq.new(:set, jid.strip)
429
+ iq.from = my_jid
430
+ query = IqQueryMUCOwner.new
431
+ form = Dataforms::XData.new
432
+ form.type = :submit
433
+ options.each do |var, values|
434
+ field = Dataforms::XDataField.new
435
+ values = [values] unless values.is_a?(Array)
436
+ field.var, field.values = var, values
437
+ form.add(field)
438
+ end
439
+ query.add(form)
440
+ iq.add(query)
441
+
442
+ @stream.send_with_id(iq)
443
+ end
444
+
445
+ ##
446
+ # Push a list of new affiliations to the room
447
+ # items:: [Array] of, or single [IqQueryMUCAdminItem]
448
+ def send_affiliations(items)
449
+ iq = Iq.new(:set, jid.strip)
450
+ iq.from = my_jid
451
+ iq.add(IqQueryMUCAdmin.new)
452
+
453
+ items = [item] unless items.kind_of? Array
454
+ items.each { |item|
455
+ iq.query.add(item)
456
+ }
457
+
458
+ @stream.send_with_id(iq)
459
+ end
460
+ end
461
+ end
462
+ end