Merge branch 'muc-improvements-reloaded' into 'master'

Muc improvements reloaded

See merge request poezio/slixmpp!123
This commit is contained in:
mathieui 2021-02-08 23:11:08 +01:00
commit 1c4e06d510
7 changed files with 657 additions and 298 deletions

View File

@ -5,6 +5,7 @@ XEP-0045: Multi-User Chat
.. module:: slixmpp.plugins.xep_0045 .. module:: slixmpp.plugins.xep_0045
.. autoclass:: XEP_0045 .. autoclass:: XEP_0045
:member-order: bysource
:members: :members:
:exclude-members: session_bind, plugin_init, plugin_end :exclude-members: session_bind, plugin_init, plugin_end

View File

@ -99,8 +99,8 @@ of an interface defined by the parent.
.. seealso:: .. seealso::
- :ref:`create-stanza-plugins` - :ref:`create-stanza-plugins`
- :ref:`create-extension-plugins` - :ref:`is_extension`
- :ref:`override-parent-interfaces` - :ref:`overrides`
Registering Stanza Plugins Registering Stanza Plugins

View File

@ -51,7 +51,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Slixmpp' project = u'Slixmpp'
year = datetime.datetime.now().year year = datetime.datetime.now().year
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year) copyright = u'{}, Mathieu Pasquet, Maxime Buquet, Emmanuel Gil Peyrot, Florent Le Coz'.format(year)
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the

View File

@ -1,406 +1,581 @@
Event Index Event Index
=========== ===========
Slixmpp relies on events and event handlers to act on received data from
the server. Some of those events come from the very base of Slixmpp such
as :class:`~.BaseXMPP` or :class:`~.XMLStream`, while most of them are
emitted from plugins which add their own listeners.
There are often multiple events running for a single stanza, with
different levels of granularity, so code must take care of not
processing the same stanza twice.
.. glossary:: .. glossary::
:sorted: :sorted:
connected connected
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` - **Source:** :py:class:`~.xmlstream.XMLstream`
Signal that a connection has been made with the XMPP server, but a session Signal that a connection has been made with the XMPP server, but a session
has not yet been established. has not yet been established.
connection_failed connection_failed
- **Data:** ``{}`` or ``Failure Stanza`` if available - **Data:** ``{}`` or ``Failure Stanza`` if available
- **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` - **Source:** :py:class:`~.xmlstream.XMLstream`
Signal that a connection can not be established after number of attempts. Signal that a connection can not be established after number of attempts.
changed_status changed_status
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.roster.item.RosterItem` - **Source:** :py:class:`~.roster.item.RosterItem`
Triggered when a presence stanza is received from a JID with a show type Triggered when a presence stanza is received from a JID with a show type
different than the last presence stanza from the same JID. different than the last presence stanza from the same JID.
changed_subscription changed_subscription
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
Triggered whenever a presence stanza with a type of ``subscribe``, Triggered whenever a presence stanza with a type of ``subscribe``,
``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received. ``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received.
Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe`` Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe``
are set to ``True`` or ``False``, and not ``None``, then Slixmpp will are set to ``True`` or ``False``, and not ``None``, then will
either accept or reject all subscription requests before your event handlers either accept or reject all subscription requests before your event handlers
are called. Set these values to ``None`` if you wish to make more complex are called. Set these values to ``None`` if you wish to make more complex
subscription decisions. subscription decisions.
chatstate_active chatstate_active
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0085.xep_0085` - **Source:** :py:class:`~.XEP_0085`
When a message containing an ``<active/>`` chatstate is received.
chatstate_composing chatstate_composing
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0085.xep_0085` - **Source:** :py:class:`~.XEP_0085`
When a message containing a ``<composing/>`` chatstate is received.
chatstate_gone chatstate_gone
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0085.xep_0085` - **Source:** :py:class:`~.XEP_0085`
When a message containing a ``<gone/>`` chatstate is received.
chatstate_inactive chatstate_inactive
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0085.xep_0085` - **Source:** :py:class:`~.XEP_0085`
When a message containing an ``<inactive/>`` chatstate is received.
chatstate_paused chatstate_paused
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0085.xep_0085` - **Source:** :py:class:`~.XEP_0085`
When a message containing a ``<paused/>`` chatstate is received.
disco_info disco_info
- **Data:** :py:class:`~slixmpp.plugins.xep_0030.stanza.DiscoInfo` - **Data:** :py:class:`~.DiscoInfo`
- **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` - **Source:** :py:class:`~.disco.XEP_0030`
Triggered whenever a ``disco#info`` result stanza is received. Triggered whenever a ``disco#info`` result stanza is received.
disco_items disco_items
- **Data:** :py:class:`~slixmpp.plugins.xep_0030.stanza.DiscoItems` - **Data:** :py:class:`~.DiscoItems`
- **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` - **Source:** :py:class:`~.disco.XEP_0030`
Triggered whenever a ``disco#items`` result stanza is received. Triggered whenever a ``disco#items`` result stanza is received.
disconnected disconnected
- **Data:** ``{}`` - **Data:** ``str``, the reason for the disconnect (if any)
- **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` - **Source:** :py:class:`~.XMLstream`
Signal that the connection with the XMPP server has been lost. Signal that the connection with the XMPP server has been lost.
entity_time
- **Data:**
- **Source:**
failed_auth failed_auth
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.ClientXMPP`, :py:class:`~slixmpp.plugins.xep_0078.xep_0078` - **Source:** :py:class:`~.ClientXMPP`, :py:class:`~.XEP_0078`
Signal that the server has rejected the provided login credentials. Signal that the server has rejected the provided login credentials.
gmail_notify gmail_notify
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.plugins.gmail_notify.gmail_notify` - **Source:** :py:class:`~.plugins.gmail_notify.gmail_notify`
Signal that there are unread emails for the Gmail account associated with the current XMPP account. Signal that there are unread emails for the Gmail account associated with the current XMPP account.
gmail_messages gmail_messages
- **Data:** :py:class:`~slixmpp.Iq` - **Data:** :py:class:`~.Iq`
- **Source:** :py:class:`~slixmpp.plugins.gmail_notify.gmail_notify` - **Source:** :py:class:`~.plugins.gmail_notify.gmail_notify`
Signal that there are unread emails for the Gmail account associated with the current XMPP account. Signal that there are unread emails for the Gmail account associated with the current XMPP account.
got_online got_online
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.roster.item.RosterItem` - **Source:** :py:class:`~.roster.item.RosterItem`
If a presence stanza is received from a JID which was previously marked as If a presence stanza is received from a JID which was previously marked as
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
or '``xa``', then this event is triggered as well. or '``xa``', then this event is triggered as well.
got_offline got_offline
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.roster.item.RosterItem` - **Source:** :py:class:`~.roster.item.RosterItem`
Signal that an unavailable presence stanza has been received from a JID. Signal that an unavailable presence stanza has been received from a JID.
groupchat_invite groupchat_invite
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.XEP_0045` - **Source:** :py:class:`~.XEP_0045`
When a Mediated MUC invite is received.
groupchat_direct_invite groupchat_direct_invite
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0249.direct` - **Source:** :py:class:`~.XEP_0249`
When a Direct MUC invite is received.
groupchat_message groupchat_message
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` - **Source:** :py:class:`~.XEP_0045`
Triggered whenever a message is received from a multi-user chat room. Triggered whenever a message is received from a multi-user chat room.
groupchat_presence groupchat_presence
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` - **Source:** :py:class:`~.XEP_0045`
Triggered whenever a presence stanza is received from a user in a multi-user chat room. Triggered whenever a presence stanza is received from a user in a multi-user chat room.
groupchat_subject groupchat_subject
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` - **Source:** :py:class:`~.XEP_0045`
Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room. Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room.
killed killed
- **Data:** - **Data:** ``{}``
- **Source:** - **Source:** :class:`~.XMLStream`
last_activity When the stream is aborted.
- **Data:**
- **Source:**
message message
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>` - **Source:** :py:class:`BaseXMPP <.BaseXMPP>`
Makes the contents of message stanzas available whenever one is received. Be Makes the contents of message stanzas available whenever one is received. Be
sure to check the message type in order to handle error messages. sure to check the message type in order to handle error messages.
message_error message_error
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>` - **Source:** :py:class:`BaseXMPP <.BaseXMPP>`
Makes the contents of message stanzas available whenever one is received. Makes the contents of message stanzas available whenever one is received.
Only handler messages with an ``error`` type. Only handler messages with an ``error`` type.
message_form message_form
- **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~.Form`
- **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~.XEP_0004`
Currently the same as :term:`message_xform`. Currently the same as :term:`message_xform`.
message_xform message_xform
- **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~.Form`
- **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~.XEP_0004`
Triggered whenever a data form is received inside a message. Triggered whenever a data form is received inside a message.
muc::[room]::got_offline muc::[room]::got_offline
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.XEP_0045` - **Source:** :py:class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
Triggered whenever we receive an unavailable presence from a MUC occupant.
muc::[room]::got_online muc::[room]::got_online
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.XEP_0045` - **Source:** :py:class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
Triggered whenever we receive a presence from a MUC occupant
we do not have in the local cache.
muc::[room]::message muc::[room]::message
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.XEP_0045` - **Source:** :py:class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
Triggered whenever we receive a message from a MUC we are in.
muc::[room]::presence muc::[room]::presence
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0045.XEP_0045` - **Source:** :py:class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
muc::[room]::self-presence
- **Data:** :class:`~.Presence`
- **Source:** :class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
Triggered whenever we receive a presence with status code ``110``
(for example on MUC join, or nick change).
muc::[room]::presence-error
- **Data:** :class:`~.Presence`
- **Source:** :class:`~.XEP_0045`
- **Name parameters:** ``room``, the room this is coming from.
Triggered whenever we receive a presence of ``type="error"`` from
a MUC.
presence_available presence_available
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``available``' is received. A presence stanza with a type of '``available``' is received.
presence_error presence_error
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``error``' is received. A presence stanza with a type of '``error``' is received.
presence_form presence_form
- **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Data:** :py:class:`~.Form`
- **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` - **Source:** :py:class:`~.XEP_0004`
This event is present in the XEP-0004 plugin code, but is currently not used. This event is present in the XEP-0004 plugin code, but is currently not used.
presence_probe presence_probe
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``probe``' is received. A presence stanza with a type of '``probe``' is received.
presence_subscribe presence_subscribe
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``subscribe``' is received. A presence stanza with a type of '``subscribe``' is received.
presence_subscribed presence_subscribed
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``subscribed``' is received. A presence stanza with a type of '``subscribed``' is received.
presence_unavailable presence_unavailable
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``unavailable``' is received. A presence stanza with a type of '``unavailable``' is received.
presence_unsubscribe presence_unsubscribe
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``unsubscribe``' is received. A presence stanza with a type of '``unsubscribe``' is received.
presence_unsubscribed presence_unsubscribed
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
A presence stanza with a type of '``unsubscribed``' is received. A presence stanza with a type of '``unsubscribed``' is received.
roster_update roster_update
- **Data:** :py:class:`~slixmpp.stanza.Roster` - **Data:** :py:class:`~.Roster`
- **Source:** :py:class:`~slixmpp.ClientXMPP` - **Source:** :py:class:`~.ClientXMPP`
An IQ result containing roster entries is received. An IQ result containing roster entries is received.
sent_presence sent_presence
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.roster.multi.Roster` - **Source:** :py:class:`~.roster.multi.Roster`
Signal that an initial presence stanza has been written to the XML stream. Signal that an initial presence stanza has been written to the XML stream.
session_end session_end
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` - **Source:** :py:class:`~.xmlstream.XMLstream`
Signal that a connection to the XMPP server has been lost and the current Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but stream session has ended. Equivalent to :term:`disconnected`, unless the
implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
distinguish between the two events. plugin is loaded.
Plugins that maintain session-based state should clear themselves when Plugins that maintain session-based state should clear themselves when
this event is fired. this event is fired.
session_start session_start
- **Data:** ``{}`` - **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <slixmpp.ClientXMPP>`, - **Source:** :py:class:`.ClientXMPP`,
:py:class:`ComponentXMPP <slixmpp.ComponentXMPP>` :py:class:`~.ComponentXMPP`,
:py:class:`XEP-0078 <slixmpp.plugins.xep_0078>` :py:class:`~.XEP-0078`
Signal that a connection to the XMPP server has been made and a session has been established. Signal that a connection to the XMPP server has been made and a session has been established.
session_resumed
- **Data:** ``{}``
- **Source:** :class:`~.XEP_0198`
When Stream Management manages to resume an ongoing session
after reconnecting.
socket_error socket_error
- **Data:** ``Socket`` exception object - **Data:** ``Socket`` exception object
- **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` - **Source:** :py:class:`~.xmlstream.XMLstream`
stream_error stream_error
- **Data:** :py:class:`~slixmpp.stanza.StreamError` - **Data:** :py:class:`~.StreamError`
- **Source:** :py:class:`~slixmpp.BaseXMPP` - **Source:** :py:class:`~.BaseXMPP`
reactions reactions
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0444.XEP_0444` - **Source:** :py:class:`~.XEP_0444`
When a message containing reactions is received.
carbon_received carbon_received
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0280.XEP_0280` - **Source:** :py:class:`~.XEP_0280`
When a carbon for a received message is received.
carbon_sent carbon_sent
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0280.XEP_0280` - **Source:** :py:class:`~.XEP_0280`
When a carbon for a sent message (from another of our resources) is received.
marker marker
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0333.XEP_0333` - **Source:** :py:class:`~.XEP_0333`
Whenever a chat marker is received (any of them).
marker_received marker_received
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0333.XEP_0333` - **Source:** :py:class:`~.XEP_0333`
Whenever a ``<received/>`` chat marker is received.
marker_displayed marker_displayed
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0333.XEP_0333` - **Source:** :py:class:`~.XEP_0333`
Whenever a ``<displayed/>`` chat marker is received.
marker_acknowledged marker_acknowledged
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0333.XEP_0333` - **Source:** :py:class:`~.XEP_0333`
Whenever an ``<acknowledged/>`` chat marker is received.
attention attention
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0224.XEP_0224` - **Source:** :py:class:`~.XEP_0224`
Whenever a message containing an attention payload is received.
message_correction message_correction
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0308.XEP_0308` - **Source:** :py:class:`~.XEP_0308`
Whenever a message containing a correction is received.
receipt_received receipt_received
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0184.XEP_0184` - **Source:** :py:class:`~.XEP_0184`
Whenever a message receipt is received.
jingle_message_propose jingle_message_propose
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0353.XEP_0353` - **Source:** :py:class:`~.XEP_0353`
jingle_message_retract jingle_message_retract
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0353.XEP_0353` - **Source:** :py:class:`~.XEP_0353`
jingle_message_accept jingle_message_accept
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0353.XEP_0353` - **Source:** :py:class:`~.XEP_0353`
jingle_message_proceed jingle_message_proceed
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0353.XEP_0353` - **Source:** :py:class:`~.XEP_0353`
jingle_message_reject jingle_message_reject
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0353.XEP_0353` - **Source:** :py:class:`~.XEP_0353`
room_activity room_activity
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0437.XEP_0437` - **Source:** :py:class:`~.XEP_0437`
When a room activity stanza is received by a client.
room_activity_bare room_activity_bare
- **Data:** :py:class:`~slixmpp.Presence` - **Data:** :py:class:`~.Presence`
- **Source:** :py:class:`~slixmpp.plugins.xep_0437.XEP_0437` - **Source:** :py:class:`~.XEP_0437`
When an empty room activity stanza is received
(typically by a component).
sm_enabled sm_enabled
- **Data:** :py:class:`~slixmpp.plugins.xep_0198.stanza.Enabled` - **Data:** :py:class:`~.stanza.Enabled`
- **Source:** :py:class:`~slixmpp.plugins.xep_0198.XEP_0198` - **Source:** :py:class:`~.XEP_0198`
When Stream Management is successfully enabled.
sm_disabled sm_disabled
- **Data:** - **Data:** ``{}``
- **Source:** :py:class:`~slixmpp.plugins.xep_0198.XEP_0198` - **Source:** :py:class:`~.XEP_0198`
When Stream Management gets disabled (when disconnected).
ibb_stream_start ibb_stream_start
- **Data:** :py:class:`~slixmpp.plugins.xep_0047.stream.IBBBytestream` - **Data:** :py:class:`~.stream.IBBBytestream`
- **Source:** :py:class:`~slixmpp.plugins.xep_0047.XEP_0047` - **Source:** :py:class:`~.XEP_0047`
When a stream is successfully opened with a remote peer.
ibb_stream_end ibb_stream_end
- **Data:** :py:class:`~slixmpp.plugins.xep_0047.stream.IBBBytestream` - **Data:** :py:class:`~.stream.IBBBytestream`
- **Source:** :py:class:`~slixmpp.plugins.xep_0047.XEP_0047` - **Source:** :py:class:`~.XEP_0047`
When an opened stream closes.
ibb_stream_data ibb_stream_data
- **Data:** :py:class:`~slixmpp.plugins.xep_0047.stream.IBBBytestream` - **Data:** :py:class:`~.stream.IBBBytestream`
- **Source:** :py:class:`~slixmpp.plugins.xep_0047.XEP_0047` - **Source:** :py:class:`~.XEP_0047`
When data is received on an opened stream.
stream:[stream id]:[peer jid] stream:[stream id]:[peer jid]
- **Data:** :py:class:`~slixmpp.plugins.xep_0047.stream.IBBBytestream` - **Data:** :py:class:`~.stream.IBBBytestream`
- **Source:** :py:class:`~slixmpp.plugins.xep_0047.XEP_0047` - **Source:** :py:class:`~.XEP_0047`
- **Name parameters:** ``stream id``, the id of the stream,
and ``peer jid`` the JID of the entity the stream is established
with.
When a stream is opened (with specific sid and jid parameters).
command command
- **Data:** :py:class:`~slixmpp.Iq` - **Data:** :py:class:`~.Iq`
- **Source:** :py:class:`~slixmpp.plugins.xep_0050.XEP_0050` - **Source:** :py:class:`~.XEP_0050`
When an ad-hoc command is received.
command_[action] command_[action]
- **Data:** :py:class:`~slixmpp.Iq` - **Data:** :py:class:`~.Iq`
- **Source:** :py:class:`~slixmpp.plugins.xep_0050.XEP_0050` - **Source:** :py:class:`~.XEP_0050`
- **Name parameters:** ``action``, the action referenced in
the command payload.
When a command with the specific action is received.
pubsub_publish pubsub_publish
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``publish`` is received.
pubsub_retract pubsub_retract
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``retract`` is received.
pubsub_purge pubsub_purge
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``purge`` is received.
pubsub_delete pubsub_delete
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``delete`` is received.
pubsub_config pubsub_config
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``config`` is received.
pubsub_subscription pubsub_subscription
- **Data:** :py:class:`~slixmpp.Message` - **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~slixmpp.plugins.xep_0060.XEP_0060` - **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``subscription`` is received.
Dedicated PubSub Events
=======================
The :class:`~.XEP_0060` plugin (and :class:`~.XEP_0163` plugin, which uses
the former) allows other plugins to map specific namespaces in
PubSub notifications to a dedicated name prefix.
The current list of plugin prefixes is the following:
- ``bookmarks``: :class:`~.XEP_0048`
- ``user_location``: :class:`~.XEP_0080`
- ``avatar_metadata``: :class:`~.XEP_0084`
- ``avatar_data``: :class:`~.XEP_0084`
- ``user_mood``: :class:`~.XEP_0107`
- ``user_activity``: :class:`~.XEP_0108`
- ``user_tune``: :class:`~.XEP_0118`
- ``reachability``: :class:`~.XEP_0152`
- ``user_nick``: :class:`~.XEP_0163`
- ``user_gaming``: :class:`~.XEP_0196`
- ``mix_participant_info``: :class:`~.XEP_0369`
- ``mix_channel_info``: :class:`~.XEP_0369`
.. glossary::
:sorted:
[plugin]_publish
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``publish`` is received.
[plugin]_retract
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``retract`` is received.
[plugin]_purge
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``purge`` is received.
[plugin]_delete
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``delete`` is received.
[plugin]_config
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``config`` is received.
[plugin]_subscription
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`~.XEP_0060`
When a pubsub event of type ``subscription`` is received.

View File

@ -327,6 +327,9 @@ Which will produces the following:
We can see that ``bool_interfaces`` allows to quickly create sub-elements with no We can see that ``bool_interfaces`` allows to quickly create sub-elements with no
content, without the need to create a custom class or getter/setter. content, without the need to create a custom class or getter/setter.
.. _overrides:
overrides overrides
~~~~~~~~~ ~~~~~~~~~
@ -350,6 +353,8 @@ parent ``interfaces`` with the same name.
parent = Parent() parent = Parent()
parent['toto'] = 'test' # equivalent to parent['sub']['toto'] = "test" parent['toto'] = 'test' # equivalent to parent['sub']['toto'] = "test"
.. _is_extension:
is_extension is_extension
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -9,6 +9,7 @@ import asyncio
import logging import logging
from datetime import datetime from datetime import datetime
from typing import ( from typing import (
Any,
Dict, Dict,
List, List,
Tuple, Tuple,
@ -29,6 +30,7 @@ from slixmpp.xmlstream.matcher.stanzapath import StanzaPath
from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask
from slixmpp.exceptions import IqError, IqTimeout, PresenceError from slixmpp.exceptions import IqError, IqTimeout, PresenceError
from slixmpp.plugins.xep_0004 import Form
from slixmpp.plugins.xep_0045 import stanza from slixmpp.plugins.xep_0045 import stanza
from slixmpp.plugins.xep_0045.stanza import ( from slixmpp.plugins.xep_0045.stanza import (
MUCInvite, MUCInvite,
@ -45,6 +47,13 @@ from slixmpp.plugins.xep_0045.stanza import (
MUCActor, MUCActor,
MUCUserItem, MUCUserItem,
) )
from slixmpp.types import (
MucRole,
MucAffiliation,
MucRoomItem,
MucRoomItemKeys,
PresenceArgs,
)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -56,7 +65,7 @@ ROLES = ('moderator', 'participant', 'visitor', 'none')
class XEP_0045(BasePlugin): class XEP_0045(BasePlugin):
""" """
Implements XEP-0045 Multi-User Chat XEP-0045 Multi-User Chat
""" """
name = 'xep_0045' name = 'xep_0045'
@ -64,6 +73,9 @@ class XEP_0045(BasePlugin):
dependencies = {'xep_0030', 'xep_0004'} dependencies = {'xep_0030', 'xep_0004'}
stanza = stanza stanza = stanza
rooms: Dict[JID, Dict[str, MucRoomItem]]
our_nicks: Dict[JID, str]
def plugin_init(self): def plugin_init(self):
self.rooms = {} self.rooms = {}
self.our_nicks = {} self.our_nicks = {}
@ -82,6 +94,7 @@ class XEP_0045(BasePlugin):
register_stanza_plugin(Iq, MUCAdminQuery) register_stanza_plugin(Iq, MUCAdminQuery)
register_stanza_plugin(Iq, MUCOwnerQuery) register_stanza_plugin(Iq, MUCOwnerQuery)
register_stanza_plugin(MUCOwnerQuery, MUCOwnerDestroy) register_stanza_plugin(MUCOwnerQuery, MUCOwnerDestroy)
register_stanza_plugin(MUCOwnerQuery, Form)
register_stanza_plugin(MUCAdminQuery, MUCAdminItem, iterable=True) register_stanza_plugin(MUCAdminQuery, MUCAdminItem, iterable=True)
# Register handlers # Register handlers
@ -89,7 +102,7 @@ class XEP_0045(BasePlugin):
Callback( Callback(
'MUCPresence', 'MUCPresence',
StanzaPath("presence/muc"), StanzaPath("presence/muc"),
self.handle_groupchat_presence, self._handle_groupchat_presence,
)) ))
# <x xmlns="http://jabber.org/protocol/muc"/> is only used in # <x xmlns="http://jabber.org/protocol/muc"/> is only used in
# presence when joining on the client side, and for errors on # presence when joining on the client side, and for errors on
@ -99,7 +112,7 @@ class XEP_0045(BasePlugin):
Callback( Callback(
'MUCPresenceJoin', 'MUCPresenceJoin',
StanzaPath("presence/muc_join"), StanzaPath("presence/muc_join"),
self.handle_groupchat_join, self._handle_groupchat_join,
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
@ -113,37 +126,37 @@ class XEP_0045(BasePlugin):
Callback( Callback(
'MUCError', 'MUCError',
MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns),
self.handle_groupchat_error_message self._handle_groupchat_error_message
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCMessage', 'MUCMessage',
MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns),
self.handle_groupchat_message self._handle_groupchat_message
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCSubject', 'MUCSubject',
MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns),
self.handle_groupchat_subject self._handle_groupchat_subject
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCConfig', 'MUCConfig',
StanzaPath('message/muc/status'), StanzaPath('message/muc/status'),
self.handle_config_change self._handle_config_change
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCInvite', 'MUCInvite',
StanzaPath('message/muc/invite'), StanzaPath('message/muc/invite'),
self.handle_groupchat_invite self._handle_groupchat_invite
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCDecline', 'MUCDecline',
StanzaPath('message/muc/decline'), StanzaPath('message/muc/decline'),
self.handle_groupchat_decline self._handle_groupchat_decline
)) ))
def plugin_end(self): def plugin_end(self):
@ -152,7 +165,7 @@ class XEP_0045(BasePlugin):
def session_bind(self, jid): def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(stanza.NS) self.xmpp.plugin['xep_0030'].add_feature(stanza.NS)
def handle_groupchat_invite(self, inv): def _handle_groupchat_invite(self, inv: Message):
""" Handle an invite into a muc. """ """ Handle an invite into a muc. """
if self.xmpp.is_component: if self.xmpp.is_component:
self.xmpp.event('groupchat_invite', inv) self.xmpp.event('groupchat_invite', inv)
@ -160,7 +173,7 @@ class XEP_0045(BasePlugin):
if inv['from'] not in self.rooms.keys(): if inv['from'] not in self.rooms.keys():
self.xmpp.event("groupchat_invite", inv) self.xmpp.event("groupchat_invite", inv)
def handle_groupchat_decline(self, decl): def _handle_groupchat_decline(self, decl: Message):
"""Handle an invitation decline.""" """Handle an invitation decline."""
if self.xmpp.is_component: if self.xmpp.is_component:
self.xmpp.event('groupchat_invite', decl) self.xmpp.event('groupchat_invite', decl)
@ -168,12 +181,12 @@ class XEP_0045(BasePlugin):
if decl['from'] in self.room.keys(): if decl['from'] in self.room.keys():
self.xmpp.event('groupchat_decline', decl) self.xmpp.event('groupchat_decline', decl)
def handle_config_change(self, msg): def _handle_config_change(self, msg: Message):
"""Handle a MUC configuration change (with status code).""" """Handle a MUC configuration change (with status code)."""
self.xmpp.event('groupchat_config_status', msg) self.xmpp.event('groupchat_config_status', msg)
self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg) self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg)
def client_handle_presence(self, pr: Presence): def _client_handle_presence(self, pr: Presence):
"""As a client, handle a presence stanza""" """As a client, handle a presence stanza"""
got_offline = False got_offline = False
got_online = False got_online = False
@ -206,61 +219,47 @@ class XEP_0045(BasePlugin):
"""Generate MUC presence error events""" """Generate MUC presence error events"""
self.xmpp.event("muc::%s::presence-error" % pr['from'].bare, pr) self.xmpp.event("muc::%s::presence-error" % pr['from'].bare, pr)
def handle_groupchat_presence(self, pr: Presence): def _handle_groupchat_presence(self, pr: Presence):
""" Handle a presence in a muc.""" """ Handle a presence in a muc."""
if self.xmpp.is_component: if self.xmpp.is_component:
self.xmpp.event('groupchat_presence', pr) self.xmpp.event('groupchat_presence', pr)
else: else:
self.client_handle_presence(pr) self._client_handle_presence(pr)
def handle_groupchat_join(self, pr: Presence): def _handle_groupchat_join(self, pr: Presence):
"""Received a join presence (as a component)""" """Received a join presence (as a component)"""
self.xmpp.event('groupchat_join', pr) self.xmpp.event('groupchat_join', pr)
def handle_groupchat_message(self, msg: Message) -> None: def _handle_groupchat_message(self, msg: Message):
""" Handle a message event in a muc. """ Handle a message event in a muc.
""" """
self.xmpp.event('groupchat_message', msg) self.xmpp.event('groupchat_message', msg)
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def handle_groupchat_error_message(self, msg): def _handle_groupchat_error_message(self, msg: Message):
""" Handle a message error event in a muc. """ Handle a message error event in a muc.
""" """
self.xmpp.event('groupchat_message_error', msg) self.xmpp.event('groupchat_message_error', msg)
self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg) self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg)
def handle_groupchat_subject(self, msg: Message) -> None: def _handle_groupchat_subject(self, msg: Message):
""" Handle a message coming from a muc indicating """ Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room) a change of subject (or announcing it when joining the room)
""" """
# See poezio#3452. A message containing subject _and_ (body or thread) # See poezio#3452. A message containing subject _and_ (body or thread)
# is not a subject change. # is not a subject change.
if msg['body'] or msg['thread']: if msg['body'] or msg['thread']:
return None return
self.xmpp.event('groupchat_subject', msg) self.xmpp.event('groupchat_subject', msg)
def jid_in_room(self, room: JID, jid: JID) -> bool:
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return True
return False
def get_nick(self, room: JID, jid: JID) -> Optional[str]:
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return nick
return None
async def join_muc_wait(self, room: JID, nick: str, *, async def join_muc_wait(self, room: JID, nick: str, *,
password: Optional[str] = None, password: Optional[str] = None,
maxchars: Optional[int] = None, maxchars: Optional[int] = None,
maxstanzas: Optional[int] = None, maxstanzas: Optional[int] = None,
seconds: Optional[int] = None, seconds: Optional[int] = None,
since: Optional[datetime] = None, since: Optional[datetime] = None,
presence_options: Optional[Dict[str, str]] = None, presence_options: Optional[PresenceArgs] = None,
timeout: Optional[int] = None) -> Presence: timeout: Optional[int] = None) -> Presence:
""" """
Try to join a MUC and block until we are joined or get an error. Try to join a MUC and block until we are joined or get an error.
@ -268,6 +267,8 @@ class XEP_0045(BasePlugin):
Only one of {maxchars, maxstanzas, seconds, since} will be used, in Only one of {maxchars, maxstanzas, seconds, since} will be used, in
that order. that order.
.. versionadded:: 1.8.0
:param password: The optional room password. :param password: The optional room password.
:param maxchars: Max number of characters to return from history. :param maxchars: Max number of characters to return from history.
:param maxstanzas: Max number of stanzas to return from history. :param maxstanzas: Max number of stanzas to return from history.
@ -303,7 +304,7 @@ class XEP_0045(BasePlugin):
self.our_nicks[room] = nick self.our_nicks[room] = nick
stanza.send() stanza.send()
future = asyncio.Future() future: asyncio.Future = asyncio.Future()
context1 = self.xmpp.event_handler("muc::%s::self-presence" % room, future.set_result) context1 = self.xmpp.event_handler("muc::%s::self-presence" % room, future.set_result)
context2 = self.xmpp.event_handler("muc::%s::presence-error" % room, future.set_result) context2 = self.xmpp.event_handler("muc::%s::presence-error" % room, future.set_result)
with context1, context2: with context1, context2:
@ -321,35 +322,118 @@ class XEP_0045(BasePlugin):
return pres return pres
def join_muc(self, room: JID, nick: str, maxhistory="0", password='', def join_muc(self, room: JID, nick: str, maxhistory="0", password='',
pstatus='', pshow='', pfrom=''): pstatus='', pshow='', pfrom='') -> asyncio.Future:
""" Join the specified room, requesting 'maxhistory' lines of history. """ Join the specified room, requesting 'maxhistory' lines of history.
.. deprecated:: 1.8.0
:meth:`join_muc_wait` will replace this old API starting from version
1.9.0.
""" """
stanza = self.xmpp.make_presence( presence_options = PresenceArgs(
pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow,
pshow=pshow, pfrom=pfrom pstatus=pstatus,
pfrom=pfrom,
) )
stanza.enable('muc_join') maxchars, maxstanzas = None, None
if password:
stanza['muc_join']['password'] = password
if maxhistory: if maxhistory:
if maxhistory == "0": if maxhistory == "0":
stanza['muc_join']['history']['maxchars'] = '0' maxchars = 9
else: else:
stanza['muc_join']['history']['maxstanzas'] = str(maxhistory) maxstanzas = int(maxhistory)
self.xmpp.send(stanza) return asyncio.ensure_future(
self.rooms[room] = {} self.join_muc_wait(
self.our_nicks[room] = nick room=room,
nick=nick,
password=password,
presence_options=presence_options,
maxchars=maxchars,
maxstanzas=maxstanzas,
),
loop=self.xmpp.loop,
)
def leave_muc(self, room: JID, nick: str, msg: str = '', pfrom: Optional[JID] = None):
""" Leave the specified room.
:param room: Room to leave.
:param nick: Your nickname.
:param msg: Presence status to use.
"""
if msg:
self.xmpp.send_presence(
pshow='unavailable',
pto="%s/%s" % (room, nick),
pstatus=msg,
pfrom=pfrom
)
else:
self.xmpp.send_presence(
pshow='unavailable',
pto="%s/%s" % (room, nick),
pfrom=pfrom
)
del self.rooms[room]
def set_subject(self, room: JID, subject: str, *, mfrom: Optional[JID] = None): def set_subject(self, room: JID, subject: str, *, mfrom: Optional[JID] = None):
"""Set a rooms subject.""" """Set a rooms subject.
:param room: JID of the room.
:param subject: Room subject to set.
"""
msg = self.xmpp.make_message(room, mfrom=mfrom) msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['type'] = 'groupchat' msg['type'] = 'groupchat'
msg['subject'] = subject msg['subject'] = subject
msg.send() msg.send()
async def destroy(self, room: JID, reason='', altroom='', *, async def get_room_config(self, room: JID, ifrom: Optional[JID] = None,
**iqkwargs) -> Form:
"""Get the room config form in 0004 plugin format.
:param room: Room to get the config form from.
:raises ValueError: When the form is not found.
:returns: A form object.
"""
iq = self.xmpp.make_iq_get(stanza.NS_OWNER, ito=room, ifrom=ifrom)
result = await iq.send(**iqkwargs)
form = result['mucowner_query'].get_plugin('form', check=True)
if form is None:
raise ValueError("Configuration form not found")
return form
async def set_room_config(self, room: JID, config: Form, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Send a room config form.
:param room: Room to send the form to.
:param config: A filled room form.
"""
query = MUCOwnerQuery()
config['type'] = 'submit'
query.append(config)
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
await iq.send(**iqkwargs)
async def cancel_config(self, room: JID, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Cancel a requested config form.
:param room: Room to cancel the form for.
"""
query = MUCOwnerQuery()
query['form']['type'] = 'cancel'
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
await iq.send(**iqkwargs)
async def destroy(self, room: JID, reason: str = '', altroom: Optional[JID] = None, *,
ifrom: Optional[JID] = None, **iqkwargs): ifrom: Optional[JID] = None, **iqkwargs):
"""Destroy a room.""" """Destroy a room.
:param room: Room JID to destroy.
:param reason: Reason for destroying the room.
:param altroom: An alternate room that users should join.
"""
iq = self.xmpp.make_iq_set(ifrom=ifrom, ito=room) iq = self.xmpp.make_iq_set(ifrom=ifrom, ito=room)
iq.enable('mucowner_query') iq.enable('mucowner_query')
iq['mucowner_query'].enable('destroy') iq['mucowner_query'].enable('destroy')
@ -359,10 +443,17 @@ class XEP_0045(BasePlugin):
iq['mucowner_query']['destroy']['reason'] = reason iq['mucowner_query']['destroy']['reason'] = reason
await iq.send(**iqkwargs) await iq.send(**iqkwargs)
async def set_affiliation(self, room: JID, affiliation: str, *, jid: Optional[JID] = None, async def set_affiliation(self, room: JID, affiliation: MucAffiliation, *,
jid: Optional[JID] = None,
nick: Optional[str] = None, reason: str = '', nick: Optional[str] = None, reason: str = '',
ifrom: Optional[JID] = None, **iqkwargs): ifrom: Optional[JID] = None, **iqkwargs):
""" Change room affiliation.""" """ Change room affiliation for a JID or nickname.
:param room: Room to modify.
:param affiliation: Affiliation to set.
:param jid: User JID to use in the set operation.
:param reason: Reason for the affiliation change.
"""
if affiliation not in AFFILIATIONS: if affiliation not in AFFILIATIONS:
raise ValueError('%s is not a valid affiliation' % affiliation) raise ValueError('%s is not a valid affiliation' % affiliation)
if not any((jid, nick)): if not any((jid, nick)):
@ -377,12 +468,45 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query']['item']['reason'] = reason iq['mucadmin_query']['item']['reason'] = reason
await iq.send(**iqkwargs) await iq.send(**iqkwargs)
async def set_role(self, room: JID, nick: str, role: str, *, async def get_affiliation_list(self, room: JID, affiliation: MucAffiliation, *,
ifrom: Optional[JID] = None, **iqkwargs) -> List[JID]:
"""Get a list of JIDs with the specified affiliation
:param room: Room to get affiliations from.
:param affiliation: The affiliation to list.
"""
iq = self.xmpp.make_iq_get(stanza.NS_ADMIN, ito=room, ifrom=ifrom)
iq['mucadmin_query']['item']['affiliation'] = affiliation
result = await iq.send(**iqkwargs)
return [item['jid'] for item in result['mucadmin_query']]
async def send_affiliation_list(self, room: JID,
affiliations: List[Tuple[JID, MucAffiliation]], *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Send an affiliation delta list.
:param room: Room to send the affiliations to.
:param affiliations: List of couples (jid, affiliation) to set.
"""
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
for jid, affiliation in affiliations:
item = MUCAdminItem()
item['jid'] = jid
item['affiliation'] = affiliation
iq['mucadmin_query'].append(item)
await iq.send(**iqkwargs)
async def set_role(self, room: JID, nick: str, role: MucRole, *,
reason: str = '', ifrom: Optional[JID] = None, **iqkwargs): reason: str = '', ifrom: Optional[JID] = None, **iqkwargs):
""" Change role property of a nick in a room. """ Change role property of a nick in a room.
Typically, roles are temporary (they last only as long as you are in the Typically, roles are temporary (they last only as long as you are in the
room), whereas affiliations are permanent (they last across groupchat room), whereas affiliations are permanent (they last across groupchat
sessions). sessions).
:param room: Room to modify.
:param nick: User nickname to use in the set operation.
:param role: Role to set.
:param reason: Reason for the role change.
""" """
if role not in ROLES: if role not in ROLES:
raise ValueError("Role %s does not exist" % role) raise ValueError("Role %s does not exist" % role)
@ -393,110 +517,127 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query']['item']['reason'] = reason iq['mucadmin_query']['item']['reason'] = reason
await iq.send(**iqkwargs) await iq.send(**iqkwargs)
def invite(self, room: JID, jid: JID, reason: str = '', *, async def get_roles_list(self, room: JID, role: MucRole, *,
mfrom: Optional[JID] = None):
""" Invite a jid to a room."""
msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['muc']['invite']['to'] = jid
if reason:
msg['muc']['invite']['reason'] = reason
self.xmpp.send(msg)
def decline(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
"""Decline a mediated invitation."""
msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['muc']['decline']['to'] = jid
if reason:
msg['muc']['decline']['reason'] = reason
self.xmpp.send(msg)
def leave_muc(self, room: JID, nick: str, msg='', pfrom=None):
""" Leave the specified room.
"""
if msg:
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom)
else:
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
del self.rooms[room]
async def get_room_config(self, room: JID, ifrom=''):
"""Get the room config form in 0004 plugin format """
iq = self.xmpp.make_iq_get(stanza.NS_OWNER, ito=room, ifrom=ifrom)
# For now, swallow errors to preserve existing API
result = await iq.send()
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if form is None:
raise ValueError("Configuration form not found")
return self.xmpp.plugin['xep_0004'].build_form(form)
async def cancel_config(self, room: JID, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Iq:
"""Cancel a requested config form"""
query = MUCOwnerQuery()
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
return await iq.send(**iqkwargs)
async def set_room_config(self, room: JID, config, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Iq:
"""Send a room config form"""
query = MUCOwnerQuery()
config['type'] = 'submit'
query.append(config)
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
return await iq.send(**iqkwargs)
async def get_affiliation_list(self, room: JID, affiliation: str, *,
ifrom: Optional[JID] = None, **iqkwargs) -> List[JID]:
""""Get a list of JIDs with the specified affiliation"""
iq = self.xmpp.make_iq_get(stanza.NS_ADMIN, ito=room, ifrom=ifrom)
iq['mucadmin_query']['item']['affiliation'] = affiliation
result = await iq.send(**iqkwargs)
return [item['jid'] for item in result['mucadmin_query']]
async def get_roles_list(self, room: JID, role: str, *,
ifrom: Optional[JID] = None, **iqkwargs) -> List[str]: ifrom: Optional[JID] = None, **iqkwargs) -> List[str]:
""""Get a list of JIDs with the specified role""" """"Get a list of JIDs with the specified role
:param room: Room to get roles from.
:param role: The role to list.
"""
iq = self.xmpp.make_iq_get(stanza.NS_ADMIN, ito=room, ifrom=ifrom) iq = self.xmpp.make_iq_get(stanza.NS_ADMIN, ito=room, ifrom=ifrom)
iq['mucadmin_query']['item']['role'] = role iq['mucadmin_query']['item']['role'] = role
result = await iq.send(**iqkwargs) result = await iq.send(**iqkwargs)
return [item['nick'] for item in result['mucadmin_query']] return [item['nick'] for item in result['mucadmin_query']]
async def send_affiliation_list(self, room: JID, affiliations: List[Tuple[JID, str]], *, async def send_role_list(self, room: JID, roles: List[Tuple[str, MucRole]], *,
ifrom: Optional[JID] = None, **iqkwargs) -> Iq: ifrom: Optional[JID] = None, **iqkwargs):
"""Send an affiliation delta list""" """Send a role delta list.
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
for jid, affiliation in affiliations:
item = MUCAdminItem()
item['jid'] = jid
item['affiliation'] = affiliation
iq['mucadmin_query'].append(item)
return await iq.send(**iqkwargs)
async def send_role_list(self, room: JID, roles: List[Tuple[str, str]], *, :param room: Room to send the roles to.
ifrom: Optional[JID] = None, **iqkwargs) -> Iq: :param roles: List of couples (nick, role) to set.
"""Send a role delta list""" """
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom) iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
for nick, affiliation in roles: for nick, affiliation in roles:
item = MUCAdminItem() item = MUCAdminItem()
item['nick'] = nick item['nick'] = nick
item['affiliation'] = affiliation item['affiliation'] = affiliation
iq['mucadmin_query'].append(item) iq['mucadmin_query'].append(item)
return await iq.send(**iqkwargs) await iq.send(**iqkwargs)
def invite(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
""" Invite a jid to a room (mediated invitation).
:param room: Room to invite the user in.
:param jid: JID of the user to invite.
:param reason: Reason for inviting the user.
"""
msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['muc']['invite']['to'] = jid
if reason:
msg['muc']['invite']['reason'] = reason
self.xmpp.send(msg)
def invite_server(self, room: JID, jid: JID,
invite_from: JID, reason: str = ''):
"""Send a mediated invite to a user, as a MUC service.
.. versionadded:: 1.8.0
:param room: Room to invite the user in.
:param jid: JID of the user to invite.
:param invite_from: JID of the user to send the invitation from.
:param reason: Reason for inviting the user.
"""
if not self.xmpp.is_component:
raise ValueError("Cannot use this method as a client.")
msg = self.xmpp.make_message(jid, mfrom=room)
msg['muc']['invite']['from'] = invite_from
if reason:
msg['muc']['invite']['reason'] = reason
msg.send()
def decline(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
"""Decline a mediated invitation.
:param room: Room the invitation came from.
:param jid: JID of the user who sent the invitation.
:param reason: Reason for declining.
"""
msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['muc']['decline']['to'] = jid
if reason:
msg['muc']['decline']['reason'] = reason
self.xmpp.send(msg)
def jid_in_room(self, room: JID, jid: JID) -> bool:
"""Check if a JID is present in a room.
:param room: Room to check.
:param jid: JID to check.
"""
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if not entry.get('jid'):
continue
if entry is not None and entry['jid'].full == jid:
return True
return False
def get_nick(self, room: JID, jid: JID) -> Optional[str]:
"""Get the nickname of a specific JID in a room.
:param room: Room to inspect.
:param jid: JID whose nick to return.
"""
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if not entry.get('jid'):
continue
if entry is not None and entry['jid'].full == jid:
return nick
return None
def get_joined_rooms(self) -> List[JID]: def get_joined_rooms(self) -> List[JID]:
return self.rooms.keys() """Get the list of rooms we sent a join presence to
and did not explicitly leave.
"""
return list(self.rooms.keys())
def get_our_jid_in_room(self, room_jid: JID) -> str: def get_our_jid_in_room(self, room_jid: JID) -> str:
""" Return the jid we're using in a room. """ Return the jid we're using in a room.
""" """
return "%s/%s" % (room_jid, self.our_nicks[room_jid]) return "%s/%s" % (room_jid, self.our_nicks[room_jid])
def get_jid_property(self, room, nick, jid_property): def get_jid_property(self, room: JID, nick: str,
jid_property: MucRoomItemKeys) -> Any:
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation' """ Get the property of a nick in a room, such as its 'jid' or 'affiliation'
If not found, return None. If not found, return None.
:param room: Get the property for this room.
:param nick: Which nickname information to get.
:param jid_property: Property to fetch.
""" """
if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]: if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]:
return self.rooms[room][nick][jid_property] return self.rooms[room][nick][jid_property]
@ -505,10 +646,12 @@ class XEP_0045(BasePlugin):
def get_roster(self, room: JID) -> List[str]: def get_roster(self, room: JID) -> List[str]:
""" Get the list of nicks in a room. """ Get the list of nicks in a room.
:param room: Room to list nicks from.
""" """
if room not in self.rooms.keys(): if room not in self.rooms.keys():
raise ValueError("Room %s is not joined" % room) raise ValueError("Room %s is not joined" % room)
return self.rooms[room].keys() return list(self.rooms[room].keys())
def get_users_by_affiliation(self, room: JID, affiliation='member', *, ifrom: Optional[JID] = None): def get_users_by_affiliation(self, room: JID, affiliation='member', *, ifrom: Optional[JID] = None):
# Preserve old API # Preserve old API

View File

@ -7,15 +7,21 @@
This file contains boilerplate to define types relevant to slixmpp. This file contains boilerplate to define types relevant to slixmpp.
""" """
from typing import Optional
try: try:
from typing import ( from typing import (
Literal, Literal,
TypedDict,
) )
except ImportError: except ImportError:
from typing_extensions import ( from typing_extensions import (
Literal, Literal,
TypedDict,
) )
from slixmpp.jid import JID
PresenceTypes = Literal[ PresenceTypes = Literal[
'error', 'probe', 'subscribe', 'subscribed', 'error', 'probe', 'subscribe', 'subscribed',
'unavailable', 'unsubscribe', 'unsubscribed', 'unavailable', 'unsubscribe', 'unsubscribed',
@ -35,3 +41,32 @@ IqTypes = Literal[
"error", "get", "set", "result", "error", "get", "set", "result",
] ]
MucRole = Literal[
'moderator', 'participant', 'visitor', 'none'
]
MucAffiliation = Literal[
'outcast', 'member', 'admin', 'owner', 'none'
]
class PresenceArgs(TypedDict, total=False):
pfrom: JID
pto: JID
pshow: PresenceShows
ptype: PresenceTypes
pstatus: str
class MucRoomItem(TypedDict, total=False):
jid: JID
role: MucRole
affiliation: MucAffiliation
show: Optional[PresenceShows]
status: str
alt_nick: str
MucRoomItemKeys = Literal[
'jid', 'role', 'affiliation', 'show', 'status', 'alt_nick',
]