Compare commits

...

139 Commits

Author SHA1 Message Date
mathieui
3dcb96d9d8 Update version to 1.5.0 2020-05-01 15:26:24 +02:00
Maxime Buquet
0a7a4c3abe Merge branch 'python-version' into 'master'
Update Python version minimum requirement to 3.7

See merge request poezio/slixmpp!42
2020-05-01 15:14:49 +02:00
Maxime “pep” Buquet
a4bbc404ed Update Python version minimum requirement to 3.7
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-05-01 15:13:19 +02:00
Maxime “pep” Buquet
c3fbc6cb80 xep_0196: Use correct tag local name (thanks ivucica)
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-04-06 12:57:12 +02:00
Maxime “pep” Buquet
355d789061 docs: remove andyet logo/link on every page
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-04-06 12:57:12 +02:00
mathieui
47ed67c04e Merge branch 'add-github-pr-template' into 'master'
Add a github pull request template

See merge request poezio/slixmpp!41
2020-04-04 20:06:23 +02:00
mathieui
34567f450a Add a github pull request template 2020-04-04 20:04:23 +02:00
mathieui
9126bd8392 Merge branch 'fix-deprecations' into 'master'
Fix deprecation warning regarding invalid escape sequences.

See merge request poezio/slixmpp!40
2020-04-04 18:53:15 +02:00
mathieui
02202f7cd8 Merge branch 'fix-nonetype-error' into 'master'
Fix an issue when deleting subelements: TypeError: 'NoneType' object is not an iterator

See merge request poezio/slixmpp!39
2020-04-04 18:44:25 +02:00
jheling
2add94f5b0 Fix TypeError: 'NoneType' object is not an iterator
When deleting sub-elements in a stanza.
2020-04-04 18:42:48 +02:00
Karthikeyan Singaravelan
5fc757f200 Fix deprecation warning regarding invalid escape sequences. 2020-04-04 16:08:44 +00:00
mathieui
98108d0445 Merge branch 'fix-celementtree-import' into 'master'
cElementTree has been deprecated since Python 3.3 and removed in Python 3.9.

See merge request poezio/slixmpp!38
2020-04-04 17:55:11 +02:00
Maxime Buquet
76f4fb49d6 Merge branch 'sync-fixes' into 'master'
Sync fixes

See merge request poezio/slixmpp!37
2020-04-04 15:28:59 +02:00
Georg Lukas
5be46a5e68 fire 'disconnected' callback from abort() 2020-04-04 13:16:33 +02:00
Georg Lukas
ab9040c30e expose is_connecting state based on connection attempt future 2020-04-04 13:16:31 +02:00
Maxime Buquet
a16e2a0f6c Merge branch 'fix-0198' into 'master'
XEP-0198: unset end_session_on_disconnect on resume/enable

See merge request poezio/slixmpp!36
2020-03-29 14:28:06 +02:00
Maxime Buquet
842aa3be8f Merge branch 'fix-reconnect-2.0' into 'master'
Reset reconnect delay on manual reconnect, add delay event

See merge request poezio/slixmpp!35
2020-03-29 14:26:15 +02:00
Georg Lukas
6c28b49e7f XEP-0198: unset end_session_on_disconnect on resume/enable 2020-03-29 14:21:40 +02:00
Georg Lukas
621255027d Reset reconnect delay on manual reconnect, add delay event
This is just a hotfix workaround for an underlying problem. The
`_connect_routine` code is "blocking" (in an async way) for
`connect_loop_wait` seconds, so that a fresh-started manual reconnect
will be silenty delayed. This code does the following changes:

1. It moves the delay to before the DNS resolution (with the exponential
   back-off it might well be that the DNS records are changed while
   slixmpp is waiting).

2. It adds a new event `reconnect_delay` that gets passed the number of
   seconds it will delay before actually reconnecting

3. It resets the `connect_loop_wait` timer on a manual connect/reconnect
   call to fix the interactive experience.

A *proper fix* would replace the sleep in `_connect_routine` with a
properly timered re-invocation of it, but I don't understand enough of
asyncio for pulling off that magic, and this is actually a proper
improvement. Also I tested this and it works!
2020-03-29 14:21:05 +02:00
Maxime Buquet
efe316dc8c Merge branch 'fix-0198' into 'master'
XEP-0198: properly disable on disconnect, fix reconnect-loop

See merge request poezio/slixmpp!34
2020-03-28 22:11:52 +01:00
Maxime Buquet
e9a87a0b77 Merge branch 'master' into 'master'
reconnect: fix callback when not currently connected

See merge request poezio/slixmpp!32
2020-03-28 22:11:34 +01:00
Georg Lukas
85c9967b9c XEP-0198: fix race conditions on enable/resume
This code splits out the `enabled` property into `enabled_in` and
`enabled_out` to reflect that client and server enable 0198
asynchronously.

This also moves the actual enabling code into the stanza processing
logic, because apparently, `enable.send()` was just added into the end
of the send queue, but `enable` got enabled immediately, so that poezio
requested ACKs for whatever happened to be in the queue before.

Async is hard, let's go get fishing.
2020-03-28 21:49:18 +01:00
Georg Lukas
deb6d4f176 XEP-0198: properly disable on disconnect, fix reconnect-loop
When the connection is disconnected (but the session didn't "end",
because 0198 resumption is enabled), poezio will reconnect and try to
send an <r/> element because the 0198 plugin doesn't realize that SM
wasn't re-enabled yet.
2020-03-28 20:50:55 +01:00
Karthikeyan Singaravelan
7218bb4499 cElementTree has been deprecated since Python 3.3 and removed in Python 3.9. 2020-03-28 04:42:23 +00:00
Georg Lukas
d85efec7a2 reconnect: fix callback when not currently connected
The 'disconnected' event is normally fired from connection_lost(), which
is called by the connection code when the connection is lost after being
established. However, if the connection wasn't successfully established,
a manual /reconnect no-ops because it waits for the 'disconnected'
callback which never fires. This patch does two things:

1. Immediately fire a 'disconnected' event in disconnect() if there is
   no transport.

2. Register the 'disconnected' event handler in reconnect() *before* it
   can be fired.
2020-03-23 18:59:29 +01:00
mathieui
115c234527 Merge branch 'async-filters' into 'master'
Add async filters on the send process

See merge request poezio/slixmpp!24
2019-12-27 15:35:00 +01:00
mathieui
a0f5cb6e09 Put the queue exception at toplevel 2019-12-27 15:27:29 +01:00
mathieui
110bbf8afc Improve the send queue code a bit 2019-12-27 15:27:29 +01:00
mathieui
d97efa0bd8 add a separate place for slow ass filters 2019-12-27 15:27:29 +01:00
mathieui
672f1b28f6 raise Exception 2019-12-27 15:27:29 +01:00
mathieui
27d3ae958b Try/except around outbound stanza processing
to avoid killing the send loop when a filter has an error
2019-12-27 15:27:29 +01:00
mathieui
a32794ec35 Remove trailing whitespace 2019-12-27 15:27:28 +01:00
mathieui
aa11ba463e Skip 0323 because 2019-12-27 15:27:28 +01:00
mathieui
a83c00e933 Update test framework to work with new filters
(eewww)
2019-12-27 15:27:28 +01:00
mathieui
31f6ef6814 Run the send queue in a separate coroutine
To be able to run async stream filters
2019-12-27 15:27:25 +01:00
Maxime “pep” Buquet
9b3874b5df xep_0048: Ensure Conference jid is of type str when given a JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-11-11 14:00:19 +01:00
mathieui
0139fb291e Fix a bug in the 0202 plugin where a time result would send back a result
poezio bug #3499
2019-09-19 22:54:53 +02:00
mathieui
e58988484a Match the sender JID as well as the queryid in MAM results 2019-09-10 23:13:04 +02:00
mathieui
5d5e5cda19 Add typing/docstring in the MAM plugin 2019-09-10 22:44:46 +02:00
root
11f707987d Added amount parameter, so that limit on the msgs received per query can be changed. 2019-09-08 14:22:48 +02:00
Maxime “pep” Buquet
db13794e0f Revert "Remove a block of compatibility code"
This reverts commit 37bc1bb9b3.

Confusion confusion. Mathieui thought this was a sleekxmpp thing when
it's actually been added not so long ago.
2019-08-28 00:12:10 +02:00
mathieui
37bc1bb9b3 Remove a block of compatibility code
even if the user makes that mistake, it does not cause problems down the
line.
2019-08-26 12:00:38 +02:00
mathieui
9be30e5291 fix a typo in the invalidjid exception name case 2019-08-25 01:42:00 +02:00
Maxime Buquet
9fe20a4056 Merge branch 'origin-id' into 'master'
Implement Origin-id (XEP-0359)

See merge request poezio/slixmpp!21
2019-08-23 17:10:42 +02:00
Maxime “pep” Buquet
3253d34c0a basexmpp: Make origin-id opt-out
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-23 17:09:21 +02:00
Maxime “pep” Buquet
fef575ee1a Implement origin-id (XEP-0359)
This XEP is not implemented as a plugin but directly into Message.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-23 17:09:21 +02:00
Maxime Buquet
540ff89427 Merge branch 'mam' into 'master'
Assign 'True' to 'before' tag if it's value is 'None'.

See merge request poezio/slixmpp!26
2019-08-23 16:37:12 +02:00
root
dd8ac8fc87 Assign True to the 'before' tag if it's value is None (eg:at the start no msg is there in the group, so no stanza-id) 2019-08-23 04:39:20 +05:30
Maxime Buquet
2249d878d1 Merge branch 'mam' into 'master'
Removed assigning 'reverse' value to the 'before' tag

See merge request poezio/slixmpp!25
2019-08-23 00:03:07 +02:00
root
89fa9dc1dd Removed before tag. (Code for setting it is already there) 2019-08-23 00:21:42 +05:30
root
d7729e8683 Removed assigning 'reverse' value to the 'before' tag (It's value is set in xep_0313 (mam.py file) and if not then by default it is takes as 'None'). 2019-08-23 00:15:56 +05:30
louiz’
d618f55dea Merge branch 'mam' into 'master'
Added <before> tag for querying messages before a stanza-id.

See merge request poezio/slixmpp!23
2019-08-13 09:54:43 +02:00
Madhur Garg
b0e688eb35 Added <before> tag for querying messages before a stanza-id. 2019-08-12 23:39:01 +05:30
Maxime “pep” Buquet
0e7176483b xep_0030: add docstring to get_info_from_domain
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-03 16:05:09 +02:00
Emmanuel Gil Peyrot
f35569a2c1 Remove the last instances of a block argument to iq.send().
Thanks Madhur Garg for spotting this in
027ce2434d7fd3cf4a286dd373cb761c0d114c66!
2019-08-01 22:36:59 +02:00
Link Mauve
bec6f7c8f3 Merge branch 'mam' into 'master'
Removed 'block' from set_preferences as it was giving a TypeError while sending the staza.

See merge request poezio/slixmpp!20
2019-08-01 22:30:58 +02:00
Madhur Garg
027ce2434d Removed 'block' from set_preferences as it was giving a TypeError while sending the staza. 2019-08-02 01:57:36 +05:30
Maxime Buquet
d57fbb57a2 Merge branch 'mam' into 'master'
Added a function in xep313 plugin to get current MAM preferences.

See merge request poezio/slixmpp!19
2019-07-22 22:05:05 +02:00
Madhur Garg
85cd7a9166 Added a function to get current MAM preferences. 2019-07-18 16:55:11 +05:30
Maxime “pep” Buquet
d50d996c68 xmlstream/stanzabase: remove unused interfaces and types attributes
These are already on each stanza, and were not applicable to all stanzas
anyway.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-16 11:26:54 +02:00
mathieui
371ad20ca7 Do not add disco#info for occupantid, it’s a server thing 2019-07-14 13:25:22 +02:00
mathieui
5f49df6b56 Add the occupant id stanza elements 2019-07-14 13:13:59 +02:00
mathieui
b50bfb2f34 Initial commit for reactions protoxep 2019-07-13 19:44:32 +02:00
Maxime “pep” Buquet
b29bb30eb7 Make generated stanza id truly random
Fix long-standing security issues where stanza @id be predictable.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-13 14:07:31 +02:00
Maxime “pep” Buquet
4435c81d77 xmlstream.disconnect: add compat behaviour, set wait to default 2.0 when True is passed. Update documentation
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-03 21:36:17 +02:00
Madhur Garg
2638ba2744 Added 'reverse' argument. 2019-07-03 14:18:33 +05:30
Madhur Garg
dbc9758311 Added 'reverse' parameter in mam and rsm plugins 2019-07-03 10:31:18 +05:30
Maxime “pep” Buquet
47968963b1 Revert part of previous commit. Return NotImplemented when object is not a valid JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-05-07 11:56:39 +01:00
Maxime “pep” Buquet
4e8800f954 jid: return not equal if value can't be converted to JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-05-07 10:08:46 +01:00
Maxime “pep” Buquet
40053518aa Merge remote-tracking branch 'origin/mr/13' 2019-04-27 12:59:23 +01:00
Maxime “pep” Buquet
1ee0f72ead xmlstream.disconnect: fix frenchism in docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 23:35:33 +01:00
Maxime “pep” Buquet
4bb81228ae xmlstream.disconnect: typing hints
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 23:35:06 +01:00
Maxime “pep” Buquet
60a7a5b8df presence: Ensure <show/> value is valid when returned as presence @type value
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 20:29:54 +01:00
Maxime “pep” Buquet
946674f424 xep_0045: Ensure <show/> value is valid.
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 20:28:58 +01:00
Maxime “pep” Buquet
412a9169bd Fixes #3432. Allow execute to be used with the meaning of 'next'.
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-14 02:34:22 +01:00
Maxime “pep” Buquet
72b355de8c xep_0050: Fix indentation
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-14 02:19:58 +01:00
Maxime “pep” Buquet
af246dcfe1 slixmpp/jid: add types
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-07 23:14:44 +01:00
Maxime Buquet
9612e518fb Merge branch 'master' into 'master'
Communicate the reason for a disconnect to the application

See merge request poezio/slixmpp!12
2019-04-07 00:24:50 +02:00
Maxime “pep” Buquet
fde8264191 xep_0202: Fix plugin_init docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-06 13:44:05 +01:00
Maxime “pep” Buquet
1cdc656208 Fixes poezio/poezio#3472: Don't remove TZ in 0202 utc tag
<utc/> MUST conform to 0082 dateTime profile and thus include a
timezone definition.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-06 13:42:26 +01:00
Maxime “pep” Buquet
0042108a67 poezio/poezio#3472: Ensure tz is correctly set when offset is an int
Thanks lovetox!

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-03 23:16:37 +01:00
Georg Lukas
704161a285 mark end-of-stream as session-ending event 2019-03-26 15:16:52 +01:00
Georg Lukas
6b1b58a339 XEP-0199: use new 0-timeout reconnect() with reason 2019-03-26 12:09:43 +01:00
Georg Lukas
4f96e5fa75 Do not directly enqueue connect() as event handler, parameter mismatch 2019-03-26 12:09:43 +01:00
Georg Lukas
bcb90a653e Do not close stream on 0-timeout disconnect, allows 0198 resume 2019-03-26 11:02:28 +01:00
Georg Lukas
7e435b703d Propagate disconnect() reason into 'disconnected' event 2019-03-26 11:01:36 +01:00
Maxime “pep” Buquet
2dda6b80d4 Partially fix poezio/poezio#3452. Prevent groupchat_subject from triggered sent when body or thread are in the message.
0045 says:
> The subject is changed by sending a message of type "groupchat" to the
> <room@service>, where the <message/> MUST contain a <subject/> element that
> specifies the new subject but MUST NOT contain a <body/> element (or a
> <thread/> element). In accordance with the core definition of XMPP, other child
> elements are allowed (although the entity that receives them might ignore
> them).
>
> Note: A message with a <subject/> and a <body/> or a <subject/> and a <thread/>
> is a legitimate message, but it SHALL NOT be interpreted as a subject change.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-03-23 17:06:07 +00:00
mathieui
5629e44710 Merge branch 'patch-1' into 'master'
Fix slixmpp.ClientXMPP.cancel_connection_attempt()

See merge request poezio/slixmpp!10
2019-03-15 00:38:35 +01:00
Maxime “pep” Buquet
6a06881d8b xep_0030: fix typo on 'remote' in get_info docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-03-05 00:00:14 +00:00
Maxime “pep” Buquet
2b666eb1de Revert "Remove license from unused project"
This reverts commit fbab3ad214.
2019-02-24 12:16:06 +00:00
Emmanuel Gil Peyrot
400e7a3903 Remove SocksiPy license
I wrote a SOCKS5 implementation back in
9c5dd024b1, and removed SocksiPy from
slixmpp’s codebase at the same time.
2019-02-24 13:02:08 +01:00
Maxime “pep” Buquet
fbab3ad214 Remove license from unused project
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-24 12:00:16 +00:00
mathieui
628b357b06 Merge branch 'eme-add-method' into 'master'
xep_0380: Add add_eme method

See merge request poezio/slixmpp!11
2019-02-23 14:07:15 +01:00
Maxime “pep” Buquet
88260cc240 xep_0380: Add add_eme method
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-23 12:54:56 +00:00
Maxime “pep” Buquet
e9f2f503b8 xep_0380: Remove remove_handler call in plugin_end
Remove probable copy paste fail in EME plugin_end handler.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-23 12:36:40 +00:00
ehendrix23
696a72247b Fix slixmpp.ClientXMPP.cancel_connection_attempt() 2019-02-22 00:41:02 +01:00
Maxime “pep” Buquet
05d76e4b1d Change more URLs to lab.louiz.org
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-12 10:34:57 +00:00
Maxime “pep” Buquet
d52d4fbbbe Update project URLs to lab.louiz.org
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-12 10:26:04 +00:00
Maxime “pep” Buquet
e53c0fcb30 README: Add pep as a contributor
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-10 14:18:28 +00:00
Maxime “pep” Buquet
97d68c5196 setup.py: GTalk is no more
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-10 14:18:28 +00:00
mathieui
b42fafabb4 Make the cache encode and decode not crash if something goes wrong 2019-02-02 17:42:24 +01:00
mathieui
3a44ec8f15 Add tests for the cache api 2019-02-02 17:32:10 +01:00
mathieui
93f385562f Add a "remove" action on the cache API 2019-02-02 17:31:48 +01:00
mathieui
9cab02438b Fix XEP-0335 2019-02-02 15:41:55 +01:00
Emmanuel Gil Peyrot
74ed50e626 Set @id by default on outgoing messages and presences.
Respects RFC6120 §8.1.3’s RECOMMENDED.
2019-01-31 16:46:51 +01:00
mathieui
9d378c611c Release 1.4.2 2019-01-31 14:50:26 +01:00
Link Mauve
d85d8f4479 Merge branch 'xep-0335' into 'master'
Add xep_0335: JSON Containers

See merge request poezio/slixmpp!5
2019-01-22 19:28:38 +01:00
Emmanuel Gil Peyrot
fb75f7cda9 Stop requesting avatar without the intervention of the client. 2019-01-22 15:12:00 +01:00
Emmanuel Gil Peyrot
41419a2161 Fix authenticating on a non-TLS socket.
This was broken since c1562b76b2.
2019-01-21 01:02:06 +01:00
Emmanuel Gil Peyrot
7cd73b594e XEP-0223: Fix default access_model, it MUST be whitelist. 2019-01-17 12:08:51 +01:00
Emmanuel Gil Peyrot
15c6b775ff Simplify the non-CDATA path of tostring.escape. 2019-01-09 15:03:05 +01:00
Emmanuel Gil Peyrot
4b482477e2 Split ns only once in fix_ns(). 2019-01-09 14:57:39 +01:00
Emmanuel Gil Peyrot
f7e4caadfe Split tag and attrib only once in tostring(). 2019-01-09 14:55:27 +01:00
Emmanuel Gil Peyrot
5f25b0b6a0 Add a default timeout to iq.send().
This fixes a leak of MatchIDSender in handlers, making it more and more
expensive to match stanzas as more iqs have been sent.
2019-01-09 14:20:31 +01:00
Mateusz Piotrowski
d228bc42ea Mention that GnuPG is required for tests 2018-12-27 17:21:12 +01:00
mathieui
ecdc44a601 Merge branch 'master' into 'master'
Decode bytes in GSSAPI handling, as expected by the kerberos module API.

See merge request poezio/slixmpp!8
2018-12-27 16:55:47 +01:00
Emmanuel Gil Peyrot
33370e42f1 XEP-0363: Use a specific exception for HTTP errors 2018-11-20 07:44:09 +01:00
Florian Klien
4699861925 catch http upload errors on upload 2018-11-20 07:34:56 +01:00
Jelmer Vernooij
2d228bdb56 Decode bytes in GSSAPI handling, as expected by the kerberos module API. 2018-10-30 22:29:20 +00:00
mathieui
570e653ac2 Release slixmpp 1.4.1 2018-10-28 14:15:51 +01:00
mathieui
282a481059 Merge branch 'setup_dependency_fix' into 'master'
added aiohttp dependency

See merge request poezio/slixmpp!6
2018-10-28 14:08:26 +01:00
Florian Klien
f386db380b docs: auto-set copyright year to current year 2018-10-28 10:48:23 +01:00
Florian Klien
7b87d98fff auto set version of Slixmpp in Docs
getting version of slixmpp from source tree for documentation
2018-10-28 10:48:23 +01:00
Florian Klien
8779d40602 typo 2018-10-27 23:38:09 +02:00
Emmanuel Gil Peyrot
f0b21c42d5 examples: Add the possibility to use another HTTP File Upload domain. 2018-10-27 23:25:59 +02:00
Emmanuel Gil Peyrot
e241d4e3c7 XEP-0030: Don’t call the timeout_callback on each domain which doesn’t reply to disco#info. 2018-10-27 23:21:27 +02:00
Emmanuel Gil Peyrot
bd22a41a78 XEP-0363: Also check for disco#info’s feature instead of just the identity. 2018-10-27 23:14:39 +02:00
Emmanuel Gil Peyrot
a29a29227a XEP-0363: Add a domain argument to discover an upload service on a specific domain. 2018-10-27 22:51:04 +02:00
Florian Klien
d4d542b741 fixing uncaught async exceptions due to missing await
fixes uncaught exceptions in the event loop.
passing timeout and timeout_callback through.
2018-10-27 22:51:04 +02:00
Florian Klien
dc4936a6d3 added aoihttp dependency
slixmpp uses aiohttp in the XEP_0363 plugin.
2018-10-22 12:41:20 +02:00
Florian Klien
897610d819 fix: failUnlessEqual -> assertEqual 2018-10-15 14:59:23 +02:00
Florian Klien
d33366badd fixing deprecation warnings for pytest 2018-10-15 14:59:23 +02:00
mathieui
809c500002 Add the loop parameters at places where it has been forgotten 2018-10-09 12:34:56 +02:00
Emmanuel Gil Peyrot
dda4e18b81 examples: Remove unused asyncio imports. 2018-10-09 10:57:19 +02:00
Emmanuel Gil Peyrot
8c09d932c8 stanzabase: Remove python2 legacy. 2018-10-03 14:56:07 +02:00
Maxime “pep” Buquet
31f5e84671 Add xep_0335: JSON Containers
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-09-16 22:13:41 +01:00
louiz’
ad0dc33df9 Trigger poezio’s build if this ours succeeded 2018-08-22 23:19:47 +02:00
Emmanuel Gil Peyrot
7c3b3827b4 jid: Make property aliases proper aliases. 2018-08-20 00:23:21 +01:00
Emmanuel Gil Peyrot
9f6fa65139 examples, tests: Replace all @asyncio.coroutines with proper async functions. 2018-08-19 17:47:26 +01:00
100 changed files with 1087 additions and 557 deletions

13
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,13 @@
################ Please use Gitlab instead of Github ###################################
Hello, thank you for contributing to slixmpp!
Youre about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp.
Please open your merge request on https://lab.louiz.org/poezio/slixmpp/
You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes.
This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes.
Thank you.

View File

@@ -1,4 +1,9 @@
stages:
- test
- trigger
test:
stage: test
tags:
- docker
image: ubuntu:latest
@@ -6,3 +11,11 @@ test:
- apt update
- apt install -y python3 cython3 gpg
- ./run_tests.py
trigger_poezio:
stage: trigger
tags:
- docker
image: appropriate/curl:latest
script:
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline

View File

@@ -1,9 +1,7 @@
language: python
python:
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
- "3.7"
- "3.8-dev"
install:
- "pip install ."
script: testall.py

View File

@@ -5,7 +5,7 @@ To contribute, the preferred way is to commit your changes on some
publicly-available git repository (on a fork `on github
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either:
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
- a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_

View File

@@ -1,6 +1,7 @@
Pre-requisites:
- Python 3.5+
- Python 3.7+
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
- GnuPG, for testing
Install:
> python3 setup.py install

25
LICENSE
View File

@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
socksipy: A Python SOCKS client module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.

View File

@@ -1,7 +1,7 @@
Slixmpp
#########
Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of
SleekXMPP.
Slixmpp's goals is to only rewrite the core of the library (the low level
@@ -113,6 +113,7 @@ Slixmpp Credits
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
Credits (SleekXMPP)
-------------------

View File

@@ -408,24 +408,3 @@ div.viewcode-block:target {
margin: -1px -12px;
padding: 0 12px;
}
#from_andyet {
-webkit-box-shadow: #CCC 0px 0px 3px;
background: rgba(255, 255, 255, 1);
bottom: 0px;
right: 17px;
padding: 3px 10px;
position: fixed;
}
#from_andyet h2 {
background-image: url("images/from_&yet.png");
background-repeat: no-repeat;
height: 29px;
line-height: 0;
text-indent: -9999em;
width: 79px;
margin-top: 0;
margin: 0px;
padding: 0px;
}

View File

@@ -65,6 +65,5 @@
<div class="bottomnav">
{{ nav() }}
</div>
<a id="from_andyet" href="http://andyet.net"><h2>From &amp;yet</h2></a>
{% endblock %}

View File

@@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace
prefixes::
>>> from slixmpp.xmlstream.tostring import tostring
>>> from xml.etree import cElementTree as ET
>>> from xml.etree import ElementTree as ET
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
>>> ET.tostring(xml)
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'

View File

@@ -12,12 +12,17 @@
# serve to show the default.
import sys, os
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# get version automagically from source tree
from slixmpp.version import __version__ as version
release = ".".join(version.split(".")[0:2])
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -41,16 +46,18 @@ master_doc = 'index'
# General information about the project.
project = u'Slixmpp'
copyright = u'2011, Nathan Fritz, Lance Stout'
year = datetime.datetime.now().year
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# auto imported from code!
# The short X.Y version.
version = '1.1'
# version = '1.4'
# The full version, including alpha/beta/rc tags.
release = '1.1'
# release = '1.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -3,8 +3,9 @@
Differences from SleekXMPP
==========================
**Python 3.5+ only**
slixmpp will only work on python 3.5 and above.
**Python 3.7+ only**
slixmpp will work on python 3.7 and above. It may work with previous
versions but we provide no guarantees.
**Stanza copies**
The same stanza object is given through all the handlers; a handler that

View File

@@ -11,7 +11,7 @@ Create and Run a Server Component
<xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <http://git.poez.io/slixmpp>`_.
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Many XMPP applications eventually graduate to requiring to run as a server
component in order to meet scalability requirements. To demonstrate how to

View File

@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
<xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <http://git.poez.io/slixmpp>`_.
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
As a basic starting project, we will create an echo bot which will reply to any
messages sent to it. We will also go through adding some basic command line configuration
@@ -329,7 +329,7 @@ The Final Product
-----------------
Here then is what the final result should look like after working through the guide above. The code
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
.. compound::

View File

@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
<xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
from `Git <http://git.poez.io/slixmpp>`_.
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Now that you've got the basic gist of using Slixmpp by following the
echobot example (:ref:`echobot`), we can use one of the bundled plugins

View File

@@ -47,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth:
self.send_message(mto=self.recipient, mbody=self.msg)
Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`.
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
for the client to disconnect before the send queue is processed and the message is actually
sent on the wire. To ensure that our message is processed, we use
:meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`.
Now, sent stanzas are placed in a queue to pass them to the send thread.
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an
acknowledgement from the server for at least `2.0` seconds. This time is configurable with
the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect
<slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully.
.. code-block:: python
@@ -61,12 +61,12 @@ sent on the wire. To ensure that our message is processed, we use
self.send_message(mto=self.recipient, mbody=self.msg)
self.disconnect(wait=True)
self.disconnect()
.. warning::
If you happen to be adding stanzas to the send queue faster than the send thread
can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`
can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>`
will block and not disconnect.
Final Product

View File

@@ -4,9 +4,9 @@ Slixmpp
.. sidebar:: Get the Code
The latest source code for Slixmpp may be found on the `Git repo
<http://git.poez.io/slixmpp>`_. ::
<https://lab.louiz.org/poezio/slixmpp>`_. ::
git clone git://git.poez.io/slixmpp
git clone https://lab.louiz.org/poezio/slixmpp
An XMPP chat room is available for discussing and getting help with slixmpp.
@@ -14,14 +14,14 @@ Slixmpp
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
**Reporting bugs**
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
.. note::
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
which goal is to use asyncio instead of threads to handle networking. See
:ref:`differences`.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+,
Slixmpp's design goals and philosphy are:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)

View File

@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
else:
self.confirmed.set_result(True)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
log.info('Sending confirm request %s to %s who wants to access %s using '
'method %s...' % (self.id, self.recipient, self.url, self.method))
try:
confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
id=self.id,
url=self.url,
method=self.method,
message='Plz say yes or no for {method} {url} ({id}).')
if isinstance(confirmed, slixmpp.Message):
confirmed = yield from self.confirmed
confirmed = await self.confirmed
else:
confirmed = True
except IqError:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.xmlstream.asyncio import asyncio
class Disco(slixmpp.ClientXMPP):
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
try:
if self.get in self.info_types:
# function using the callback parameter.
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
info = await self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
items = await self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node)
if self.get not in self.info_types and self.get not in self.items_types:
logging.error("Invalid disco request type.")

View File

@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.roster_received.set()
self.presences_received.clear()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.get_roster(callback=self.roster_received_cb)
print('Waiting for presence updates...\n')
yield from self.roster_received.wait()
await self.roster_received.wait()
print('Roster received')
yield from self.presences_received.wait()
await self.presences_received.wait()
self.disconnect()
@asyncio.coroutine
def on_vcard_avatar(self, pres):
async def on_vcard_avatar(self, pres):
print("Received vCard avatar update from %s" % pres['from'].bare)
try:
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % pres['from'])
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
with open(filename, 'wb+') as img:
img.write(avatar['BINVAL'])
@asyncio.coroutine
def on_avatar(self, msg):
async def on_avatar(self, msg):
print("Received avatar update from %s" % msg['from'])
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
for info in metadata['items']:
if not info['url']:
try:
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % msg['from'])

View File

@@ -14,7 +14,6 @@ from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp import asyncio
log = logging.getLogger(__name__)
@@ -25,18 +24,21 @@ class HttpUpload(slixmpp.ClientXMPP):
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, filename):
def __init__(self, jid, password, recipient, filename, domain=None):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.filename = filename
self.domain = domain
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
log.info('Uploading file %s...', self.filename)
url = yield from self['xep_0363'].upload_file(self.filename)
def timeout_callback(arg):
raise TimeoutError("could not send message in time")
url = await self['xep_0363'].upload_file(
self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
log.info('Upload success!')
log.info('Sending file to %s', self.recipient)
@@ -70,6 +72,8 @@ if __name__ == '__main__':
help="Recipient JID")
parser.add_argument("-f", "--file", required=True,
help="File to send")
parser.add_argument("--domain",
help="Domain to use for HTTP File Upload (leave out for your own servers)")
args = parser.parse_args()
@@ -82,7 +86,7 @@ if __name__ == '__main__':
if args.password is None:
args.password = getpass("Password: ")
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file)
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
xmpp.register_plugin('xep_0071')
xmpp.register_plugin('xep_0128')
xmpp.register_plugin('xep_0363')

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
try:
# Open the IBB stream in which to write to.
stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
# If you want to send in-memory bytes, use stream.sendall() instead.
yield from stream.sendfile(self.file, timeout=10)
await stream.sendfile(self.file, timeout=10)
# And finally close the stream.
yield from stream.close(timeout=10)
await stream.close(timeout=10)
except (IqError, IqTimeout):
print('File transfer errored')
else:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)

View File

@@ -13,7 +13,6 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp import asyncio
import slixmpp
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
self.get_roster()
try:
rtt = yield from self['xep_0199'].ping(self.pingjid,
rtt = await self['xep_0199'].ping(self.pingjid,
timeout=10)
logging.info("Success! RTT: %s", rtt)
except IqError as e:

View File

@@ -5,7 +5,6 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
import asyncio
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import ET, tostring
@@ -32,87 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
self.add_event_handler('session_start', self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
self.get_roster()
self.send_presence()
try:
yield from getattr(self, self.action)()
await getattr(self, self.action)()
except:
logging.exception('Could not execute %s:', self.action)
self.disconnect()
def nodes(self):
async def nodes(self):
try:
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
for item in result['disco_items']['items']:
logging.info(' - %s', str(item))
except XMPPError as error:
logging.error('Could not retrieve node list: %s', error.format())
def create(self):
async def create(self):
try:
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
await self['xep_0060'].create_node(self.pubsub_server, self.node)
logging.info('Created node %s', self.node)
except XMPPError as error:
logging.error('Could not create node %s: %s', self.node, error.format())
def delete(self):
async def delete(self):
try:
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
await self['xep_0060'].delete_node(self.pubsub_server, self.node)
logging.info('Deleted node %s', self.node)
except XMPPError as error:
logging.error('Could not delete node %s: %s', self.node, error.format())
def get_configure(self):
async def get_configure(self):
try:
configuration_form = yield from self['xep_0060'].get_node_config(self.pubsub_server, self.node)
configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
except XMPPError as error:
logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
def publish(self):
async def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
try:
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
except XMPPError as error:
logging.error('Could not publish to %s: %s', self.node, error.format())
def get(self):
async def get(self):
try:
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
for item in result['pubsub']['items']['substanzas']:
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
except XMPPError as error:
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
def retract(self):
async def retract(self):
try:
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
await self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
logging.info('Retracted item %s from node %s', self.data, self.node)
except XMPPError as error:
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
def purge(self):
async def purge(self):
try:
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
await self['xep_0060'].purge(self.pubsub_server, self.node)
logging.info('Purged all items from node %s', self.node)
except XMPPError as error:
logging.error('Could not purge items from node %s: %s', self.node, error.format())
def subscribe(self):
async def subscribe(self):
try:
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node)
subscription = iq['pubsub']['subscription']
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
except XMPPError as error:
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
def unsubscribe(self):
async def unsubscribe(self):
try:
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
await self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
except XMPPError as error:
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())

View File

@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
# We're only concerned about registering, so nothing more to do here.
self.disconnect()
def register(self, iq):
async def register(self, iq):
"""
Fill out and submit a registration form.
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
resp['register']['password'] = self.password
try:
yield from resp.send()
await resp.send()
logging.info("Account created for %s!" % self.boundjid)
except IqError as e:
logging.error("Could not register account: %s" %

View File

@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.received = set()
self.presences_received = asyncio.Event()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
future.set_result(None)
try:
self.get_roster(callback=callback)
yield from future
await future
except IqError as err:
print('Error: %s' % err.iq['error']['condition'])
except IqTimeout:
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
print('Waiting for presence updates...\n')
yield from asyncio.sleep(10)
await asyncio.sleep(10)
print('Roster for %s' % self.boundjid.bare)
groups = self.client_roster.groups()

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
# and the XML streams are ready for use.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
try:
# Open the S5B stream in which to write to.
proxy = yield from self['xep_0065'].handshake(self.receiver)
proxy = await self['xep_0065'].handshake(self.receiver)
# Send the entire file.
while True:
data = self.file.read(1048576)
if not data:
break
yield from proxy.write(data)
await proxy.write(data)
# And finally close the stream.
proxy.transport.write_eof()

View File

@@ -18,7 +18,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
class AvatarSetter(slixmpp.ClientXMPP):
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
self.filepath = filepath
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
used_xep84 = False
print('Publish XEP-0084 avatar data')
result = yield from self['xep_0084'].publish_avatar(avatar)
result = await self['xep_0084'].publish_avatar(avatar)
if isinstance(result, XMPPError):
print('Could not publish XEP-0084 avatar')
else:
used_xep84 = True
print('Update vCard with avatar')
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
if isinstance(result, XMPPError):
print('Could not set vCard avatar')
if used_xep84:
print('Advertise XEP-0084 avatar metadata')
result = yield from self['xep_0084'].publish_avatar_metadata([
result = await self['xep_0084'].publish_avatar_metadata([
{'id': avatar_id,
'type': avatar_type,
'bytes': avatar_bytes}

View File

@@ -20,8 +20,7 @@ from run_tests import TestCommand
from slixmpp.version import __version__
VERSION = __version__
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
'Google Talk, etc).')
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
with open('README.rst', encoding='utf8') as readme:
LONG_DESCRIPTION = readme.read()
@@ -29,9 +28,8 @@ CLASSIFIERS = [
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules',
]
@@ -79,12 +77,12 @@ setup(
long_description=LONG_DESCRIPTION,
author='Florent Le Coz',
author_email='louiz@louiz.org',
url='https://dev.louiz.org/projects/slixmpp',
url='https://lab.louiz.org/poezio/slixmpp',
license='MIT',
platforms=['any'],
packages=packages,
ext_modules=ext_modules,
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
classifiers=CLASSIFIERS,
cmdclass={'test': TestCommand}
)

View File

@@ -104,12 +104,15 @@ class BaseXMPP(XMLStream):
#: :attr:`use_message_ids` to `True` will assign all outgoing
#: messages an ID. Some plugin features require enabling
#: this option.
self.use_message_ids = False
self.use_message_ids = True
#: Presence updates may optionally be tagged with ID values.
#: Setting :attr:`use_message_ids` to `True` will assign all
#: outgoing messages an ID.
self.use_presence_ids = False
self.use_presence_ids = True
#: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas.
self.use_origin_id = True
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is

View File

@@ -97,7 +97,10 @@ class FeatureMechanisms(BasePlugin):
jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid)
elif value == 'channel_binding':
result[value] = self.xmpp.socket.get_channel_binding()
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
result[value] = self.xmpp.socket.get_channel_binding()
else:
result[value] = None
elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
elif value == 'realm':

View File

@@ -16,6 +16,7 @@ import socket
from copy import deepcopy
from functools import lru_cache
from typing import Optional
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
@@ -71,7 +72,7 @@ def _parse_jid(data):
return node, domain, resource
def _validate_node(node):
def _validate_node(node: Optional[str]):
"""Validate the local, or username, portion of a JID.
:raises InvalidJID:
@@ -93,7 +94,7 @@ def _validate_node(node):
return node
def _validate_domain(domain):
def _validate_domain(domain: str):
"""Validate the domain portion of a JID.
IP literal addresses are left as-is, if valid. Domain names
@@ -152,7 +153,7 @@ def _validate_domain(domain):
return domain
def _validate_resource(resource):
def _validate_resource(resource: Optional[str]):
"""Validate the resource portion of a JID.
:raises InvalidJID:
@@ -174,7 +175,7 @@ def _validate_resource(resource):
return resource
def _unescape_node(node):
def _unescape_node(node: str):
"""Unescape a local portion of a JID.
.. note::
@@ -199,7 +200,11 @@ def _unescape_node(node):
return ''.join(unescaped)
def _format_jid(local=None, domain=None, resource=None):
def _format_jid(
local: Optional[str] = None,
domain: Optional[str] = None,
resource: Optional[str] = None,
):
"""Format the given JID components into a full or bare JID.
:param string local: Optional. The local portion of the JID.
@@ -237,12 +242,17 @@ class UnescapedJID:
__slots__ = ('_node', '_domain', '_resource')
def __init__(self, node, domain, resource):
def __init__(
self,
node: Optional[str],
domain: Optional[str],
resource: Optional[str],
):
self._node = node
self._domain = domain
self._resource = resource
def __getattribute__(self, name):
def __getattribute__(self, name: str):
"""Retrieve the given JID component.
:param name: one of: user, server, domain, resource,
@@ -301,7 +311,7 @@ class JID:
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
def __init__(self, jid=None):
def __init__(self, jid: Optional[str] = None):
if not jid:
self._node = ''
self._domain = ''
@@ -346,30 +356,10 @@ class JID:
def node(self):
return self._node
@property
def user(self):
return self._node
@property
def local(self):
return self._node
@property
def username(self):
return self._node
@property
def domain(self):
return self._domain
@property
def server(self):
return self._domain
@property
def host(self):
return self._domain
@property
def resource(self):
return self._resource
@@ -382,47 +372,18 @@ class JID:
def full(self):
return self._full
@property
def jid(self):
return self._full
@node.setter
def node(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@user.setter
def user(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@local.setter
def local(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@username.setter
def username(self, value):
def node(self, value: str):
self._node = _validate_node(value)
self._update_bare_full()
@domain.setter
def domain(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@server.setter
def server(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@host.setter
def host(self, value):
def domain(self, value: str):
self._domain = _validate_domain(value)
self._update_bare_full()
@bare.setter
def bare(self, value):
def bare(self, value: str):
node, domain, resource = _parse_jid(value)
assert not resource
self._node = node
@@ -430,19 +391,23 @@ class JID:
self._update_bare_full()
@resource.setter
def resource(self, value):
def resource(self, value: str):
self._resource = _validate_resource(value)
self._update_bare_full()
@full.setter
def full(self, value):
def full(self, value: str):
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
@jid.setter
def jid(self, value):
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
user = node
local = node
username = node
server = domain
host = domain
jid = full
def __str__(self):
"""Use the full JID as the string value."""
@@ -458,7 +423,10 @@ class JID:
if isinstance(other, UnescapedJID):
return False
if not isinstance(other, JID):
other = JID(other)
try:
other = JID(other)
except InvalidJID:
return NotImplemented
return (self._node == other._node and
self._domain == other._domain and

View File

@@ -85,4 +85,6 @@ __all__ = [
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
'xep_0332', # HTTP Over XMPP Transport
'protoxep_reactions', # https://dino.im/xeps/reactions.html
'protoxep_occupantid', # https://dino.im/xeps/occupant-id.html
]

View File

@@ -0,0 +1,12 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.protoxep_occupantid.occupantid import XEP_OccupantID
from slixmpp.plugins.protoxep_occupantid.stanza import OccupantID
register_plugin(XEP_OccupantID)

View File

@@ -0,0 +1,23 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins import BasePlugin
from slixmpp.stanza import Message, Presence
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.protoxep_occupantid import stanza
class XEP_OccupantID(BasePlugin):
name = 'protoxep_occupantid'
description = 'XEP-XXXX: Anonymous unique occupant identifiers for MUCs'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.OccupantID)
register_stanza_plugin(Presence, stanza.OccupantID)

View File

@@ -0,0 +1,16 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase
class OccupantID(ElementBase):
name = 'occupant-id'
plugin_attrib = 'occupant-id'
namespace = 'urn:xmpp:occupant-id:0'
interfaces = {'id'}

View File

@@ -0,0 +1,11 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.protoxep_reactions.reactions import XEP_Reactions
register_plugin(XEP_Reactions)

View File

@@ -0,0 +1,54 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import Iterable
from slixmpp.plugins import BasePlugin
from slixmpp.stanza import Message
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.matcher import MatchXMLMask
from slixmpp.xmlstream.handler import Callback
from slixmpp.plugins.protoxep_reactions import stanza
class XEP_Reactions(BasePlugin):
name = 'protoxep_reactions'
description = 'XEP-XXXX: Message Reactions'
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):
self.xmpp.register_handler(
Callback(
'Reaction received',
MatchXMLMask('<message><reactions xmlns="urn:xmpp:reactions:0"/></message>'),
self._handle_reactions,
)
)
self.xmpp['xep_0030'].add_feature('urn:xmpp:reactions:0')
register_stanza_plugin(Message, stanza.Reactions)
def plugin_end(self):
self.xmpp.remove_handler('Reaction received')
self.xmpp['xep_0030'].remove_feature('urn:xmpp:reactions:0')
def _handle_reactions(self, message: Message):
self.xmpp.event('reactions', message)
@staticmethod
def set_reactions(message: Message, to_id: str, reactions: Iterable[str]):
"""
Add reactions to a Message object.
"""
reactions_stanza = stanza.Reactions()
reactions_stanza['to'] = to_id
for reaction in reactions:
reaction_stanza = stanza.Reaction()
reaction_stanza['value'] = reaction
reactions_stanza.append(reaction_stanza)
message.append(reactions_stanza)

View File

@@ -0,0 +1,31 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
class Reactions(ElementBase):
name = 'reactions'
plugin_attrib = 'reactions'
namespace = 'urn:xmpp:reactions:0'
interfaces = {'to'}
class Reaction(ElementBase):
name = 'reaction'
namespace = 'urn:xmpp:reactions:0'
interfaces = {'value'}
def get_value(self) -> str:
return self.xml.text
def set_value(self, value: str):
self.xml.text = value
register_stanza_plugin(Reactions, Reaction, iterable=True)

View File

@@ -7,7 +7,7 @@
"""
from slixmpp.xmlstream.stanzabase import ElementBase
from xml.etree import cElementTree as ET
from xml.etree import ElementTree as ET
class RPCQuery(ElementBase):

View File

@@ -299,23 +299,29 @@ class XEP_0030(BasePlugin):
return self.api['has_identity'](jid, node, ifrom, data)
async def get_info_from_domain(self, domain=None, timeout=None,
cached=True, callback=None, **kwargs):
cached=True, callback=None):
"""Fetch disco#info of specified domain and one disco#items level below"""
if domain is None:
domain = self.xmpp.boundjid.domain
if not cached or domain not in self.domain_infos:
infos = [self.get_info(
domain, timeout=timeout, **kwargs)]
domain, timeout=timeout)]
iq_items = await self.get_items(
domain, timeout=timeout, **kwargs)
domain, timeout=timeout)
items = iq_items['disco_items']['items']
infos += [
self.get_info(item[0], timeout=timeout, **kwargs)
self.get_info(item[0], timeout=timeout)
for item in items]
info_futures, _ = await asyncio.wait(infos, timeout=timeout)
info_futures, _ = await asyncio.wait(
infos,
timeout=timeout,
loop=self.xmpp.loop
)
self.domain_infos[domain] = [
future.result() for future in info_futures]
future.result() for future in info_futures if not future.exception()]
results = self.domain_infos[domain]
@@ -344,7 +350,7 @@ class XEP_0030(BasePlugin):
combination handled by this Slixmpp instance and
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info.
remote JID to retrieve the info.
cached -- If true, then look for the disco info data from
the local cache system. If no results are found,
send the query as usual. The self.use_cache

View File

@@ -9,7 +9,7 @@ from __future__ import with_statement
import logging
from slixmpp import Presence
from slixmpp import Presence, Message
from slixmpp.plugins import BasePlugin, register_plugin
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
from slixmpp.xmlstream.handler.callback import Callback
@@ -162,7 +162,7 @@ class XEP_0045(BasePlugin):
return
self.xmpp.roster[pr['from']].ignore_updates = True
entry = pr['muc'].get_stanza_values()
entry['show'] = pr['show']
entry['show'] = pr['show'] if pr['show'] in pr.showtypes else None
entry['status'] = pr['status']
entry['alt_nick'] = pr['nick']
if pr['type'] == 'unavailable':
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
if got_online:
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
def handle_groupchat_message(self, msg):
def handle_groupchat_message(self, msg: Message) -> None:
""" Handle a message event in a muc.
"""
self.xmpp.event('groupchat_message', msg)
@@ -195,10 +195,14 @@ class XEP_0045(BasePlugin):
def handle_groupchat_subject(self, msg):
def handle_groupchat_subject(self, msg: Message) -> None:
""" Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room)
"""
# See poezio#3452. A message containing subject _and_ (body or thread)
# is not a subject change.
if msg['body'] or msg['thread']:
return None
self.xmpp.event('groupchat_subject', msg)
def jid_in_room(self, room, jid):

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
from slixmpp import JID
from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
@@ -52,6 +53,12 @@ class Conference(ElementBase):
if value in ('1', 'true', True):
self._set_attr('autojoin', 'true')
def set_jid(self, value):
del self['jid']
if isinstance(value, JID):
value = value.full
self._set_attr('jid', value)
class URL(ElementBase):
name = 'url'

View File

@@ -89,31 +89,17 @@ class XEP_0050(BasePlugin):
self.commands = {}
self.xmpp.register_handler(
Callback("Ad-Hoc Execute",
StanzaPath('iq@type=set/command'),
self._handle_command))
Callback("Ad-Hoc Execute",
StanzaPath('iq@type=set/command'),
self._handle_command))
register_stanza_plugin(Iq, Command)
register_stanza_plugin(Command, Form, iterable=True)
self.xmpp.add_event_handler('command_execute',
self._handle_command_start)
self.xmpp.add_event_handler('command_next',
self._handle_command_next)
self.xmpp.add_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.add_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.add_event_handler('command', self._handle_command_all)
def plugin_end(self):
self.xmpp.del_event_handler('command_execute',
self._handle_command_start)
self.xmpp.del_event_handler('command_next',
self._handle_command_next)
self.xmpp.del_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.del_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.del_event_handler('command', self._handle_command_all)
self.xmpp.remove_handler('Ad-Hoc Execute')
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
def _handle_command(self, iq):
"""Raise command events based on the command action."""
self.xmpp.event('command', iq)
self.xmpp.event('command_%s' % iq['command']['action'], iq)
def _handle_command_all(self, iq: Iq) -> None:
action = iq['command']['action']
sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid)
if session is None:
return self._handle_command_start(iq)
if action in ('next', 'execute'):
return self._handle_command_next(iq)
if action == 'prev':
return self._handle_command_prev(iq)
if action == 'complete':
return self._handle_command_complete(iq)
if action == 'cancel':
return self._handle_command_cancel(iq)
return None
def _handle_command_start(self, iq):
"""
Process an initial request to execute a command.
@@ -468,7 +473,7 @@ class XEP_0050(BasePlugin):
**kwargs)
def send_command(self, jid, node, ifrom=None, action='execute',
payload=None, sessionid=None, flow=False, **kwargs):
payload=None, sessionid=None, flow=False, **kwargs):
"""
Create and send a command stanza, without using the provided
workflow management APIs.

View File

@@ -79,7 +79,8 @@ class ResultIterator:
"""
if self._stop:
raise StopAsyncIteration
self.query[self.interface]['rsm']['before'] = self.reverse
if self.query[self.interface]['rsm']['before'] is None:
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount)
@@ -141,7 +142,7 @@ class XEP_0059(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface, results='substanzas',
def iterate(self, stanza, interface, results='substanzas', amount=10, reverse=False,
recv_interface=None, pre_cb=None, post_cb=None):
"""
Create a new result set iterator for a given stanza query.
@@ -169,6 +170,6 @@ class XEP_0059(BasePlugin):
results -- The name of the interface containing the
query results (typically just 'substanzas').
"""
return ResultIterator(stanza, interface, results,
return ResultIterator(stanza, interface, results, amount, reverse=reverse,
recv_interface=recv_interface, pre_cb=pre_cb,
post_cb=post_cb)

View File

@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
def _force_stream_feature(self, stanza):
if isinstance(stanza, StreamFeatures):
if self.xmpp.use_tls or self.xmpp.use_ssl:
if not self.xmpp.disable_starttls:
if 'starttls' not in self.xmpp.features:
return stanza
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):

View File

@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
sec = now.second
if micro is None:
micro = now.microsecond
if offset is None:
if offset in (None, 0):
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset)
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
sec = now.second
if micro is None:
micro = now.microsecond
if offset is None:
if offset in (None, 0):
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset)

View File

@@ -167,10 +167,7 @@ class XEP_0153(BasePlugin):
data = pres['vcard_temp_update']['photo']
if data is None:
return
elif data == '' or data != self.api['get_hash'](pres['from']):
ifrom = pres['to'] if self.xmpp.is_component else None
self.api['reset_hash'](pres['from'], ifrom=ifrom)
self.xmpp.event('vcard_avatar_update', pres)
self.xmpp.event('vcard_avatar_update', pres)
# =================================================================

View File

@@ -62,7 +62,10 @@ class XEP_0163(BasePlugin):
for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
jid=jid)
asyncio.ensure_future(self.xmpp['xep_0115'].update_caps(jid))
asyncio.ensure_future(
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def remove_interest(self, namespace, jid=None):
"""
@@ -81,7 +84,10 @@ class XEP_0163(BasePlugin):
for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid,
feature='%s+notify' % namespace)
asyncio.ensure_future(self.xmpp['xep_0115'].update_caps(jid))
asyncio.ensure_future(
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
timeout_callback=None, callback=None, timeout=None):

View File

@@ -11,10 +11,9 @@ from slixmpp.xmlstream import ElementBase, ET
class UserGaming(ElementBase):
name = 'gaming'
name = 'game'
namespace = 'urn:xmpp:gaming:0'
plugin_attrib = 'gaming'
interfaces = {'character_name', 'character_profile', 'name',
'level', 'server_address', 'server_name', 'uri'}
sub_interfaces = interfaces

View File

@@ -71,7 +71,8 @@ class XEP_0198(BasePlugin):
self.window_counter = self.window
self.enabled = False
self.enabled_in = False
self.enabled_out = False
self.unacked_queue = collections.deque()
register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
@@ -82,10 +83,6 @@ class XEP_0198(BasePlugin):
self.xmpp.register_stanza(stanza.Ack)
self.xmpp.register_stanza(stanza.RequestAck)
# Only end the session when a </stream> element is sent,
# not just because the connection has died.
self.xmpp.end_session_on_disconnect = False
# Register the feature twice because it may be ordered two
# different ways: enabling after binding and resumption
# before binding.
@@ -131,6 +128,7 @@ class XEP_0198(BasePlugin):
self.xmpp.add_filter('in', self._handle_incoming)
self.xmpp.add_filter('out_sync', self._handle_outgoing)
self.xmpp.add_event_handler('disconnected', self.disconnected)
self.xmpp.add_event_handler('session_end', self.session_end)
def plugin_end(self):
@@ -139,6 +137,7 @@ class XEP_0198(BasePlugin):
self.xmpp.unregister_feature('sm', self.order)
self.xmpp.unregister_feature('sm', self.resume_order)
self.xmpp.del_event_handler('disconnected', self.disconnected)
self.xmpp.del_event_handler('session_end', self.session_end)
self.xmpp.del_filter('in', self._handle_incoming)
self.xmpp.del_filter('out_sync', self._handle_outgoing)
@@ -154,9 +153,19 @@ class XEP_0198(BasePlugin):
self.xmpp.remove_stanza(stanza.Ack)
self.xmpp.remove_stanza(stanza.RequestAck)
def disconnected(self, event):
"""Reset enabled state until we can resume/reenable."""
log.debug("disconnected, disabling SM")
self.xmpp.event('sm_disabled', event)
self.enabled_in = False
self.enabled_out = False
def session_end(self, event):
"""Reset stream management state."""
self.enabled = False
log.debug("session_end, disabling SM")
self.xmpp.event('sm_disabled', event)
self.enabled_in = False
self.enabled_out = False
self.unacked_queue.clear()
self.sm_id = None
self.handled = 0
@@ -171,6 +180,7 @@ class XEP_0198(BasePlugin):
def request_ack(self, e=None):
"""Request an ack from the server."""
log.debug("requesting ack")
req = stanza.RequestAck(self.xmpp)
self.xmpp.send_raw(str(req))
@@ -193,9 +203,7 @@ class XEP_0198(BasePlugin):
enable = stanza.Enable(self.xmpp)
enable['resume'] = self.allow_resume
enable.send()
self.enabled = True
self.handled = 0
self.unacked_queue.clear()
log.debug("enabling SM")
waiter = Waiter('enabled_or_failed',
MatchMany([
@@ -204,11 +212,11 @@ class XEP_0198(BasePlugin):
self.xmpp.register_handler(waiter)
result = await waiter.wait()
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
self.enabled = True
resume = stanza.Resume(self.xmpp)
resume['h'] = self.handled
resume['previd'] = self.sm_id
resume.send()
log.debug("resuming SM")
# Wait for a response before allowing stream feature processing
# to continue. The actual result processing will be done in the
@@ -231,7 +239,10 @@ class XEP_0198(BasePlugin):
self.xmpp.features.add('stream_management')
if stanza['id']:
self.sm_id = stanza['id']
self.enabled_in = True
self.handled = 0
self.xmpp.event('sm_enabled', stanza)
self.xmpp.end_session_on_disconnect = False
def _handle_resumed(self, stanza):
"""Finish resuming a stream by resending unacked stanzas.
@@ -239,10 +250,12 @@ class XEP_0198(BasePlugin):
Raises a :term:`session_resumed` event.
"""
self.xmpp.features.add('stream_management')
self.enabled_in = True
self._handle_ack(stanza)
for id, stanza in self.unacked_queue:
self.xmpp.send(stanza, use_filters=False)
self.xmpp.event('session_resumed', stanza)
self.xmpp.end_session_on_disconnect = False
def _handle_failed(self, stanza):
"""
@@ -252,7 +265,8 @@ class XEP_0198(BasePlugin):
Raises an :term:`sm_failed` event.
"""
self.enabled = False
self.enabled_in = False
self.enabled_out = False
self.unacked_queue.clear()
self.xmpp.event('sm_failed', stanza)
@@ -289,7 +303,7 @@ class XEP_0198(BasePlugin):
def _handle_incoming(self, stanza):
"""Increment the handled counter for each inbound stanza."""
if not self.enabled:
if not self.enabled_in:
return stanza
if isinstance(stanza, (Message, Presence, Iq)):
@@ -299,7 +313,13 @@ class XEP_0198(BasePlugin):
def _handle_outgoing(self, stanza):
"""Store outgoing stanzas in a queue to be acked."""
if not self.enabled:
from slixmpp.plugins.xep_0198 import stanza as st
if isinstance(stanza, (st.Enable, st.Resume)):
self.enabled_out = True
self.unacked_queue.clear()
log.debug("enabling outgoing SM: %s" % stanza)
if not self.enabled_out:
return stanza
if isinstance(stanza, (Message, Presence, Iq)):

View File

@@ -95,7 +95,10 @@ class XEP_0199(BasePlugin):
self.timeout = timeout
self.keepalive = True
handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
handler = lambda event=None: asyncio.ensure_future(
self._keepalive(event),
loop=self.xmpp.loop,
)
self.xmpp.schedule('Ping keepalive',
self.interval,
handler,
@@ -109,9 +112,9 @@ class XEP_0199(BasePlugin):
try:
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
except IqTimeout:
log.debug("Did not receive ping back in time." + \
log.debug("Did not receive ping back in time. " + \
"Requesting Reconnect.")
self.xmpp.reconnect()
self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
else:
log.debug('Keepalive RTT: %s' % rtt)

View File

@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
if not isinstance(value, dt.datetime):
date = xep_0082.parse(value)
date = date.astimezone(tzutc())
value = xep_0082.format_datetime(date)[:-1]
value = xep_0082.format_datetime(date)
self._set_sub_text('utc', value)

View File

@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
}
def plugin_init(self):
"""Start the XEP-0203 plugin."""
"""Start the XEP-0202 plugin."""
if not self.local_time:
def default_local_time(jid):
@@ -50,7 +50,7 @@ class XEP_0202(BasePlugin):
self.xmpp.register_handler(
Callback('Entity Time',
StanzaPath('iq/entity_time'),
StanzaPath('iq@type=get/entity_time'),
self._handle_time_request))
register_stanza_plugin(Iq, stanza.EntityTime)

View File

@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
'pubsub#access_model': 'whitelist'}
def configure(self, node, ifrom=None, callback=None, timeout=None):
"""

View File

@@ -31,11 +31,11 @@ class XEP_0279(BasePlugin):
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
def check_ip(self, ifrom=None, block=True, timeout=None, callback=None,
def check_ip(self, ifrom=None, timeout=None, callback=None,
timeout_callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['from'] = ifrom
iq.enable('ip_check')
return iq.send(block=block, timeout=timeout, callback=callback,
return iq.send(timeout=timeout, callback=callback,
timeout_callback=timeout_callback)

View File

@@ -3,16 +3,18 @@
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of Slixmpp.
See the file LICENSE for copying permissio
See the file LICENSE for copying permission
"""
import logging
import slixmpp
from datetime import datetime
from typing import Any, Dict, Callable, Optional, Awaitable
from slixmpp import JID
from slixmpp.stanza import Message, Iq
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream.handler import Collector
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream.matcher import MatchXMLMask
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.xep_0313 import stanza
@@ -41,8 +43,32 @@ class XEP_0313(BasePlugin):
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
timeout=None, callback=None, iterator=False, rsm=None):
def retrieve(
self,
jid: Optional[JID] = None,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
with_jid: Optional[JID] = None,
ifrom: Optional[JID] = None,
reverse: bool = False,
timeout: int = None,
callback: Callable[[Iq], None] = None,
iterator: bool = False,
rsm: Optional[Dict[str, Any]] = None
) -> Awaitable:
"""
Send a MAM query and retrieve the results.
:param JID jid: Entity holding the MAM records
:param datetime start,end: MAM query temporal boundaries
:param JID with_jid: Filter results on this JID
:param JID ifrom: To change the from address of the query
:param bool reverse: Get the results in reverse order
:param int timeout: IQ timeout
:param func callback: Custom callback for handling results
:param bool iterator: Use RSM and iterate over a paginated query
:param dict rsm: RSM custom options
"""
iq = self.xmpp.Iq()
query_id = iq['id']
@@ -53,35 +79,48 @@ class XEP_0313(BasePlugin):
iq['mam']['start'] = start
iq['mam']['end'] = end
iq['mam']['with'] = with_jid
amount = 10
if rsm:
for key, value in rsm.items():
iq['mam']['rsm'][key] = str(value)
if key == 'max':
amount = value
cb_data = {}
def pre_cb(query):
stanza_mask = self.xmpp.Message()
stanza_mask.xml.remove(stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id'))
del stanza_mask['id']
del stanza_mask['lang']
stanza_mask['from'] = jid
stanza_mask['mam_result']['queryid'] = query_id
xml_mask = str(stanza_mask)
def pre_cb(query: Iq) -> None:
stanza_mask['mam_result']['queryid'] = query['id']
xml_mask = str(stanza_mask)
query['mam']['queryid'] = query['id']
collector = Collector(
'MAM_Results_%s' % query_id,
StanzaPath('message/mam_result@queryid=%s' % query['id']))
MatchXMLMask(xml_mask))
self.xmpp.register_handler(collector)
cb_data['collector'] = collector
def post_cb(result):
def post_cb(result: Iq) -> None:
results = cb_data['collector'].stop()
if result['type'] == 'result':
result['mam']['results'] = results
if iterator:
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results',
recv_interface='mam_fin',
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount,
reverse=reverse, recv_interface='mam_fin',
pre_cb=pre_cb, post_cb=post_cb)
collector = Collector(
'MAM_Results_%s' % query_id,
StanzaPath('message/mam_result@queryid=%s' % query_id))
MatchXMLMask(xml_mask))
self.xmpp.register_handler(collector)
def wrapped_cb(iq):
def wrapped_cb(iq: Iq) -> None:
results = collector.stop()
if iq['type'] == 'result':
iq['mam']['results'] = results
@@ -90,8 +129,15 @@ class XEP_0313(BasePlugin):
return iq.send(timeout=timeout, callback=wrapped_cb)
def get_preferences(self, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
query_id = iq['id']
iq['mam_prefs']['query_id'] = query_id
return iq.send(timeout=timeout, callback=callback)
def set_preferences(self, jid=None, default=None, always=None, never=None,
ifrom=None, block=True, timeout=None, callback=None):
ifrom=None, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = jid
@@ -99,7 +145,7 @@ class XEP_0313(BasePlugin):
iq['mam_prefs']['default'] = default
iq['mam_prefs']['always'] = always
iq['mam_prefs']['never'] = never
return iq.send(block=block, timeout=timeout, callback=callback)
return iq.send(timeout=timeout, callback=callback)
def get_configuration_commands(self, jid, **kwargs):
return self.xmpp['xep_0030'].get_items(

View File

@@ -516,7 +516,7 @@ class Field(ElementBase):
:param value: string
"""
pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
pattern = re.compile(r"^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
if pattern.match(value) is not None:
self.xml.stringIds = value
else:

View File

@@ -114,7 +114,6 @@ class XEP_0332(BasePlugin):
iq['http-req']['data'] = data
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)
@@ -135,7 +134,6 @@ class XEP_0332(BasePlugin):
iq['http-resp']['data'] = data
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)
@@ -153,7 +151,6 @@ class XEP_0332(BasePlugin):
iq['id'] = kwargs["id"]
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)

View File

@@ -0,0 +1,15 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0335 import stanza
from slixmpp.plugins.xep_0335.stanza import JSON_Container
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
register_plugin(XEP_0335)

View File

@@ -0,0 +1,23 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp import Message
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.xep_0335 import JSON_Container
from slixmpp.plugins.xep_0335 import stanza
class XEP_0335(BasePlugin):
name = 'xep_0335'
description = 'XEP-0335: JSON Containers'
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, JSON_Container)

View File

@@ -0,0 +1,28 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import json
from slixmpp.xmlstream import ElementBase
class JSON_Container(ElementBase):
name = 'json'
plugin_attrib = 'json'
namespace = 'urn:xmpp:json:0'
interfaces = {'value'}
def get_value(self):
return json.loads(self.xml.text)
def set_value(self, value):
if not isinstance(value, str):
value = json.dumps(value)
self.xml.text = value
def del_value(self):
self.xml.text = ''

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
import os.path
@@ -31,6 +30,10 @@ class UploadServiceNotFound(FileUploadError):
class FileTooBig(FileUploadError):
pass
class HTTPError(FileUploadError):
def __str__(self):
return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
class XEP_0363(BasePlugin):
''' This plugin only supports Python 3.5+ '''
@@ -68,12 +71,18 @@ class XEP_0363(BasePlugin):
def _handle_request(self, iq):
self.xmpp.event('http_upload_request', iq)
async def find_upload_service(self, timeout=None):
results = await self.xmpp['xep_0030'].get_info_from_domain()
async def find_upload_service(self, domain=None, timeout=None):
results = await self.xmpp['xep_0030'].get_info_from_domain(
domain=domain, timeout=timeout)
candidates = []
for info in results:
for identity in info['disco_info']['identities']:
if identity[0] == 'store' and identity[1] == 'file':
candidates.append(info)
for info in candidates:
for feature in info['disco_info']['features']:
if feature == Request.namespace:
return info
def request_slot(self, jid, filename, size, content_type=None, ifrom=None,
@@ -90,11 +99,12 @@ class XEP_0363(BasePlugin):
timeout_callback=timeout_callback)
async def upload_file(self, filename, size=None, content_type=None, *,
input_file=None, ifrom=None, timeout=None,
input_file=None, ifrom=None, domain=None, timeout=None,
callback=None, timeout_callback=None):
''' Helper function which does all of the uploading process. '''
if self.upload_service is None:
info_iq = await self.find_upload_service(timeout=timeout)
info_iq = await self.find_upload_service(
domain=domain, timeout=timeout)
if info_iq is None:
raise UploadServiceNotFound()
self.upload_service = info_iq['from']
@@ -125,7 +135,8 @@ class XEP_0363(BasePlugin):
basename = os.path.basename(filename)
slot_iq = await self.request_slot(self.upload_service, basename, size,
content_type, ifrom, timeout)
content_type, ifrom, timeout,
timeout_callback=timeout_callback)
slot = slot_iq['http_upload_slot']
headers = {
@@ -141,6 +152,8 @@ class XEP_0363(BasePlugin):
data=input_file,
headers=headers,
timeout=timeout)
if response.status >= 400:
raise HTTPError(response.status, await response.text())
log.info('Response code: %d (%s)', response.status, await response.text())
response.close()
return slot['get']['url']

View File

@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
register_stanza_plugin(Message, Encryption)
def plugin_end(self):
self.xmpp.remove_handler('Chat State')
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
def has_eme(self, msg):
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
def add_eme(self, msg: Message, namespace: str) -> Message:
msg['eme']['name'] = self.mechanisms[namespace]
msg['eme']['namespace'] = namespace
return msg
def replace_body_with_eme(self, msg):
eme = msg['eme']
namespace = eme['namespace']

View File

@@ -187,6 +187,10 @@ class Iq(RootStanza):
future = asyncio.Future()
# Prevents handlers from existing forever.
if timeout is None:
timeout = 120
def callback_success(result):
type_ = result['type']
if type_ == 'result':

View File

@@ -10,6 +10,9 @@ from slixmpp.stanza.rootstanza import RootStanza
from slixmpp.xmlstream import StanzaBase, ET
ORIGIN_NAME = '{urn:xmpp:sid:0}origin-id'
class Message(RootStanza):
"""
@@ -63,6 +66,8 @@ class Message(RootStanza):
if self['id'] == '':
if self.stream is not None and self.stream.use_message_ids:
self['id'] = self.stream.new_id()
else:
del self['origin_id']
def get_type(self):
"""
@@ -76,6 +81,43 @@ class Message(RootStanza):
"""
return self._get_attr('type', 'normal')
def get_id(self):
return self._get_attr('id') or ''
def get_origin_id(self):
sub = self.xml.find(ORIGIN_NAME)
if sub is not None:
return sub.attrib.get('id') or ''
return ''
def _set_ids(self, value) -> None:
if value is None or value == '':
return None
self.xml.attrib['id'] = value
if not self.stream.use_origin_id:
return None
sub = self.xml.find(ORIGIN_NAME)
if sub is not None:
sub.attrib['id'] = value
else:
sub = ET.Element(ORIGIN_NAME)
sub.attrib['id'] = value
self.xml.append(sub)
def set_id(self, value):
return self._set_ids(value)
def set_origin_id(self, value: str):
return self._set_ids(value)
def del_origin_id(self):
sub = self.xml.find(ORIGIN_NAME)
if sub is not None:
self.xml.remove(sub)
def get_parent_thread(self):
"""Return the message thread's parent thread.
@@ -140,6 +182,8 @@ class Message(RootStanza):
new_message['parent_thread'] = self['parent_thread']
del new_message['id']
if self.stream is not None and self.stream.use_message_ids:
new_message['id'] = self.stream.new_id()
if body is not None:
new_message['body'] = body

View File

@@ -90,10 +90,10 @@ class Presence(RootStanza):
def get_type(self):
"""
Return the value of the <presence> stanza's type attribute, or
the value of the <show> element.
the value of the <show> element if valid.
"""
out = self._get_attr('type')
if not out:
if not out and self['show'] in self.showtypes:
out = self['show']
if not out or out is None:
out = 'available'

View File

@@ -222,7 +222,7 @@ class SlixTest(unittest.TestCase):
if Matcher is None:
raise ValueError("Unknown matching method.")
test = Matcher(criteria)
self.failUnless(test.match(stanza),
self.assertTrue(test.match(stanza),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % str(criteria) + \
"Stanza:\n%s" % str(stanza))
@@ -280,7 +280,7 @@ class SlixTest(unittest.TestCase):
debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
result = self.compare(xml, stanza.xml, stanza2.xml)
self.failUnless(result, debug)
self.assertTrue(result, debug)
# ------------------------------------------------------------------
# Methods for simulating stanza streams.
@@ -340,11 +340,19 @@ class SlixTest(unittest.TestCase):
self.xmpp.default_lang = None
self.xmpp.peer_default_lang = None
def new_id():
self.xmpp._id += 1
return str(self.xmpp._id)
self.xmpp._id = 0
self.xmpp.new_id = new_id
# Must have the stream header ready for xmpp.process() to work.
if not header:
header = self.xmpp.stream_header
self.xmpp.data_received(header)
self.wait_for_send_queue()
if skip:
self.xmpp.socket.next_sent()
@@ -361,6 +369,7 @@ class SlixTest(unittest.TestCase):
# Some plugins require messages to have ID values. Set
# this to True in tests related to those plugins.
self.xmpp.use_message_ids = False
self.xmpp.use_presence_ids = False
def make_header(self, sto='',
sfrom='',
@@ -487,7 +496,7 @@ class SlixTest(unittest.TestCase):
recv_xml.clear()
recv_xml.attrib = attrib
self.failUnless(
self.assertTrue(
self.compare(xml, recv_xml),
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
'%s %s' % (xml.tag, xml.attrib),
@@ -543,7 +552,7 @@ class SlixTest(unittest.TestCase):
xml = self.parse_xml(header2)
sent_xml = self.parse_xml(sent_header2)
self.failUnless(
self.assertTrue(
self.compare(xml, sent_xml),
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
header, sent_header))
@@ -557,12 +566,12 @@ class SlixTest(unittest.TestCase):
if sent_data is None:
self.fail("No stanza was sent.")
if method == 'exact':
self.failUnless(self.compare(xml, sent_xml),
self.assertTrue(self.compare(xml, sent_xml),
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
highlight(tostring(xml)), highlight(tostring(sent_xml))))
elif method == 'mask':
matcher = MatchXMLMask(xml)
self.failUnless(matcher.match(sent_xml),
self.assertTrue(matcher.match(sent_xml),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % highlight(tostring(xml)) + \
"Stanza:\n%s" % highlight(tostring(sent_xml)))
@@ -591,6 +600,7 @@ class SlixTest(unittest.TestCase):
'id', 'stanzapath', 'xpath', and 'mask'.
Defaults to the value of self.match_method.
"""
self.wait_for_send_queue()
sent = self.xmpp.socket.next_sent(timeout)
if data is None and sent is None:
return
@@ -607,6 +617,14 @@ class SlixTest(unittest.TestCase):
defaults=defaults,
use_values=use_values)
def wait_for_send_queue(self):
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(self.xmpp.run_filters(), loop=loop)
queue = self.xmpp.waiting_queue
print(queue)
loop.run_until_complete(queue.join())
future.cancel()
def stream_close(self):
"""
Disconnect the dummy XMPP client.

View File

@@ -160,7 +160,7 @@ except:
return _fixed_offset_tzs[offsetmins]
_iso8601_parser = re.compile("""
_iso8601_parser = re.compile(r"""
^
(?P<year> [0-9]{4})?(?P<ymdsep>-?)?
(?P<month>[0-9]{2})?(?P=ymdsep)?

View File

@@ -18,6 +18,9 @@ class Cache:
def store(self, key, value):
raise NotImplementedError
def remove(self, key):
raise NotImplemented
class PerJidCache:
def retrieve_by_jid(self, jid, key):
raise NotImplementedError
@@ -25,6 +28,9 @@ class PerJidCache:
def store_by_jid(self, jid, key, value):
raise NotImplementedError
def remove_by_jid(self, jid, key):
raise NotImplementedError
class MemoryCache(Cache):
def __init__(self):
self.cache = {}
@@ -36,6 +42,11 @@ class MemoryCache(Cache):
self.cache[key] = value
return True
def remove(self, key):
if key in self.cache:
del self.cache[key]
return True
class MemoryPerJidCache(PerJidCache):
def __init__(self):
self.cache = {}
@@ -51,6 +62,12 @@ class MemoryPerJidCache(PerJidCache):
cache[key] = value
return True
def remove_by_jid(self, jid, key):
cache = self.cache.get(jid, None)
if cache is not None and key in cache:
del cache[key]
return True
class FileSystemStorage:
def __init__(self, encode, decode, binary):
self.encode = encode if encode is not None else lambda x: x
@@ -67,7 +84,10 @@ class FileSystemStorage:
log.debug('%s not present in cache', key)
except OSError:
log.debug('Failed to read %s from cache:', key, exc_info=True)
return None
except Exception:
log.debug('Failed to decode %s from cache:', key, exc_info=True)
log.debug('Removing %s entry', key)
self._remove(directory, key)
def _store(self, directory, key, value):
filename = os.path.join(directory, key.replace('/', '_'))
@@ -79,6 +99,17 @@ class FileSystemStorage:
except OSError:
log.debug('Failed to store %s to cache:', key, exc_info=True)
return False
except Exception:
log.debug('Failed to encode %s to cache:', key, exc_info=True)
def _remove(self, directory, key):
filename = os.path.join(directory, key.replace('/', '_'))
try:
os.remove(filename)
except OSError:
log.debug('Failed to remove %s from cache:', key, exc_info=True)
return False
return True
class FileSystemCache(Cache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
@@ -91,6 +122,9 @@ class FileSystemCache(Cache, FileSystemStorage):
def store(self, key, value):
return self._store(self.base_dir, key, value)
def remove(self, key):
return self._remove(self.base_dir, key)
class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
FileSystemStorage.__init__(self, encode, decode, binary)
@@ -103,3 +137,7 @@ class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
def store_by_jid(self, jid, key, value):
directory = os.path.join(self.base_dir, jid)
return self._store(directory, key, value)
def remove_by_jid(self, jid, key):
directory = os.path.join(self.base_dir, jid)
return self._remove(directory, key)

View File

@@ -516,13 +516,13 @@ else:
def setup(self, name):
authzid = self.credentials['authzid']
if not authzid:
authzid = 'xmpp@%s' % self.credentials['service-name']
authzid = 'xmpp@' + self.credentials['service-name'].decode()
_, self.gss = kerberos.authGSSClientInit(authzid)
self.step = 0
def process(self, challenge=b''):
b64_challenge = b64encode(challenge)
b64_challenge = b64encode(challenge).decode('ascii')
try:
if self.step == 0:
result = kerberos.authGSSClientStep(self.gss, b64_challenge)
@@ -536,7 +536,7 @@ else:
kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
resp = kerberos.authGSSClientResponse(self.gss)
kerberos.authGSSClientWrap(self.gss, resp, username)
kerberos.authGSSClientWrap(self.gss, resp, username.decode())
resp = kerberos.authGSSClientResponse(self.gss)
except kerberos.GSSError as e:

View File

@@ -9,5 +9,5 @@
# We don't want to have to import the entire library
# just to get the version info for setup.py
__version__ = '1.4.0'
__version_info__ = (1, 4, 0)
__version__ = '1.5.0'
__version_info__ = (1, 5, 0)

View File

@@ -17,7 +17,7 @@ from __future__ import with_statement, unicode_literals
import copy
import logging
import weakref
from xml.etree import cElementTree as ET
from xml.etree import ElementTree as ET
from slixmpp.xmlstream import JID
from slixmpp.xmlstream.tostring import tostring
@@ -177,8 +177,9 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
if '}' in ns_block:
# Apply the found namespace to following elements
# that do not have namespaces.
namespace = ns_block.split('}')[0]
elements = ns_block.split('}')[1].split('/')
ns_block_split = ns_block.split('}')
namespace = ns_block_split[0]
elements = ns_block_split[1].split('/')
else:
# Apply the stanza's namespace to the following
# elements since no namespace was provided.
@@ -202,7 +203,7 @@ class ElementBase(object):
"""
The core of Slixmpp's stanza XML manipulation and handling is provided
by ElementBase. ElementBase wraps XML cElementTree objects and enables
by ElementBase. ElementBase wraps XML ElementTree objects and enables
access to the XML contents through dictionary syntax, similar in style
to the Ruby XMPP library Blather's stanza implementation.
@@ -386,7 +387,7 @@ class ElementBase(object):
self._index = 0
#: The underlying XML object for the stanza. It is a standard
#: :class:`xml.etree.cElementTree` object.
#: :class:`xml.etree.ElementTree` object.
self.xml = xml
#: An ordered dictionary of plugin stanzas, mapped by their
@@ -1030,14 +1031,19 @@ class ElementBase(object):
if not lang:
lang = default_lang
parent = self.xml
for level, _ in enumerate(path):
# Generate the paths to the target elements and their parent.
element_path = "/".join(path[:len(path) - level])
parent_path = "/".join(path[:len(path) - level - 1])
elements = self.xml.findall(element_path)
parent = self.xml.find(parent_path)
if parent_path == '':
parent_path = None
if parent_path is not None:
parent = self.xml.find(parent_path)
if elements:
if parent is None:
parent = self.xml
@@ -1291,15 +1297,6 @@ class ElementBase(object):
def __bool__(self):
"""Stanza objects should be treated as True in boolean contexts.
Python 3.x version.
"""
return True
def __nonzero__(self):
"""Stanza objects should be treated as True in boolean contexts.
Python 2.x version.
"""
return True
@@ -1382,14 +1379,6 @@ class StanzaBase(ElementBase):
#: The default XMPP client namespace
namespace = 'jabber:client'
#: There is a small set of attributes which apply to all XMPP stanzas:
#: the stanza type, the to and from JIDs, the stanza ID, and, especially
#: in the case of an Iq stanza, a payload.
interfaces = {'type', 'to', 'from', 'id', 'payload'}
#: A basic set of allowed values for the ``'type'`` interface.
types = {'get', 'set', 'error', None, 'unavailable', 'normal', 'chat'}
def __init__(self, stream=None, xml=None, stype=None,
sto=None, sfrom=None, sid=None, parent=None):
self.stream = stream

View File

@@ -45,11 +45,12 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
tag_split = xml.tag.split('}', 1)
tag_name = tag_split[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
tag_xmlns = tag_split[0][1:]
else:
tag_xmlns = ''
@@ -82,8 +83,9 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
if '}' not in attrib:
output.append(' %s="%s"' % (attrib, value))
else:
attrib_ns = attrib.split('}')[0][1:]
attrib = attrib.split('}')[1]
attrib_split = attrib.split('}')
attrib_ns = attrib_split[0][1:]
attrib = attrib_split[1]
if attrib_ns == XML_NS:
output.append(' xml:%s="%s"' % (attrib, value))
elif stream and attrib_ns in stream.namespace_map:
@@ -144,10 +146,7 @@ def escape(text, use_cdata=False):
'"': '&quot;'}
if not use_cdata:
text = list(text)
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return ''.join(text)
return ''.join(escapes.get(c, c) for c in text)
else:
escape_needed = False
for c in text:

View File

@@ -12,6 +12,8 @@
:license: MIT, see LICENSE for more details
"""
from typing import Optional, Set, Callable
import functools
import logging
import socket as Socket
@@ -19,6 +21,8 @@ import ssl
import weakref
import uuid
from asyncio import iscoroutinefunction, wait
import xml.etree.ElementTree as ET
from slixmpp.xmlstream.asyncio import asyncio
@@ -30,6 +34,10 @@ from slixmpp.xmlstream.resolver import resolve, default_resolver
RESPONSE_TIMEOUT = 30
log = logging.getLogger(__name__)
class ContinueQueue(Exception):
"""
Exception raised in the send queue to "continue" from within an inner loop
"""
class NotConnectedError(Exception):
"""
@@ -81,6 +89,8 @@ class XMLStream(asyncio.BaseProtocol):
self.force_starttls = None
self.disable_starttls = None
self.waiting_queue = asyncio.Queue()
# A dict of {name: handle}
self.scheduled_events = {}
@@ -199,11 +209,6 @@ class XMLStream(asyncio.BaseProtocol):
self.__event_handlers = {}
self.__filters = {'in': [], 'out': [], 'out_sync': []}
self._id = 0
#: We use an ID prefix to ensure that all ID values are unique.
self._id_prefix = '%s-' % uuid.uuid4()
# Current connection attempt (Future)
self._current_connection_attempt = None
@@ -215,6 +220,9 @@ class XMLStream(asyncio.BaseProtocol):
#: ``_xmpp-client._tcp`` service.
self.dns_service = None
#: The reason why we are disconnecting from the server
self.disconnect_reason = None
#: An asyncio Future being done when the stream is disconnected.
self.disconnected = asyncio.Future()
@@ -238,12 +246,7 @@ class XMLStream(asyncio.BaseProtocol):
ID values. Using this method ensures that all new ID values
are unique in this stream.
"""
self._id += 1
return self.get_id()
def get_id(self):
"""Return the current unique stream ID in hexadecimal form."""
return "%s%X" % (self._id_prefix, self._id)
return uuid.uuid4().hex
def connect(self, host='', port=0, use_ssl=False,
force_starttls=True, disable_starttls=False):
@@ -268,7 +271,13 @@ class XMLStream(asyncio.BaseProtocol):
localhost
"""
asyncio.ensure_future(
self.run_filters(),
loop=self.loop,
)
self.disconnect_reason = None
self.cancel_connection_attempt()
self.connect_loop_wait = 0
if host and port:
self.address = (host, int(port))
try:
@@ -285,11 +294,18 @@ class XMLStream(asyncio.BaseProtocol):
self.disable_starttls = disable_starttls
self.event("connecting")
self._current_connection_attempt = asyncio.ensure_future(self._connect_routine())
self._current_connection_attempt = asyncio.ensure_future(
self._connect_routine(),
loop=self.loop,
)
async def _connect_routine(self):
self.event_when_connected = "connected"
if self.connect_loop_wait > 0:
self.event('reconnect_delay', self.connect_loop_wait)
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
record = await self.pick_dns_answer(self.default_domain)
if record is not None:
host, address, dns_port = record
@@ -306,7 +322,8 @@ class XMLStream(asyncio.BaseProtocol):
else:
ssl_context = None
await asyncio.sleep(self.connect_loop_wait)
if self._current_connection_attempt is None:
return
try:
await self.loop.create_connection(lambda: self,
self.address[0],
@@ -320,8 +337,13 @@ class XMLStream(asyncio.BaseProtocol):
except OSError as e:
log.debug('Connection failed: %s', e)
self.event("connection_failed", e)
if self._current_connection_attempt is None:
return
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
self._current_connection_attempt = asyncio.ensure_future(self._connect_routine())
self._current_connection_attempt = asyncio.ensure_future(
self._connect_routine(),
loop=self.loop,
)
def process(self, *, forever=True, timeout=None):
"""Process all the available XMPP events (receiving or sending data on the
@@ -336,10 +358,10 @@ class XMLStream(asyncio.BaseProtocol):
else:
self.loop.run_until_complete(self.disconnected)
else:
tasks = [asyncio.sleep(timeout)]
tasks = [asyncio.sleep(timeout, loop=self.loop)]
if not forever:
tasks.append(self.disconnected)
self.loop.run_until_complete(asyncio.wait(tasks))
self.loop.run_until_complete(asyncio.wait(tasks, loop=self.loop))
def init_parser(self):
"""init the XML parser. The parser must always be reset for each new
@@ -358,6 +380,7 @@ class XMLStream(asyncio.BaseProtocol):
"ssl_object",
default=self.transport.get_extra_info("socket")
)
self._current_connection_attempt = None
self.init_parser()
self.send_raw(self.stream_header)
self.dns_answers = None
@@ -392,7 +415,9 @@ class XMLStream(asyncio.BaseProtocol):
if self.xml_depth == 0:
# The stream's root element has closed,
# terminating the stream.
self.end_session_on_disconnect = True
log.debug("End of stream received")
self.disconnect_reason = "End of stream"
self.abort()
elif self.xml_depth == 1:
# A stanza is an XML element that is a direct child of
@@ -414,6 +439,9 @@ class XMLStream(asyncio.BaseProtocol):
self.send(error)
self.disconnect()
def is_connecting(self):
return self._current_connection_attempt is not None
def is_connected(self):
return self.transport is not None
@@ -427,7 +455,7 @@ class XMLStream(asyncio.BaseProtocol):
closure of the TCP connection
"""
log.info("connection_lost: %s", (exception,))
self.event("disconnected")
self.event("disconnected", self.disconnect_reason or exception and exception.strerror)
if self.end_session_on_disconnect:
self.event('session_end')
# All these objects are associated with one TCP connection. Since
@@ -447,24 +475,35 @@ class XMLStream(asyncio.BaseProtocol):
self._current_connection_attempt.cancel()
self._current_connection_attempt = None
def disconnect(self, wait=2.0):
def disconnect(self, wait: float = 2.0, reason: Optional[str] = None) -> None:
"""Close the XML stream and wait for an acknowldgement from the server for
at most `wait` seconds. After the given number of seconds has
passed without a response from the serveur, or when the server
passed without a response from the server, or when the server
successfully responds with a closure of its own stream, abort() is
called. If wait is 0.0, this is almost equivalent to calling abort()
directly.
called. If wait is 0.0, this will call abort() directly without closing
the stream.
Does nothing if we are not connected.
:param wait: Time to wait for a response from the server.
"""
# Compat: docs/getting_started/sendlogout.rst has been promoting
# `disconnect(wait=True)` for ages. This doesn't mean anything to the
# schedule call below. It would fortunately be converted to `1` later
# down the call chain. Praise the implicit casts lord.
if wait == True:
wait = 2.0
self.disconnect_reason = reason
self.cancel_connection_attempt()
if self.transport:
self.send_raw(self.stream_footer)
if wait > 0.0:
self.send_raw(self.stream_footer)
self.schedule('Disconnect wait', wait,
self.abort, repeat=False)
else:
self.event("disconnected", reason)
def abort(self):
"""
@@ -477,14 +516,15 @@ class XMLStream(asyncio.BaseProtocol):
self.event("killed")
self.disconnected.set_result(True)
self.disconnected = asyncio.Future()
self.event("disconnected", self.disconnect_reason)
def reconnect(self, wait=2.0):
def reconnect(self, wait=2.0, reason="Reconnecting"):
"""Calls disconnect(), and once we are disconnected (after the timeout, or
when the server acknowledgement is received), call connect()
"""
log.debug("reconnecting...")
self.disconnect(wait)
self.add_event_handler('disconnected', self.connect, disposable=True)
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
self.disconnect(wait, reason)
def configure_socket(self):
"""Set timeout and other options for self.socket.
@@ -772,7 +812,7 @@ class XMLStream(asyncio.BaseProtocol):
# If the callback is a coroutine, schedule it instead of
# running it directly
if asyncio.iscoroutinefunction(handler_callback):
if iscoroutinefunction(handler_callback):
async def handler_callback_routine(cb):
try:
await cb(data)
@@ -781,7 +821,10 @@ class XMLStream(asyncio.BaseProtocol):
old_exception(e)
else:
self.exception(e)
asyncio.ensure_future(handler_callback_routine(handler_callback))
asyncio.ensure_future(
handler_callback_routine(handler_callback),
loop=self.loop,
)
else:
try:
handler_callback(data)
@@ -868,11 +911,93 @@ class XMLStream(asyncio.BaseProtocol):
"""
return xml
async def _continue_slow_send(
self,
task: asyncio.Task,
already_used: Set[Callable[[ElementBase], Optional[StanzaBase]]]
) -> None:
"""
Used when an item in the send queue has taken too long to process.
This is away from the send queue and can take as much time as needed.
:param asyncio.Task task: the Task wrapping the coroutine
:param set already_used: Filters already used on this outgoing stanza
"""
data = await task
for filter in self.__filters['out']:
if filter in already_used:
continue
if iscoroutinefunction(filter):
data = await task
else:
data = filter(data)
if data is None:
return
if isinstance(data, ElementBase):
for filter in self.__filters['out_sync']:
data = filter(data)
if data is None:
return
str_data = tostring(data.xml, xmlns=self.default_ns,
stream=self, top_level=True)
self.send_raw(str_data)
else:
self.send_raw(data)
async def run_filters(self):
"""
Background loop that processes stanzas to send.
"""
while True:
(data, use_filters) = await self.waiting_queue.get()
try:
if isinstance(data, ElementBase):
if use_filters:
already_run_filters = set()
for filter in self.__filters['out']:
already_run_filters.add(filter)
if iscoroutinefunction(filter):
task = asyncio.create_task(filter(data))
completed, pending = await wait(
{task},
timeout=1,
)
if pending:
asyncio.ensure_future(
self._continue_slow_send(
task,
already_run_filters
)
)
raise Exception("Slow coro, rescheduling")
data = task.result()
else:
data = filter(data)
if data is None:
raise ContinueQueue('Empty stanza')
if isinstance(data, ElementBase):
if use_filters:
for filter in self.__filters['out_sync']:
data = filter(data)
if data is None:
raise ContinueQueue('Empty stanza')
str_data = tostring(data.xml, xmlns=self.default_ns,
stream=self, top_level=True)
self.send_raw(str_data)
else:
self.send_raw(data)
except ContinueQueue as exc:
log.debug('Stanza in send queue not sent: %s', exc)
except Exception:
log.error('Exception raised in send queue:', exc_info=True)
self.waiting_queue.task_done()
def send(self, data, use_filters=True):
"""A wrapper for :meth:`send_raw()` for sending stanza objects.
May optionally block until an expected response is received.
:param data: The :class:`~slixmpp.xmlstream.stanzabase.ElementBase`
stanza to send on the stream.
:param bool use_filters: Indicates if outgoing filters should be
@@ -880,24 +1005,7 @@ class XMLStream(asyncio.BaseProtocol):
filters is useful when resending stanzas.
Defaults to ``True``.
"""
if isinstance(data, ElementBase):
if use_filters:
for filter in self.__filters['out']:
data = filter(data)
if data is None:
return
if isinstance(data, ElementBase):
if use_filters:
for filter in self.__filters['out_sync']:
data = filter(data)
if data is None:
return
str_data = tostring(data.xml, xmlns=self.default_ns,
stream=self, top_level=True)
self.send_raw(str_data)
else:
self.send_raw(data)
self.waiting_queue.put_nowait((data, use_filters))
def send_xml(self, data):
"""Send an XML object on the stream
@@ -995,4 +1103,3 @@ class XMLStream(asyncio.BaseProtocol):
:param exception: An unhandled exception object.
"""
pass

74
tests/test_cache.py Normal file
View File

@@ -0,0 +1,74 @@
import unittest
from slixmpp.test import SlixTest
from slixmpp.util import (
MemoryCache, MemoryPerJidCache,
FileSystemCache, FileSystemPerJidCache
)
from tempfile import TemporaryDirectory
class TestCacheClass(SlixTest):
def testMemoryCache(self):
cache = MemoryCache()
cache.store("test", "test_value")
self.assertEqual(cache.retrieve("test"), "test_value")
self.assertEqual(cache.retrieve("test2"), None)
cache.remove("test")
self.assertEqual(cache.retrieve("test"), None)
def testMemoryPerJidcache(self):
cache = MemoryPerJidCache()
cache.store_by_jid("test@example.com", "test", "test_value")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
"test_value"
)
cache.remove_by_jid("test@example.com", "test")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
None
)
def testFileSystemCache(self):
def failing_decode(value):
if value == "failme":
raise Exception("you failed")
return value
with TemporaryDirectory() as tmpdir:
cache = FileSystemCache(tmpdir, "test", decode=failing_decode)
cache.store("test", "test_value")
cache.store("test2", "failme")
self.assertEqual(
cache.retrieve("test"),
"test_value"
)
cache.remove("test")
self.assertEqual(
cache.retrieve("test"),
None
)
self.assertEqual(
cache.retrieve("test2"),
None
)
def testFileSystemPerJidCache(self):
with TemporaryDirectory() as tmpdir:
cache = FileSystemPerJidCache(tmpdir, "test")
cache.store_by_jid("test@example.com", "test", "test_value")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
"test_value"
)
cache.remove_by_jid("test@example.com", "test")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
None
)
suite = unittest.TestLoader().loadTestsFromTestCase(TestCacheClass)

View File

@@ -23,7 +23,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event")
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True, True], msg)
self.assertTrue(happened == [True, True], msg)
def testDelEvent(self):
"""Test handler working, then deleted and not triggered"""
@@ -41,7 +41,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
self.assertTrue(happened == [True], msg % happened)
def testAddDelAddEvent(self):
"""Test adding, then removing, then adding an event handler."""
@@ -61,7 +61,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True, True], msg % happened)
self.assertTrue(happened == [True, True], msg % happened)
def testDisposableEvent(self):
"""Test disposable handler working, then not being triggered again."""
@@ -78,7 +78,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
self.assertTrue(happened == [True], msg % happened)
suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)

View File

@@ -16,11 +16,11 @@ class TestOverall(unittest.TestCase):
"""Testing all modules by compiling them"""
src = '.%sslixmpp' % os.sep
rx = re.compile('/[.]svn|.*26.*')
self.failUnless(compileall.compile_dir(src, rx=rx, quiet=True))
self.assertTrue(compileall.compile_dir(src, rx=rx, quiet=True))
def testTabNanny(self):
"""Testing that indentation is consistent"""
self.failIf(tabnanny.check('..%sslixmpp' % os.sep))
self.assertFalse(tabnanny.check('..%sslixmpp' % os.sep))
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)

View File

@@ -9,37 +9,37 @@ class TestStanzaBase(SlixTest):
"""Test the 'to' interface of StanzaBase."""
stanza = StanzaBase()
stanza['to'] = 'user@example.com'
self.failUnless(str(stanza['to']) == 'user@example.com',
self.assertTrue(str(stanza['to']) == 'user@example.com',
"Setting and retrieving stanza 'to' attribute did not work.")
def testFrom(self):
"""Test the 'from' interface of StanzaBase."""
stanza = StanzaBase()
stanza['from'] = 'user@example.com'
self.failUnless(str(stanza['from']) == 'user@example.com',
self.assertTrue(str(stanza['from']) == 'user@example.com',
"Setting and retrieving stanza 'from' attribute did not work.")
def testPayload(self):
"""Test the 'payload' interface of StanzaBase."""
stanza = StanzaBase()
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Empty stanza does not have an empty payload.")
stanza['payload'] = ET.Element("{foo}foo")
self.failUnless(len(stanza['payload']) == 1,
self.assertTrue(len(stanza['payload']) == 1,
"Stanza contents and payload do not match.")
stanza['payload'] = ET.Element('{bar}bar')
self.failUnless(len(stanza['payload']) == 2,
self.assertTrue(len(stanza['payload']) == 2,
"Stanza payload was not appended.")
del stanza['payload']
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza payload not cleared after deletion.")
stanza['payload'] = [ET.Element('{foo}foo'),
ET.Element('{bar}bar')]
self.failUnless(len(stanza['payload']) == 2,
self.assertTrue(len(stanza['payload']) == 2,
"Adding multiple elements to stanza's payload did not work.")
def testClear(self):
@@ -49,9 +49,9 @@ class TestStanzaBase(SlixTest):
stanza['payload'] = ET.Element("{foo}foo")
stanza.clear()
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza payload was not cleared after calling .clear()")
self.failUnless(str(stanza['to']) == "user@example.com",
self.assertTrue(str(stanza['to']) == "user@example.com",
"Stanza attributes were not preserved after calling .clear()")
def testReply(self):
@@ -63,18 +63,10 @@ class TestStanzaBase(SlixTest):
stanza = stanza.reply()
self.failUnless(str(stanza['to'] == "sender@example.com"),
self.assertTrue(str(stanza['to'] == "sender@example.com"),
"Stanza reply did not change 'to' attribute.")
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza reply did not empty stanza payload.")
def testError(self):
"""Test marking a stanza as an error."""
stanza = StanzaBase()
stanza['type'] = 'get'
stanza.error()
self.failUnless(stanza['type'] == 'error',
"Stanza type is not 'error' after calling error()")
suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase)

View File

@@ -17,7 +17,7 @@ class TestElementBase(SlixTest):
"{%s}bar" % ns,
"{abc}baz",
"{%s}more" % ns])
self.failUnless(expected == result,
self.assertTrue(expected == result,
"Incorrect namespace fixing result: %s" % str(result))
@@ -80,7 +80,7 @@ class TestElementBase(SlixTest):
'lang': '',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
self.assertTrue(values == expected,
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
@@ -170,13 +170,13 @@ class TestElementBase(SlixTest):
'meh': ''}
for interface, value in expected.items():
result = stanza[interface]
self.failUnless(result == value,
self.assertTrue(result == value,
"Incorrect stanza interface access result: %s" % result)
# Test plugin interfaces
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
self.assertTrue(isinstance(stanza['foobar'], TestStanzaPlugin),
"Incorrect plugin object result.")
self.failUnless(stanza['foobar']['fizz'] == 'c',
self.assertTrue(stanza['foobar']['fizz'] == 'c',
"Incorrect plugin subvalue result.")
def testSetItem(self):
@@ -269,7 +269,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" />
""")
self.failUnless(stanza._get_attr('bar') == '',
self.assertTrue(stanza._get_attr('bar') == '',
"Incorrect value returned for an unset XML attribute.")
stanza._set_attr('bar', 'a')
@@ -279,7 +279,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" bar="a" baz="b" />
""")
self.failUnless(stanza._get_attr('bar') == 'a',
self.assertTrue(stanza._get_attr('bar') == 'a',
"Retrieved XML attribute value is incorrect.")
stanza._set_attr('bar', None)
@@ -289,7 +289,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" />
""")
self.failUnless(stanza._get_attr('bar', 'c') == 'c',
self.assertTrue(stanza._get_attr('bar', 'c') == 'c',
"Incorrect default value returned for an unset XML attribute.")
def testGetSubText(self):
@@ -311,7 +311,7 @@ class TestElementBase(SlixTest):
return self._get_sub_text("wrapper/bar", default="not found")
stanza = TestStanza()
self.failUnless(stanza['bar'] == 'not found',
self.assertTrue(stanza['bar'] == 'not found',
"Default _get_sub_text value incorrect.")
stanza['bar'] = 'found'
@@ -322,7 +322,7 @@ class TestElementBase(SlixTest):
</wrapper>
</foo>
""")
self.failUnless(stanza['bar'] == 'found',
self.assertTrue(stanza['bar'] == 'found',
"_get_sub_text value incorrect: %s." % stanza['bar'])
def testSubElement(self):
@@ -481,45 +481,45 @@ class TestElementBase(SlixTest):
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
self.failUnless(stanza.match("foo"),
self.assertTrue(stanza.match("foo"),
"Stanza did not match its own tag name.")
self.failUnless(stanza.match("{foo}foo"),
self.assertTrue(stanza.match("{foo}foo"),
"Stanza did not match its own namespaced name.")
stanza['bar'] = 'a'
self.failUnless(stanza.match("foo@bar=a"),
self.assertTrue(stanza.match("foo@bar=a"),
"Stanza did not match its own name with attribute value check.")
stanza['baz'] = 'b'
self.failUnless(stanza.match("foo@bar=a@baz=b"),
self.assertTrue(stanza.match("foo@bar=a@baz=b"),
"Stanza did not match its own name with multiple attributes.")
stanza['qux'] = 'c'
self.failUnless(stanza.match("foo/qux"),
self.assertTrue(stanza.match("foo/qux"),
"Stanza did not match with subelements.")
stanza['qux'] = ''
self.failUnless(stanza.match("foo/qux") == False,
self.assertTrue(stanza.match("foo/qux") == False,
"Stanza matched missing subinterface element.")
self.failUnless(stanza.match("foo/bar") == False,
self.assertTrue(stanza.match("foo/bar") == False,
"Stanza matched nonexistent element.")
stanza['plugin']['attrib'] = 'c'
self.failUnless(stanza.match("foo/plugin@attrib=c"),
self.assertTrue(stanza.match("foo/plugin@attrib=c"),
"Stanza did not match with plugin and attribute.")
self.failUnless(stanza.match("foo/{http://test/slash/bar}plugin"),
self.assertTrue(stanza.match("foo/{http://test/slash/bar}plugin"),
"Stanza did not match with namespaced plugin.")
substanza = TestSubStanza()
substanza['attrib'] = 'd'
stanza.append(substanza)
self.failUnless(stanza.match("foo/sub@attrib=d"),
self.assertTrue(stanza.match("foo/sub@attrib=d"),
"Stanza did not match with substanzas and attribute.")
self.failUnless(stanza.match("foo/{baz}sub"),
self.assertTrue(stanza.match("foo/{baz}sub"),
"Stanza did not match with namespaced substanza.")
def testComparisons(self):
@@ -533,19 +533,19 @@ class TestElementBase(SlixTest):
stanza1 = TestStanza()
stanza1['bar'] = 'a'
self.failUnless(stanza1,
self.assertTrue(stanza1,
"Stanza object does not evaluate to True")
stanza2 = TestStanza()
stanza2['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
self.assertTrue(stanza1 != stanza2,
"Different stanza objects incorrectly compared equal.")
stanza1['baz'] = 'b'
stanza2['bar'] = 'a'
self.failUnless(stanza1 == stanza2,
self.assertTrue(stanza1 == stanza2,
"Equal stanzas incorrectly compared inequal.")
def testKeys(self):
@@ -561,12 +561,12 @@ class TestElementBase(SlixTest):
stanza = TestStanza()
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz'},
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz'},
"Returned set of interface keys does not match expected.")
stanza.enable('qux')
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
"Incorrect set of interface and plugin keys.")
def testGet(self):
@@ -580,10 +580,10 @@ class TestElementBase(SlixTest):
stanza = TestStanza()
stanza['bar'] = 'a'
self.failUnless(stanza.get('bar') == 'a',
self.assertTrue(stanza.get('bar') == 'a',
"Incorrect value returned by stanza.get")
self.failUnless(stanza.get('baz', 'b') == 'b',
self.assertTrue(stanza.get('baz', 'b') == 'b',
"Incorrect default value returned by stanza.get")
def testSubStanzas(self):
@@ -608,7 +608,7 @@ class TestElementBase(SlixTest):
substanza2['qux'] = 'b'
# Test appending substanzas
self.failUnless(len(stanza) == 0,
self.assertTrue(len(stanza) == 0,
"Incorrect empty stanza size.")
stanza.append(substanza1)
@@ -617,7 +617,7 @@ class TestElementBase(SlixTest):
<foobar qux="a" />
</foo>
""", use_values=False)
self.failUnless(len(stanza) == 1,
self.assertTrue(len(stanza) == 1,
"Incorrect stanza size with 1 substanza.")
stanza.append(substanza2)
@@ -627,7 +627,7 @@ class TestElementBase(SlixTest):
<foobar qux="b" />
</foo>
""", use_values=False)
self.failUnless(len(stanza) == 2,
self.assertTrue(len(stanza) == 2,
"Incorrect stanza size with 2 substanzas.")
# Test popping substanzas
@@ -643,7 +643,7 @@ class TestElementBase(SlixTest):
results = []
for substanza in stanza:
results.append(substanza['qux'])
self.failUnless(results == ['b', 'a'],
self.assertTrue(results == ['b', 'a'],
"Iteration over substanzas failed: %s." % str(results))
def testCopy(self):
@@ -659,11 +659,11 @@ class TestElementBase(SlixTest):
stanza2 = stanza1.__copy__()
self.failUnless(stanza1 == stanza2,
self.assertTrue(stanza1 == stanza2,
"Copied stanzas are not equal to each other.")
stanza1['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
self.assertTrue(stanza1 != stanza2,
"Divergent stanza copies incorrectly compared equal.")
def testExtension(self):
@@ -701,7 +701,7 @@ class TestElementBase(SlixTest):
</foo>
""")
self.failUnless(stanza['extended'] == 'testing',
self.assertTrue(stanza['extended'] == 'testing',
"Could not retrieve stanza extension value.")
del stanza['extended']

View File

@@ -34,7 +34,7 @@ class TestErrorStanzas(SlixTest):
</message>
""")
self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
self.assertTrue(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
msg['error']['condition'] = 'resource-constraint'

View File

@@ -62,30 +62,30 @@ class TestGmail(SlixTest):
iq = self.Iq(xml=xml)
mailbox = iq['mailbox']
self.failUnless(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.failUnless(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.failUnless(mailbox['matched'] == '95', "total-matched incorrect")
self.failUnless(mailbox['estimate'] == False, "total-estimate incorrect")
self.failUnless(len(mailbox['threads']) == 1, "could not extract message threads")
self.assertTrue(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.assertTrue(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.assertTrue(mailbox['matched'] == '95', "total-matched incorrect")
self.assertTrue(mailbox['estimate'] == False, "total-estimate incorrect")
self.assertTrue(len(mailbox['threads']) == 1, "could not extract message threads")
thread = mailbox['threads'][0]
self.failUnless(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.failUnless(thread['participation'] == '1', "thread participation incorrect")
self.failUnless(thread['messages'] == '28', "thread message count incorrect")
self.failUnless(thread['date'] == '1118012394209', "thread date doesn't match")
self.failUnless(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.failUnless(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.failUnless(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.failUnless(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.failUnless(len(thread['senders']) == 3, "could not extract senders")
self.assertTrue(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.assertTrue(thread['participation'] == '1', "thread participation incorrect")
self.assertTrue(thread['messages'] == '28', "thread message count incorrect")
self.assertTrue(thread['date'] == '1118012394209', "thread date doesn't match")
self.assertTrue(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.assertTrue(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.assertTrue(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.assertTrue(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.assertTrue(len(thread['senders']) == 3, "could not extract senders")
sender1 = thread['senders'][0]
self.failUnless(sender1['name'] == 'Me', "sender name doesn't match")
self.failUnless(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.failUnless(sender1['originator'] == True, "sender originator incorrect")
self.failUnless(sender1['unread'] == False, "sender unread incorrectly True")
self.assertTrue(sender1['name'] == 'Me', "sender name doesn't match")
self.assertTrue(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.assertTrue(sender1['originator'] == True, "sender originator incorrect")
self.assertTrue(sender1['unread'] == False, "sender unread incorrectly True")
sender2 = thread['senders'][2]
self.failUnless(sender2['unread'] == True, "sender unread incorrectly False")
self.assertTrue(sender2['unread'] == True, "sender unread incorrectly False")
suite = unittest.TestLoader().loadTestsFromTestCase(TestGmail)

View File

@@ -70,7 +70,7 @@ class TestIqStanzas(SlixTest):
</iq>
""")
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
self.assertTrue(iq['query'] == 'query_ns2', "Query namespace doesn't match")
del iq['query']
self.check(iq, """

View File

@@ -18,7 +18,7 @@ class TestMessageStanzas(SlixTest):
msg['type'] = 'groupchat'
msg['body'] = "this is a message"
msg = msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
self.assertTrue(str(msg['to']) == 'room@someservice.someserver.tld')
def testHTMLPlugin(self):
"Test message/html/body stanza"

View File

@@ -15,7 +15,7 @@ class TestPresenceStanzas(SlixTest):
p = self.Presence()
p['type'] = 'available'
self.check(p, "<presence />")
self.failUnless(p['type'] == 'available',
self.assertTrue(p['type'] == 'available',
"Incorrect presence['type'] for type 'available': %s" % p['type'])
for showtype in ['away', 'chat', 'dnd', 'xa']:
@@ -23,7 +23,7 @@ class TestPresenceStanzas(SlixTest):
self.check(p, """
<presence><show>%s</show></presence>
""" % showtype)
self.failUnless(p['type'] == showtype,
self.assertTrue(p['type'] == showtype,
"Incorrect presence['type'] for type '%s'" % showtype)
p['type'] = None
@@ -47,10 +47,10 @@ class TestPresenceStanzas(SlixTest):
c.add_event_handler("changed_status", handlechangedpresence)
c._handle_presence(p)
self.failUnless(happened == [],
self.assertTrue(happened == [],
"changed_status event triggered for extra unavailable presence")
roster = c.roster['crap@wherever']
self.failUnless(roster['bill@chadmore.com'].resources == {},
self.assertTrue(roster['bill@chadmore.com'].resources == {},
"Roster updated for superfulous unavailable presence")
def testNickPlugin(self):

View File

@@ -61,7 +61,7 @@ class TestRosterStanzas(SlixTest):
debug = "Roster items don't match after retrieval."
debug += "\nReturned: %s" % str(iq['roster']['items'])
debug += "\nExpected: %s" % str(expected)
self.failUnless(iq['roster']['items'] == expected, debug)
self.assertTrue(iq['roster']['items'] == expected, debug)
def testDelItems(self):
"""Test clearing items from a roster stanza."""

View File

@@ -258,7 +258,7 @@ class TestDisco(SlixTest):
('client', 'pc', 'no', None),
('client', 'pc', 'en', None),
('client', 'pc', 'fr', None)}
self.failUnless(iq['disco_info']['identities'] == expected,
self.assertTrue(iq['disco_info']['identities'] == expected,
"Identities do not match:\n%s\n%s" % (
expected,
iq['disco_info']['identities']))
@@ -276,7 +276,7 @@ class TestDisco(SlixTest):
expected = {('client', 'pc', 'no', None)}
result = iq['disco_info'].get_identities(lang='no')
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Identities do not match:\n%s\n%s" % (
expected, result))
@@ -337,7 +337,7 @@ class TestDisco(SlixTest):
iq['disco_info'].add_feature('baz')
expected = {'foo', 'bar', 'baz'}
self.failUnless(iq['disco_info']['features'] == expected,
self.assertTrue(iq['disco_info']['features'] == expected,
"Features do not match:\n%s\n%s" % (
expected,
iq['disco_info']['features']))
@@ -475,7 +475,7 @@ class TestDisco(SlixTest):
expected = {('user@localhost', None, None),
('user@localhost', 'foo', None),
('test@localhost', 'bar', 'Tester')}
self.failUnless(iq['disco_items']['items'] == expected,
self.assertTrue(iq['disco_items']['items'] == expected,
"Items do not match:\n%s\n%s" % (
expected,
iq['disco_items']['items']))

View File

@@ -17,13 +17,13 @@ class TestAdHocCommandStanzas(SlixTest):
iq['command']['node'] = 'foo'
iq['command']['action'] = 'execute'
self.failUnless(iq['command']['action'] == 'execute')
self.assertTrue(iq['command']['action'] == 'execute')
iq['command']['action'] = 'complete'
self.failUnless(iq['command']['action'] == 'complete')
self.assertTrue(iq['command']['action'] == 'complete')
iq['command']['action'] = 'cancel'
self.failUnless(iq['command']['action'] == 'cancel')
self.assertTrue(iq['command']['action'] == 'cancel')
def testSetActions(self):
"""Test setting next actions in a command stanza."""
@@ -98,7 +98,7 @@ class TestAdHocCommandStanzas(SlixTest):
('error', "I can't let you do that")]
iq['command']['notes'] = notes
self.failUnless(iq['command']['notes'] == notes,
self.assertTrue(iq['command']['notes'] == notes,
"Notes don't match: %s %s" % (notes, iq['command']['notes']))
self.check(iq, """

View File

@@ -24,7 +24,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = '10'
self.failUnless(s['first_index'] == expected)
self.assertTrue(s['first_index'] == expected)
def testDelFirstIndex(self):
xml_string = """
@@ -57,7 +57,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = True
self.failUnless(s['before'] == expected)
self.assertTrue(s['before'] == expected)
def testGetBefore(self):
xml_string = """
@@ -89,7 +89,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = 'id'
self.failUnless(s['before'] == expected)
self.assertTrue(s['before'] == expected)
def testGetBeforeVal(self):
xml_string = """

View File

@@ -112,7 +112,7 @@ class TestHandlers(SlixTest):
# Check that the waiter is no longer registered
waiter_exists = self.xmpp.remove_handler('IqWait_test2')
self.failUnless(waiter_exists == False,
self.assertTrue(waiter_exists == False,
"Waiter handler was not removed.")
def testIqCallback(self):
@@ -145,7 +145,7 @@ class TestHandlers(SlixTest):
</iq>
""")
self.failUnless(events == ['foo'],
self.assertTrue(events == ['foo'],
"Iq callback was not executed: %s" % events)
def testMultipleHandlersForStanza(self):

View File

@@ -52,7 +52,7 @@ class TestStreamRoster(SlixTest):
pending_out=True,
groups=['Friends', 'Examples'])
self.failUnless(len(roster_updates) == 1,
self.assertTrue(len(roster_updates) == 1,
"Wrong number of roster_update events fired: %s (should be 1)" % len(roster_updates))
def testRosterSet(self):
@@ -89,7 +89,7 @@ class TestStreamRoster(SlixTest):
groups=['Friends', 'Examples'])
self.failUnless('roster_update' in events,
self.assertTrue('roster_update' in events,
"Roster updated event not triggered: %s" % events)
def testRosterPushRemove(self):
@@ -188,7 +188,7 @@ class TestStreamRoster(SlixTest):
</iq>
""")
self.failUnless(events == ['roster_callback'],
self.assertTrue(events == ['roster_callback'],
"Roster timeout event not triggered: %s." % events)
def testRosterUnicode(self):
@@ -209,7 +209,7 @@ class TestStreamRoster(SlixTest):
groups=['Unicode'])
jids = list(self.xmpp.client_roster.keys())
self.failUnless(jids == ['andré@foo'],
self.assertTrue(jids == ['andré@foo'],
"Too many roster entries found: %s" % jids)
self.recv("""
@@ -223,7 +223,7 @@ class TestStreamRoster(SlixTest):
expected = {'bar': {'status':'Testing',
'show':'away',
'priority':0}}
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Unexpected roster values: %s" % result)
def testSendLastPresence(self):

View File

@@ -79,8 +79,7 @@ class TestInBandByteStreams(SlixTest):
self.assertEqual(events, {'ibb_stream_start', 'callback'})
@asyncio.coroutine
def testSendData(self):
async def testSendData(self):
"""Test sending data over an in-band bytestream."""
streams = []
@@ -117,7 +116,7 @@ class TestInBandByteStreams(SlixTest):
# Test sending data out
yield from stream.send("Testing")
await stream.send("Testing")
self.send("""
<iq type="set" id="2"

View File

@@ -626,7 +626,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo', 'bar', 'baz'],
self.assertTrue(results == ['foo', 'bar', 'baz'],
'Incomplete command workflow: %s' % results)
def testClientAPICancel(self):
@@ -689,7 +689,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo', 'bar'],
self.assertTrue(results == ['foo', 'bar'],
'Incomplete command workflow: %s' % results)
def testClientAPIError(self):
@@ -727,7 +727,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo'],
self.assertTrue(results == ['foo'],
'Incomplete command workflow: %s' % results)
def testClientAPIErrorStrippedResponse(self):
@@ -762,7 +762,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo'],
self.assertTrue(results == ['foo'],
'Incomplete command workflow: %s' % results)

View File

@@ -50,7 +50,7 @@ class TestStreamChatStates(SlixTest):
""")
expected = ['active', 'inactive', 'paused', 'composing', 'gone']
self.failUnless(results == expected,
self.assertTrue(results == expected,
"Chat state event not handled: %s" % results)

View File

@@ -35,7 +35,7 @@ class TestStreamDirectInvite(SlixTest):
</message>
""")
self.failUnless(events == [True],
self.assertTrue(events == [True],
"Event not raised: %s" % events)
def testSentDirectInvite(self):

View File

@@ -4,6 +4,7 @@ import sys
import datetime
import time
import threading
import unittest
import re
from slixmpp.test import *
@@ -11,6 +12,7 @@ from slixmpp.xmlstream import ElementBase
from slixmpp.plugins.xep_0323.device import Device
@unittest.skip('')
class TestStreamSensorData(SlixTest):
"""
@@ -456,7 +458,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnless(results == ["rejected"],
self.assertTrue(results == ["rejected"],
"Rejected callback was not properly executed")
def testRequestAcceptedAPI(self):
@@ -493,7 +495,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnless(results == ["accepted"],
self.assertTrue(results == ["accepted"],
"Accepted callback was not properly executed")
def testRequestFieldsAPI(self):
@@ -561,19 +563,19 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["accepted","fields","done"])
self.assertEqual(results, ["accepted","fields","done"])
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
#self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
#self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testServiceDiscoveryClient(self):
self.stream_start(mode='client',
@@ -675,16 +677,16 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["accepted","failure"]);
self.assertEqual(results, ["accepted","failure"]);
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
self.assertEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
# self.assertIn("error_msg", callback_data);
self.assertTrue("error_msg" in callback_data)
self.failUnlessEqual(callback_data["error_msg"], "Timeout.")
self.assertEqual(callback_data["error_msg"], "Timeout.")
def testDelayedRequest(self):
self.stream_start(mode='component',
@@ -1071,19 +1073,19 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["queued","started","fields","done"]);
self.assertEqual(results, ["queued","started","fields","done"]);
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
# self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
# self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testRequestFieldsCancelAPI(self):
@@ -1139,7 +1141,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnlessEqual(results, ["accepted","cancelled"])
self.assertEqual(results, ["accepted","cancelled"])
def testDelayedRequestCancel(self):
self.stream_start(mode='component',

View File

@@ -25,7 +25,7 @@ class TestToString(SlixTest):
else:
xml=original
result = tostring(xml, **kwargs)
self.failUnless(result == expected, "%s: %s" % (message, result))
self.assertTrue(result == expected, "%s: %s" % (message, result))
def testXMLEscape(self):
"""Test escaping XML special characters."""
@@ -34,7 +34,7 @@ class TestToString(SlixTest):
desired = """&lt;foo bar=&quot;baz&quot;&gt;&apos;Hi"""
desired += """ &amp; welcome!&apos;&lt;/foo&gt;"""
self.failUnless(escaped == desired,
self.assertTrue(escaped == desired,
"XML escaping did not work: %s." % escaped)
def testEmptyElement(self):
@@ -99,7 +99,7 @@ class TestToString(SlixTest):
msg['body'] = utf8_message.decode('utf-8')
expected = '<message><body>\xe0\xb2\xa0_\xe0\xb2\xa0</body></message>'
result = msg.__str__()
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Stanza Unicode handling is incorrect: %s" % result)
def testXMLLang(self):
@@ -112,7 +112,7 @@ class TestToString(SlixTest):
expected = '<message xml:lang="no" />'
result = msg.__str__()
self.failUnless(expected == result,
self.assertTrue(expected == result,
"Serialization with xml:lang failed: %s" % result)