jfcouture-xmpp4r-simple 0.8.9

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.
data/CHANGELOG ADDED
@@ -0,0 +1,73 @@
1
+ xmpp4r-simple (0.8.8)
2
+
3
+ [ Blaine Cook]
4
+ * Add xmpp4r-simple.gemspec, remove gem-related code from Rakefile
5
+
6
+ -- Blaine Cook <romeda@gmail.com> Wed, 30 Jul 2008 16:03:00 -0800
7
+
8
+ xmpp4r-simple (0.8.7)
9
+
10
+ [ Blaine Cook ]
11
+ * If using Jabber::Simple in a DRb environment, the Jabber::Simple object
12
+ will execute in the DRb server's environment to improve efficiency.
13
+ * Jabber::Simple will now recover gracefully from server-side disconnects.
14
+ * Updated Rakefile to pass tests on firebrigade (http://firebrigade.seattlerb.org)
15
+
16
+ -- Blaine Cook <romeda@gmail.com> Wed, 31 Jan 2007 17:15:56 -0800
17
+
18
+ xmpp4r-simple (0.8.6)
19
+
20
+ [ Blaine Cook ]
21
+ * Catch a rare exception that's thrown when we are disconnecting.
22
+
23
+ -- Blaine Cook <romeda@gmail.com> Fri, 26 Jan 2007 11:45:23 -0800
24
+
25
+
26
+ xmpp4r-simple (0.8.5)
27
+
28
+ [ Blaine Cook ]
29
+ * Accept Jabber::Message messages as arguments to Jabber::Simple#deliver
30
+
31
+ -- Blaine Cook <romeda@gmail.com> Mon, 22 Jan 2007 07:23:09 -0800
32
+
33
+
34
+ xmpp4r-simple (0.8.4)
35
+
36
+ [ Blaine Cook ]
37
+ * Catch broken connections and attempt to reconnect 3 times.
38
+
39
+ -- Blaine Cook <romeda@gmail.com> Fri, 23 Dec 2006 00:12:09 -0800
40
+
41
+
42
+ xmpp4r-simple (0.8.3)
43
+
44
+ [ Blaine Cook ]
45
+ * Update presence_updates to only store one presence_update per user.
46
+ Changes methods, will break code that uses presence_updates if not updated
47
+ correspondingly, check the documenation for the new semantics.
48
+
49
+ -- Blaine Cook <romeda@gmail.com> Thu, 07 Dec 2006 12:45:52 -0800
50
+
51
+
52
+ xmpp4r-simple (0.8.2)
53
+
54
+ [ Blaine Cook ]
55
+ * Add presence_updates?, received_messages?, and new_subscriptions? methods.
56
+
57
+ -- Blaine Cook <romeda@gmail.com> Wed, 06 Dec 2006 09:40:28 -0800
58
+
59
+
60
+ xmpp4r-simple (0.8.1)
61
+
62
+ [ Blaine Cook ]
63
+ * Make the deferred_delivery method public
64
+
65
+ -- Blaine Cook <romeda@gmail.com> Tue, 05 Dec 2006 17:39:14 -0800
66
+
67
+
68
+ xmpp4r-simple (0.8.0)
69
+
70
+ [ Blaine Cook ]
71
+ * initial import
72
+
73
+ -- Blaine Cook <romeda@gmail.com> Wed, 08 Nov 2006 20:42:42 -0800
data/COPYING ADDED
@@ -0,0 +1,281 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 2, June 1991
3
+
4
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6
+ Everyone is permitted to copy and distribute verbatim copies
7
+ of this license document, but changing it is not allowed.
8
+
9
+ Preamble
10
+
11
+ The licenses for most software are designed to take away your
12
+ freedom to share and change it. By contrast, the GNU General Public
13
+ License is intended to guarantee your freedom to share and change free
14
+ software--to make sure the software is free for all its users. This
15
+ General Public License applies to most of the Free Software
16
+ Foundation's software and to any other program whose authors commit to
17
+ using it. (Some other Free Software Foundation software is covered by
18
+ the GNU Lesser General Public License instead.) You can apply it to
19
+ your programs, too.
20
+
21
+ When we speak of free software, we are referring to freedom, not
22
+ price. Our General Public Licenses are designed to make sure that you
23
+ have the freedom to distribute copies of free software (and charge for
24
+ this service if you wish), that you receive source code or can get it
25
+ if you want it, that you can change the software or use pieces of it
26
+ in new free programs; and that you know you can do these things.
27
+
28
+ To protect your rights, we need to make restrictions that forbid
29
+ anyone to deny you these rights or to ask you to surrender the rights.
30
+ These restrictions translate to certain responsibilities for you if you
31
+ distribute copies of the software, or if you modify it.
32
+
33
+ For example, if you distribute copies of such a program, whether
34
+ gratis or for a fee, you must give the recipients all the rights that
35
+ you have. You must make sure that they, too, receive or can get the
36
+ source code. And you must show them these terms so they know their
37
+ rights.
38
+
39
+ We protect your rights with two steps: (1) copyright the software, and
40
+ (2) offer you this license which gives you legal permission to copy,
41
+ distribute and/or modify the software.
42
+
43
+ Also, for each author's protection and ours, we want to make certain
44
+ that everyone understands that there is no warranty for this free
45
+ software. If the software is modified by someone else and passed on, we
46
+ want its recipients to know that what they have is not the original, so
47
+ that any problems introduced by others will not reflect on the original
48
+ authors' reputations.
49
+
50
+ Finally, any free program is threatened constantly by software
51
+ patents. We wish to avoid the danger that redistributors of a free
52
+ program will individually obtain patent licenses, in effect making the
53
+ program proprietary. To prevent this, we have made it clear that any
54
+ patent must be licensed for everyone's free use or not licensed at all.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ GNU GENERAL PUBLIC LICENSE
60
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
+
62
+ 0. This License applies to any program or other work which contains
63
+ a notice placed by the copyright holder saying it may be distributed
64
+ under the terms of this General Public License. The "Program", below,
65
+ refers to any such program or work, and a "work based on the Program"
66
+ means either the Program or any derivative work under copyright law:
67
+ that is to say, a work containing the Program or a portion of it,
68
+ either verbatim or with modifications and/or translated into another
69
+ language. (Hereinafter, translation is included without limitation in
70
+ the term "modification".) Each licensee is addressed as "you".
71
+
72
+ Activities other than copying, distribution and modification are not
73
+ covered by this License; they are outside its scope. The act of
74
+ running the Program is not restricted, and the output from the Program
75
+ is covered only if its contents constitute a work based on the
76
+ Program (independent of having been made by running the Program).
77
+ Whether that is true depends on what the Program does.
78
+
79
+ 1. You may copy and distribute verbatim copies of the Program's
80
+ source code as you receive it, in any medium, provided that you
81
+ conspicuously and appropriately publish on each copy an appropriate
82
+ copyright notice and disclaimer of warranty; keep intact all the
83
+ notices that refer to this License and to the absence of any warranty;
84
+ and give any other recipients of the Program a copy of this License
85
+ along with the Program.
86
+
87
+ You may charge a fee for the physical act of transferring a copy, and
88
+ you may at your option offer warranty protection in exchange for a fee.
89
+
90
+ 2. You may modify your copy or copies of the Program or any portion
91
+ of it, thus forming a work based on the Program, and copy and
92
+ distribute such modifications or work under the terms of Section 1
93
+ above, provided that you also meet all of these conditions:
94
+
95
+ a) You must cause the modified files to carry prominent notices
96
+ stating that you changed the files and the date of any change.
97
+
98
+ b) You must cause any work that you distribute or publish, that in
99
+ whole or in part contains or is derived from the Program or any
100
+ part thereof, to be licensed as a whole at no charge to all third
101
+ parties under the terms of this License.
102
+
103
+ c) If the modified program normally reads commands interactively
104
+ when run, you must cause it, when started running for such
105
+ interactive use in the most ordinary way, to print or display an
106
+ announcement including an appropriate copyright notice and a
107
+ notice that there is no warranty (or else, saying that you provide
108
+ a warranty) and that users may redistribute the program under
109
+ these conditions, and telling the user how to view a copy of this
110
+ License. (Exception: if the Program itself is interactive but
111
+ does not normally print such an announcement, your work based on
112
+ the Program is not required to print an announcement.)
113
+
114
+ These requirements apply to the modified work as a whole. If
115
+ identifiable sections of that work are not derived from the Program,
116
+ and can be reasonably considered independent and separate works in
117
+ themselves, then this License, and its terms, do not apply to those
118
+ sections when you distribute them as separate works. But when you
119
+ distribute the same sections as part of a whole which is a work based
120
+ on the Program, the distribution of the whole must be on the terms of
121
+ this License, whose permissions for other licensees extend to the
122
+ entire whole, and thus to each and every part regardless of who wrote it.
123
+
124
+ Thus, it is not the intent of this section to claim rights or contest
125
+ your rights to work written entirely by you; rather, the intent is to
126
+ exercise the right to control the distribution of derivative or
127
+ collective works based on the Program.
128
+
129
+ In addition, mere aggregation of another work not based on the Program
130
+ with the Program (or with a work based on the Program) on a volume of
131
+ a storage or distribution medium does not bring the other work under
132
+ the scope of this License.
133
+
134
+ 3. You may copy and distribute the Program (or a work based on it,
135
+ under Section 2) in object code or executable form under the terms of
136
+ Sections 1 and 2 above provided that you also do one of the following:
137
+
138
+ a) Accompany it with the complete corresponding machine-readable
139
+ source code, which must be distributed under the terms of Sections
140
+ 1 and 2 above on a medium customarily used for software interchange; or,
141
+
142
+ b) Accompany it with a written offer, valid for at least three
143
+ years, to give any third party, for a charge no more than your
144
+ cost of physically performing source distribution, a complete
145
+ machine-readable copy of the corresponding source code, to be
146
+ distributed under the terms of Sections 1 and 2 above on a medium
147
+ customarily used for software interchange; or,
148
+
149
+ c) Accompany it with the information you received as to the offer
150
+ to distribute corresponding source code. (This alternative is
151
+ allowed only for noncommercial distribution and only if you
152
+ received the program in object code or executable form with such
153
+ an offer, in accord with Subsection b above.)
154
+
155
+ The source code for a work means the preferred form of the work for
156
+ making modifications to it. For an executable work, complete source
157
+ code means all the source code for all modules it contains, plus any
158
+ associated interface definition files, plus the scripts used to
159
+ control compilation and installation of the executable. However, as a
160
+ special exception, the source code distributed need not include
161
+ anything that is normally distributed (in either source or binary
162
+ form) with the major components (compiler, kernel, and so on) of the
163
+ operating system on which the executable runs, unless that component
164
+ itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering
167
+ access to copy from a designated place, then offering equivalent
168
+ access to copy the source code from the same place counts as
169
+ distribution of the source code, even though third parties are not
170
+ compelled to copy the source along with the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program
173
+ except as expressly provided under this License. Any attempt
174
+ otherwise to copy, modify, sublicense or distribute the Program is
175
+ void, and will automatically terminate your rights under this License.
176
+ However, parties who have received copies, or rights, from you under
177
+ this License will not have their licenses terminated so long as such
178
+ parties remain in full compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not
181
+ signed it. However, nothing else grants you permission to modify or
182
+ distribute the Program or its derivative works. These actions are
183
+ prohibited by law if you do not accept this License. Therefore, by
184
+ modifying or distributing the Program (or any work based on the
185
+ Program), you indicate your acceptance of this License to do so, and
186
+ all its terms and conditions for copying, distributing or modifying
187
+ the Program or works based on it.
188
+
189
+ 6. Each time you redistribute the Program (or any work based on the
190
+ Program), the recipient automatically receives a license from the
191
+ original licensor to copy, distribute or modify the Program subject to
192
+ these terms and conditions. You may not impose any further
193
+ restrictions on the recipients' exercise of the rights granted herein.
194
+ You are not responsible for enforcing compliance by third parties to
195
+ this License.
196
+
197
+ 7. If, as a consequence of a court judgment or allegation of patent
198
+ infringement or for any other reason (not limited to patent issues),
199
+ conditions are imposed on you (whether by court order, agreement or
200
+ otherwise) that contradict the conditions of this License, they do not
201
+ excuse you from the conditions of this License. If you cannot
202
+ distribute so as to satisfy simultaneously your obligations under this
203
+ License and any other pertinent obligations, then as a consequence you
204
+ may not distribute the Program at all. For example, if a patent
205
+ license would not permit royalty-free redistribution of the Program by
206
+ all those who receive copies directly or indirectly through you, then
207
+ the only way you could satisfy both it and this License would be to
208
+ refrain entirely from distribution of the Program.
209
+
210
+ If any portion of this section is held invalid or unenforceable under
211
+ any particular circumstance, the balance of the section is intended to
212
+ apply and the section as a whole is intended to apply in other
213
+ circumstances.
214
+
215
+ It is not the purpose of this section to induce you to infringe any
216
+ patents or other property right claims or to contest validity of any
217
+ such claims; this section has the sole purpose of protecting the
218
+ integrity of the free software distribution system, which is
219
+ implemented by public license practices. Many people have made
220
+ generous contributions to the wide range of software distributed
221
+ through that system in reliance on consistent application of that
222
+ system; it is up to the author/donor to decide if he or she is willing
223
+ to distribute software through any other system and a licensee cannot
224
+ impose that choice.
225
+
226
+ This section is intended to make thoroughly clear what is believed to
227
+ be a consequence of the rest of this License.
228
+
229
+ 8. If the distribution and/or use of the Program is restricted in
230
+ certain countries either by patents or by copyrighted interfaces, the
231
+ original copyright holder who places the Program under this License
232
+ may add an explicit geographical distribution limitation excluding
233
+ those countries, so that distribution is permitted only in or among
234
+ countries not thus excluded. In such case, this License incorporates
235
+ the limitation as if written in the body of this License.
236
+
237
+ 9. The Free Software Foundation may publish revised and/or new versions
238
+ of the General Public License from time to time. Such new versions will
239
+ be similar in spirit to the present version, but may differ in detail to
240
+ address new problems or concerns.
241
+
242
+ Each version is given a distinguishing version number. If the Program
243
+ specifies a version number of this License which applies to it and "any
244
+ later version", you have the option of following the terms and conditions
245
+ either of that version or of any later version published by the Free
246
+ Software Foundation. If the Program does not specify a version number of
247
+ this License, you may choose any version ever published by the Free Software
248
+ Foundation.
249
+
250
+ 10. If you wish to incorporate parts of the Program into other free
251
+ programs whose distribution conditions are different, write to the author
252
+ to ask for permission. For software which is copyrighted by the Free
253
+ Software Foundation, write to the Free Software Foundation; we sometimes
254
+ make exceptions for this. Our decision will be guided by the two goals
255
+ of preserving the free status of all derivatives of our free software and
256
+ of promoting the sharing and reuse of software generally.
257
+
258
+ NO WARRANTY
259
+
260
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
+ REPAIR OR CORRECTION.
269
+
270
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
+ POSSIBILITY OF SUCH DAMAGES.
279
+
280
+ END OF TERMS AND CONDITIONS
281
+
data/README ADDED
@@ -0,0 +1,59 @@
1
+ = Name
2
+
3
+ Jabber::Simple - An extremely easy-to-use Jabber client library.
4
+
5
+ = Synopsis
6
+
7
+ # Send a message to a friend, asking for authorization if necessary:
8
+ im = Jabber::Simple.new("user@example.com", "password")
9
+ im.deliver("friend@example.com", "Hey there friend!")
10
+
11
+ # Get received messages and print them out to the console:
12
+ im.received_messages { |msg| puts msg.body if msg.type == :chat }
13
+
14
+ # Send an authorization request to a user:
15
+ im.add("friend@example.com")
16
+
17
+ # Get presence updates from your friends, and print them out to the console:
18
+ # (admittedly, this one needs some work)
19
+ im.presence_updates do |update|
20
+ from = update[0].jid.strip.to_s
21
+ status = update[2].status
22
+ presence = update[2].show
23
+ puts "#{from} went #{presence}: #{status}"
24
+ end
25
+
26
+ # Remove a user from your contact list:
27
+ im.remove("unfriendly@example.com")
28
+
29
+ # See the Jabber::Simple documentation for more information.
30
+
31
+ = Description
32
+
33
+ Jabber::Simple is intended to make Jabber client programming dead simple. XMPP,
34
+ the Jabber protocol, is extremely powerful but also carries a steep learning
35
+ curve. This library exposes only the most common tasks, and does so in a way
36
+ that is familiar to users of traditional instant messenger clients.
37
+
38
+ = Known Issues
39
+
40
+ * None. If you'd like additional functionality, please contact the developer!
41
+
42
+ = Copyright
43
+
44
+ Jabber::Simple - An extremely easy-to-use Jabber client library.
45
+ Copyright 2006-2008 Blaine Cook <romeda@gmail.com>.
46
+
47
+ Jabber::Simple is free software; you can redistribute it and/or modify
48
+ it under the terms of the GNU General Public License as published by
49
+ the Free Software Foundation; either version 2 of the License, or
50
+ (at your option) any later version.
51
+
52
+ Jabber::Simple is distributed in the hope that it will be useful,
53
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
54
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55
+ GNU General Public License for more details.
56
+
57
+ You should have received a copy of the GNU General Public License
58
+ along with Jabber::Simple; if not, write to the Free Software
59
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
@@ -0,0 +1,494 @@
1
+ # Jabber::Simple - An extremely easy-to-use Jabber client library.
2
+ # Copyright 2006 Blaine Cook <blaine@obvious.com>, Obvious Corp.
3
+ #
4
+ # Jabber::Simple is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # Jabber::Simple is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Jabber::Simple; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ require 'rubygems'
19
+ require 'xmpp4r'
20
+ require 'xmpp4r/roster'
21
+ require 'xmpp4r/vcard'
22
+
23
+ module Jabber
24
+
25
+ class ConnectionError < StandardError #:nodoc:
26
+ end
27
+
28
+ class Contact #:nodoc:
29
+
30
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
31
+
32
+ def initialize(client, jid)
33
+ @jid = jid.respond_to?(:resource) ? jid : JID.new(jid)
34
+ @client = client
35
+ end
36
+
37
+ def inspect
38
+ "Jabber::Contact #{jid.to_s}"
39
+ end
40
+
41
+ def subscribed?
42
+ [:to, :both].include?(subscription)
43
+ end
44
+
45
+ def subscription
46
+ roster_item && roster_item.subscription
47
+ end
48
+
49
+ def ask_for_authorization!
50
+ subscription_request = Presence.new.set_type(:subscribe)
51
+ subscription_request.to = jid
52
+ client.send!(subscription_request)
53
+ end
54
+
55
+ def unsubscribe!
56
+ unsubscription_request = Presence.new.set_type(:unsubscribe)
57
+ unsubscription_request.to = jid
58
+ client.send!(unsubscription_request)
59
+ client.send!(unsubscription_request.set_type(:unsubscribed))
60
+ end
61
+
62
+ def jid(bare=true)
63
+ bare ? @jid.strip : @jid
64
+ end
65
+
66
+ private
67
+
68
+ def roster_item
69
+ client.roster.items[jid]
70
+ end
71
+
72
+ def client
73
+ @client
74
+ end
75
+ end
76
+
77
+ class Simple
78
+
79
+ include DRb::DRbUndumped if defined?(DRb::DRbUndumped)
80
+
81
+ # Create a new Jabber::Simple client. You will be automatically connected
82
+ # to the Jabber server and your status message will be set to the string
83
+ # passed in as the status_message argument.
84
+ #
85
+ # jabber = Jabber::Simple.new("me@example.com", "password", "Chat with me - Please!")
86
+ def initialize(jid, password, status = nil, status_message = "Available", host = nil, port=5222)
87
+ @jid = jid
88
+ @password = password
89
+ @host = host
90
+ @port = port
91
+ @disconnected = false
92
+ status(status, status_message)
93
+ start_deferred_delivery_thread
94
+ end
95
+
96
+ def inspect #:nodoc:
97
+ "Jabber::Simple #{@jid}"
98
+ end
99
+
100
+ # Send a message to jabber user jid.
101
+ #
102
+ # Valid message types are:
103
+ #
104
+ # * :normal (default): a normal message.
105
+ # * :chat: a one-to-one chat message.
106
+ # * :groupchat: a group-chat message.
107
+ # * :headline: a "headline" message.
108
+ # * :error: an error message.
109
+ #
110
+ # If the recipient is not in your contacts list, the message will be queued
111
+ # for later delivery, and the Contact will be automatically asked for
112
+ # authorization (see Jabber::Simple#add).
113
+ #
114
+ # message should be a string or a valid Jabber::Message object. In either case,
115
+ # the message recipient will be set to jid.
116
+ def deliver(jid, message, type=:chat)
117
+ contacts(jid) do |friend|
118
+ unless subscribed_to? friend
119
+ add(friend.jid)
120
+ return deliver_deferred(friend.jid, message, type)
121
+ end
122
+ if message.kind_of?(Jabber::Message)
123
+ msg = message
124
+ msg.to = friend.jid
125
+ else
126
+ msg = Message.new(friend.jid)
127
+ msg.type = type
128
+ msg.body = message
129
+ end
130
+ send!(msg)
131
+ end
132
+ end
133
+
134
+ # Set your presence, with a message.
135
+ #
136
+ # Available values for presence are:
137
+ #
138
+ # * nil: online.
139
+ # * :chat: free for chat.
140
+ # * :away: away from the computer.
141
+ # * :dnd: do not disturb.
142
+ # * :xa: extended away.
143
+ #
144
+ # It's not possible to set an offline status - to do that, disconnect! :-)
145
+ def status(presence, message)
146
+ @presence = presence
147
+ @status_message = message
148
+ stat_msg = Presence.new(@presence, @status_message)
149
+ send!(stat_msg)
150
+ end
151
+
152
+ # Ask the users specified by jids for authorization (i.e., ask them to add
153
+ # you to their contact list). If you are already in the user's contact list,
154
+ # add() will not attempt to re-request authorization. In order to force
155
+ # re-authorization, first remove() the user, then re-add them.
156
+ #
157
+ # Example usage:
158
+ #
159
+ # jabber_simple.add("friend@friendosaurus.com")
160
+ #
161
+ # Because the authorization process might take a few seconds, or might
162
+ # never happen depending on when (and if) the user accepts your
163
+ # request, results are placed in the Jabber::Simple#new_subscriptions queue.
164
+ def add(*jids)
165
+ contacts(*jids) do |friend|
166
+ next if subscribed_to? friend
167
+ friend.ask_for_authorization!
168
+ end
169
+ end
170
+
171
+ # Remove the jabber users specified by jids from the contact list.
172
+ def remove(*jids)
173
+ contacts(*jids) do |unfriend|
174
+ unfriend.unsubscribe!
175
+ end
176
+ end
177
+
178
+ # Returns true if this Jabber account is subscribed to status updates for
179
+ # the jabber user jid, false otherwise.
180
+ def subscribed_to?(jid)
181
+ contacts(jid) do |contact|
182
+ return contact.subscribed?
183
+ end
184
+ end
185
+
186
+ # If contacts is a single contact, returns a Jabber::Contact object
187
+ # representing that user; if contacts is an array, returns an array of
188
+ # Jabber::Contact objects.
189
+ #
190
+ # When called with a block, contacts will yield each Jabber::Contact object
191
+ # in turn. This is mainly used internally, but exposed as an utility
192
+ # function.
193
+ def contacts(*contacts, &block)
194
+ @contacts ||= {}
195
+ contakts = []
196
+ contacts.each do |contact|
197
+ jid = contact.to_s
198
+ unless @contacts[jid]
199
+ @contacts[jid] = contact.respond_to?(:ask_for_authorization!) ? contact : Contact.new(self, contact)
200
+ end
201
+ yield @contacts[jid] if block_given?
202
+ contakts << @contacts[jid]
203
+ end
204
+ contakts.size > 1 ? contakts : contakts.first
205
+ end
206
+
207
+ # Returns true if the Jabber client is connected to the Jabber server,
208
+ # false otherwise.
209
+ def connected?
210
+ @client ||= nil
211
+ connected = @client.respond_to?(:is_connected?) && @client.is_connected?
212
+ return connected
213
+ end
214
+
215
+ # Returns an array of messages received since the last time
216
+ # received_messages was called. Passing a block will yield each message in
217
+ # turn, allowing you to break part-way through processing (especially
218
+ # useful when your message handling code is not thread-safe (e.g.,
219
+ # ActiveRecord).
220
+ #
221
+ # e.g.:
222
+ #
223
+ # jabber.received_messages do |message|
224
+ # puts "Received message from #{message.from}: #{message.body}"
225
+ # end
226
+ def received_messages(&block)
227
+ dequeue(:received_messages, &block)
228
+ end
229
+
230
+ # Returns true if there are unprocessed received messages waiting in the
231
+ # queue, false otherwise.
232
+ def received_messages?
233
+ !queue(:received_messages).empty?
234
+ end
235
+
236
+ # Returns an array of presence updates received since the last time
237
+ # presence_updates was called. Passing a block will yield each update in
238
+ # turn, allowing you to break part-way through processing (especially
239
+ # useful when your presence handling code is not thread-safe (e.g.,
240
+ # ActiveRecord).
241
+ #
242
+ # e.g.:
243
+ #
244
+ # jabber.presence_updates do |friend, new_presence|
245
+ # puts "Received presence update from #{friend}: #{new_presence}"
246
+ # end
247
+ def presence_updates(&block)
248
+ updates = []
249
+ @presence_mutex.synchronize do
250
+ dequeue(:presence_updates) do |friend|
251
+ presence = @presence_updates[friend]
252
+ next unless presence
253
+ new_update = [friend, presence[0], presence[1]]
254
+ yield new_update if block_given?
255
+ updates << new_update
256
+ @presence_updates.delete(friend)
257
+ end
258
+ end
259
+ return updates
260
+ end
261
+
262
+ # Returns true if there are unprocessed presence updates waiting in the
263
+ # queue, false otherwise.
264
+ def presence_updates?
265
+ !queue(:presence_updates).empty?
266
+ end
267
+
268
+ # Returns an array of subscription notifications received since the last
269
+ # time new_subscriptions was called. Passing a block will yield each update
270
+ # in turn, allowing you to break part-way through processing (especially
271
+ # useful when your subscription handling code is not thread-safe (e.g.,
272
+ # ActiveRecord).
273
+ #
274
+ # e.g.:
275
+ #
276
+ # jabber.new_subscriptions do |friend, presence|
277
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
278
+ # end
279
+ def new_subscriptions(&block)
280
+ dequeue(:new_subscriptions, &block)
281
+ end
282
+
283
+ # Returns true if there are unprocessed presence updates waiting in the
284
+ # queue, false otherwise.
285
+ def new_subscriptions?
286
+ !queue(:new_subscriptions).empty?
287
+ end
288
+
289
+ # Returns an array of subscription notifications received since the last
290
+ # time subscription_requests was called. Passing a block will yield each update
291
+ # in turn, allowing you to break part-way through processing (especially
292
+ # useful when your subscription handling code is not thread-safe (e.g.,
293
+ # ActiveRecord).
294
+ #
295
+ # e.g.:
296
+ #
297
+ # jabber.subscription_requests do |friend, presence|
298
+ # puts "Received presence update from #{friend.to_s}: #{presence}"
299
+ # end
300
+ def subscription_requests(&block)
301
+ dequeue(:subscription_requests, &block)
302
+ end
303
+
304
+ # Returns true if auto-accept subscriptions (friend requests) is enabled
305
+ # (default), false otherwise.
306
+ def accept_subscriptions?
307
+ @accept_subscriptions = true if @accept_subscriptions.nil?
308
+ @accept_subscriptions
309
+ end
310
+
311
+ # Change whether or not subscriptions (friend requests) are automatically accepted.
312
+ def accept_subscriptions=(accept_status)
313
+ @accept_subscriptions = accept_status
314
+ end
315
+
316
+ # Direct access to the underlying Roster helper.
317
+ def roster
318
+ return @roster if @roster
319
+ self.roster = Roster::Helper.new(client)
320
+ end
321
+
322
+ # Direct access to the underlying Jabber client.
323
+ def client
324
+ connect!() unless connected?
325
+ @client
326
+ end
327
+
328
+ # Send a Jabber stanza over-the-wire.
329
+ def send!(msg)
330
+ attempts = 0
331
+ begin
332
+ attempts += 1
333
+ client.send(msg)
334
+ rescue Errno::EPIPE, IOError => e
335
+ sleep 1
336
+ disconnect
337
+ reconnect
338
+ retry unless attempts > 3
339
+ raise e
340
+ rescue Errno::ECONNRESET => e
341
+ sleep (attempts^2) * 60 + 60
342
+ disconnect
343
+ reconnect
344
+ retry unless attempts > 3
345
+ raise e
346
+ end
347
+ end
348
+
349
+ # Use this to force the client to reconnect after a force_disconnect.
350
+ def reconnect
351
+ @disconnected = false
352
+ connect!
353
+ end
354
+
355
+ # Use this to force the client to disconnect and not automatically
356
+ # reconnect.
357
+ def disconnect
358
+ disconnect!
359
+ end
360
+
361
+ # Queue messages for delivery once a user has accepted our authorization
362
+ # request. Works in conjunction with the deferred delivery thread.
363
+ #
364
+ # You can use this method if you want to manually add friends and still
365
+ # have the message queued for later delivery.
366
+ def deliver_deferred(jid, message, type)
367
+ msg = {:to => jid, :message => message, :type => type}
368
+ queue(:pending_messages) << [msg]
369
+ end
370
+
371
+ private
372
+
373
+ def client=(client)
374
+ self.roster = nil # ensure we clear the roster, since that's now associated with a different client.
375
+ @client = client
376
+ end
377
+
378
+ def roster=(new_roster)
379
+ @roster = new_roster
380
+ end
381
+
382
+ def connect!
383
+ raise ConnectionError, "Connections are disabled - use Jabber::Simple::force_connect() to reconnect." if @disconnected
384
+ # Pre-connect
385
+ @connect_mutex ||= Mutex.new
386
+
387
+ # don't try to connect if another thread is already connecting.
388
+ return if @connect_mutex.locked?
389
+
390
+ @connect_mutex.lock
391
+ disconnect!(false) if connected?
392
+
393
+ # Connect
394
+ jid = JID.new(@jid)
395
+ my_client = Client.new(@jid)
396
+ my_client.connect(@host, @port)
397
+ my_client.auth(@password)
398
+ self.client = my_client
399
+
400
+ # Post-connect
401
+ register_default_callbacks
402
+ status(@presence, @status_message)
403
+ @connect_mutex.unlock
404
+ end
405
+
406
+ def disconnect!(auto_reconnect = true)
407
+ if client.respond_to?(:is_connected?) && client.is_connected?
408
+ begin
409
+ client.close
410
+ rescue Errno::EPIPE, IOError => e
411
+ # probably should log this.
412
+ nil
413
+ end
414
+ end
415
+ client = nil
416
+ @disconnected = auto_reconnect
417
+ end
418
+
419
+ def register_default_callbacks
420
+ client.add_message_callback do |message|
421
+ queue(:received_messages) << message unless message.body.nil?
422
+ end
423
+
424
+ roster.add_subscription_callback do |roster_item, presence|
425
+ if presence.type == :subscribed
426
+ queue(:new_subscriptions) << [roster_item, presence]
427
+ end
428
+ end
429
+
430
+ roster.add_subscription_request_callback do |roster_item, presence|
431
+ if accept_subscriptions?
432
+ roster.accept_subscription(presence.from)
433
+ else
434
+ queue(:subscription_requests) << [roster_item, presence]
435
+ end
436
+ end
437
+
438
+ @presence_updates = {}
439
+ @presence_mutex = Mutex.new
440
+ roster.add_presence_callback do |roster_item, old_presence, new_presence|
441
+ simple_jid = roster_item.jid.strip.to_s
442
+ presence = case new_presence.type
443
+ when nil then new_presence.show || :online
444
+ when :unavailable then :unavailable
445
+ else
446
+ nil
447
+ end
448
+
449
+ if presence && @presence_updates[simple_jid] != presence
450
+ queue(:presence_updates) << simple_jid
451
+ @presence_mutex.synchronize { @presence_updates[simple_jid] = [presence, new_presence.status] }
452
+ end
453
+ end
454
+ end
455
+
456
+ # This thread facilitates the delivery of messages to users who haven't yet
457
+ # accepted an invitation from us. When we attempt to deliver a message, if
458
+ # the user hasn't subscribed, we place the message in a queue for later
459
+ # delivery. Once a user has accepted our authorization request, we deliver
460
+ # any messages that have been queued up in the meantime.
461
+ def start_deferred_delivery_thread #:nodoc:
462
+ Thread.new {
463
+ loop {
464
+ messages = [queue(:pending_messages).pop].flatten
465
+ messages.each do |message|
466
+ if subscribed_to?(message[:to])
467
+ deliver(message[:to], message[:message], message[:type])
468
+ else
469
+ queue(:pending_messages) << message
470
+ end
471
+ end
472
+ }
473
+ }
474
+ end
475
+
476
+ def queue(queue)
477
+ @queues ||= Hash.new { |h,k| h[k] = Queue.new }
478
+ @queues[queue]
479
+ end
480
+
481
+ def dequeue(queue, non_blocking = true, max_items = 100, &block)
482
+ queue_items = []
483
+ max_items.times do
484
+ queue_item = queue(queue).pop(non_blocking) rescue nil
485
+ break if queue_item.nil?
486
+ queue_items << queue_item
487
+ yield queue_item if block_given?
488
+ end
489
+ queue_items
490
+ end
491
+ end
492
+ end
493
+
494
+ true
@@ -0,0 +1,250 @@
1
+ # Jabber::Simple - An extremely easy-to-use Jabber client library.
2
+ # Copyright 2006 Blaine Cook <blaine@obvious.com>, Obvious Corp.
3
+ #
4
+ # Jabber::Simple is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # Jabber::Simple is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with Jabber::Simple; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
+ #
18
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
19
+
20
+ require 'test/unit'
21
+ require 'timeout'
22
+ require 'xmpp4r-simple'
23
+
24
+ class JabberSimpleTest < Test::Unit::TestCase
25
+
26
+ def setup
27
+ @@connections ||= {}
28
+
29
+ if @@connections.include?(:client1)
30
+ @client1 = @@connections[:client1]
31
+ @client2 = @@connections[:client2]
32
+ @client1.accept_subscriptions = true
33
+ @client2.accept_subscriptions = true
34
+ @jid1_raw = @@connections[:jid1_raw]
35
+ @jid2_raw = @@connections[:jid2_raw]
36
+ @jid1 = @jid1_raw.strip.to_s
37
+ @jid2 = @jid2_raw.strip.to_s
38
+ return true
39
+ end
40
+
41
+ logins = []
42
+ begin
43
+ logins = File.readlines(File.expand_path("~/.xmpp4r-simple-test-config")).map! { |login| login.split(" ") }
44
+ raise StandardError unless logins.size == 2
45
+ rescue => e
46
+ puts "\nConfiguration Error!\n\nYou must make available two unique Jabber accounts in order for the tests to pass."
47
+ puts "Place them in ~/.xmpp4r-simple-test-config, one per line like so:\n\n"
48
+ puts "user1@example.com/res password"
49
+ puts "user2@example.com/res password\n\n"
50
+ raise e
51
+ end
52
+
53
+ @@connections[:client1] = Jabber::Simple.new(*logins[0])
54
+ @@connections[:client2] = Jabber::Simple.new(*logins[1])
55
+
56
+ @@connections[:jid1_raw] = Jabber::JID.new(logins[0][0])
57
+ @@connections[:jid2_raw] = Jabber::JID.new(logins[1][0])
58
+
59
+ # Force load the client and roster, just to be safe.
60
+ @@connections[:client1].roster
61
+ @@connections[:client2].roster
62
+
63
+ # Re-run this method to setup the local instance variables the first time.
64
+ setup
65
+ end
66
+
67
+ def test_ensure_the_jabber_clients_are_connected_after_setup
68
+ assert @client1.client.is_connected?
69
+ assert @client2.client.is_connected?
70
+ end
71
+
72
+ def test_inspect_should_be_custom
73
+ assert_equal "Jabber::Simple #{@jid1_raw}", @client1.inspect
74
+ end
75
+
76
+ def test_inspect_contact_should_be_custom
77
+ assert_equal "Jabber::Contact #{@jid2}", @client1.contacts(@jid2).inspect
78
+ end
79
+
80
+ def test_remove_users_from_our_roster_should_succeed
81
+ @client2.remove(@jid1)
82
+ @client1.remove(@jid2)
83
+
84
+ sleep 3
85
+
86
+ assert_equal false, @client1.subscribed_to?(@jid2)
87
+ assert_equal false, @client2.subscribed_to?(@jid1)
88
+ end
89
+
90
+ def test_add_users_to_our_roster_should_succeed_with_automatic_approval
91
+ @client1.remove(@jid2)
92
+ @client2.remove(@jid1)
93
+
94
+ assert_before 60 do
95
+ assert_equal false, @client1.subscribed_to?(@jid2)
96
+ assert_equal false, @client2.subscribed_to?(@jid1)
97
+ end
98
+
99
+ @client1.new_subscriptions
100
+ @client1.add(@jid2)
101
+
102
+ assert_before 60 do
103
+ assert @client1.subscribed_to?(@jid2)
104
+ assert @client2.subscribed_to?(@jid1)
105
+ end
106
+
107
+ new_subscriptions = @client1.new_subscriptions
108
+ assert_equal 1, new_subscriptions.size
109
+ assert_equal @jid2, new_subscriptions[0][0].jid.strip.to_s
110
+ end
111
+
112
+ def test_sent_message_should_be_received
113
+ # First clear the client's message queue, just in case.
114
+ assert_kind_of Array, @client2.received_messages
115
+
116
+ # Next ensure that we're not subscribed, so that we can test the deferred message queue.
117
+ @client1.remove(@jid2)
118
+ @client2.remove(@jid1)
119
+ sleep 2
120
+
121
+ # Deliver the messages; this should be received by the other client.
122
+ @client1.deliver(@jid2, "test message")
123
+
124
+ sleep 2
125
+
126
+ # Fetch the message; allow up to ten seconds for the delivery to occur.
127
+ messages = []
128
+ begin
129
+ Timeout::timeout(20) {
130
+ loop do
131
+ messages = @client2.received_messages
132
+ break unless messages.empty?
133
+ sleep 1
134
+ end
135
+ }
136
+ rescue Timeout::Error
137
+ flunk "Timeout waiting for message"
138
+ end
139
+
140
+ # Ensure that the message was received intact.
141
+ assert_equal @jid1, messages.first.from.strip.to_s
142
+ assert_equal "test message", messages.first.body
143
+ end
144
+
145
+ def test_presence_updates_should_be_received
146
+
147
+ @client2.add(@client1)
148
+ @client1.add(@client2)
149
+
150
+ assert_before(60) { assert @client2.subscribed_to?(@jid1) }
151
+ assert_before(60) { assert_equal 0, @client2.presence_updates.size }
152
+
153
+ @client1.status(:away, "Doing something else.")
154
+
155
+ new_statuses = []
156
+ assert_before(60) do
157
+ new_statuses = @client2.presence_updates
158
+ assert_equal 1, new_statuses.size
159
+ end
160
+
161
+ new_status = new_statuses.first
162
+ assert_equal @jid1, new_status[0]
163
+ assert_equal "Doing something else.", new_status[2]
164
+ assert_equal :away, new_status[1]
165
+ end
166
+
167
+ def test_disable_auto_accept_subscription_requests
168
+ @client1.remove(@jid2)
169
+ @client2.remove(@jid1)
170
+
171
+ assert_before(60) do
172
+ assert_equal false, @client1.subscribed_to?(@jid2)
173
+ assert_equal false, @client2.subscribed_to?(@jid1)
174
+ end
175
+
176
+ @client1.accept_subscriptions = false
177
+ assert_equal false, @client1.accept_subscriptions?
178
+
179
+ assert_before(60) { assert_equal 0, @client1.subscription_requests.size }
180
+
181
+ @client2.add(@jid1)
182
+
183
+ new_subscription_requests = []
184
+ assert_before(60) do
185
+ new_subscription_requests = @client1.subscription_requests
186
+ assert_equal 1, new_subscription_requests.size
187
+ end
188
+
189
+ new_subscription = new_subscription_requests.first
190
+ assert_equal @jid2, new_subscription[0].jid.strip.to_s
191
+ assert_equal :subscribe, new_subscription[1].type
192
+ end
193
+
194
+ def test_automatically_reconnect
195
+ @client1.client.close
196
+
197
+ assert_before 60 do
198
+ assert_equal false, @client1.connected?
199
+ end
200
+
201
+ # empty client 2's received message queue.
202
+ @client2.received_messages
203
+
204
+ @client1.deliver(@jid2, "Testing")
205
+
206
+ assert_before(60) { assert @client1.connected? }
207
+ assert @client1.roster.instance_variable_get('@stream').is_connected?
208
+ assert_before(60) { assert_equal 1, @client2.received_messages.size }
209
+ end
210
+
211
+ def test_disconnect_doesnt_allow_auto_reconnects
212
+ @client1.disconnect
213
+
214
+ assert_equal false, @client1.connected?
215
+
216
+ assert_raises Jabber::ConnectionError do
217
+ @client1.deliver(@jid2, "testing")
218
+ end
219
+
220
+ @client1.reconnect
221
+ end
222
+
223
+ private
224
+
225
+ def assert_before(seconds, &block)
226
+ error = nil
227
+
228
+ # This is for Ruby 1.9.1 compatibility
229
+ assertion_exception_class = begin
230
+ MiniTest::Assertion
231
+ rescue NameError
232
+ Test::Unit::AssertionFailedError
233
+ end
234
+
235
+ begin
236
+ Timeout::timeout(seconds) {
237
+ begin
238
+ yield
239
+ rescue assertion_exception_class => e
240
+ error = e
241
+ sleep 0.5
242
+ retry
243
+ end
244
+ }
245
+ rescue Timeout::Error
246
+ raise error
247
+ end
248
+ end
249
+
250
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jfcouture-xmpp4r-simple
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.9
5
+ platform: ruby
6
+ authors:
7
+ - Blaine Cook
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-13 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: xmpp4r
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.3.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rcov
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Jabber::Simple takes the strong foundation laid by xmpp4r and hides the relatively high complexity of maintaining a simple instant messenger bot in Ruby.
46
+ email: romeda@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README
53
+ - COPYING
54
+ files:
55
+ - test/test_xmpp4r_simple.rb
56
+ - lib/xmpp4r-simple.rb
57
+ - README
58
+ - COPYING
59
+ - CHANGELOG
60
+ has_rdoc: true
61
+ homepage: http://xmpp4r-simple.rubyforge.org/
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project: xmpp4r-simple
82
+ rubygems_version: 1.2.0
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: A simplified Jabber client library.
86
+ test_files: []
87
+