slidge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
import logging
|
2
|
+
import warnings
|
3
|
+
|
4
|
+
from slixmpp import JID, Iq, Message, Presence, register_stanza_plugin
|
5
|
+
from slixmpp.exceptions import XMPPError
|
6
|
+
from slixmpp.plugins.base import BasePlugin
|
7
|
+
|
8
|
+
from slidge.core import config
|
9
|
+
|
10
|
+
from . import stanza
|
11
|
+
|
12
|
+
log = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class XEP_0100(BasePlugin):
|
16
|
+
name = "xep_0100"
|
17
|
+
description = "XEP-0100: Gateway interaction (slidge)"
|
18
|
+
dependencies = {
|
19
|
+
"xep_0030", # Service discovery
|
20
|
+
"xep_0077", # In band registration
|
21
|
+
"xep_0356", # Privileged entities
|
22
|
+
}
|
23
|
+
|
24
|
+
default_config = {
|
25
|
+
"component_name": "SliXMPP gateway",
|
26
|
+
"type": "xmpp",
|
27
|
+
"needs_registration": True,
|
28
|
+
}
|
29
|
+
|
30
|
+
def plugin_init(self):
|
31
|
+
if not self.xmpp.is_component:
|
32
|
+
log.error("Only components can be gateways, aborting plugin load")
|
33
|
+
return
|
34
|
+
|
35
|
+
self.xmpp["xep_0030"].add_identity(
|
36
|
+
name=self.component_name, category="gateway", itype=self.type
|
37
|
+
)
|
38
|
+
|
39
|
+
# Without that BaseXMPP sends unsub/unavailable on sub requests, and we don't want that
|
40
|
+
self.xmpp.client_roster.auto_authorize = False
|
41
|
+
self.xmpp.client_roster.auto_subscribe = False
|
42
|
+
|
43
|
+
self.xmpp.add_event_handler("user_register", self.on_user_register)
|
44
|
+
self.xmpp.add_event_handler("user_unregister", self.on_user_unregister)
|
45
|
+
self.xmpp.add_event_handler(
|
46
|
+
"presence_unsubscribe", self.on_presence_unsubscribe
|
47
|
+
)
|
48
|
+
|
49
|
+
self.xmpp.add_event_handler("message", self.on_message)
|
50
|
+
|
51
|
+
register_stanza_plugin(Iq, stanza.Gateway)
|
52
|
+
|
53
|
+
def plugin_end(self):
|
54
|
+
if not self.xmpp.is_component:
|
55
|
+
self.xmpp.remove_event_handler("user_register", self.on_user_register)
|
56
|
+
self.xmpp.remove_event_handler("user_unregister", self.on_user_unregister)
|
57
|
+
self.xmpp.remove_event_handler(
|
58
|
+
"presence_unsubscribe", self.on_presence_unsubscribe
|
59
|
+
)
|
60
|
+
|
61
|
+
self.xmpp.remove_event_handler("message", self.on_message)
|
62
|
+
|
63
|
+
async def get_user(self, stanza):
|
64
|
+
return await self.xmpp["xep_0077"].api["user_get"](None, None, None, stanza)
|
65
|
+
|
66
|
+
async def on_user_unregister(self, iq: Iq):
|
67
|
+
self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unavailable")
|
68
|
+
self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unsubscribe")
|
69
|
+
self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unsubscribed")
|
70
|
+
|
71
|
+
async def on_user_register(self, iq: Iq):
|
72
|
+
self.xmpp.client_roster[iq.get_from()].load()
|
73
|
+
await self.add_component_to_roster(jid=iq.get_from())
|
74
|
+
|
75
|
+
async def add_component_to_roster(self, jid: JID):
|
76
|
+
if config.NO_ROSTER_PUSH:
|
77
|
+
return
|
78
|
+
items = {
|
79
|
+
self.xmpp.boundjid.bare: {
|
80
|
+
"name": self.component_name,
|
81
|
+
"subscription": "both",
|
82
|
+
"groups": ["Slidge"],
|
83
|
+
}
|
84
|
+
}
|
85
|
+
try:
|
86
|
+
await self._set_roster(jid, items)
|
87
|
+
except PermissionError:
|
88
|
+
warnings.warn(
|
89
|
+
"Slidge does not have the privilege to manage users' rosters. "
|
90
|
+
"Users should add the slidge component to their rosters manually."
|
91
|
+
)
|
92
|
+
if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
|
93
|
+
self.xmpp.send_presence(ptype="subscribe", pto=jid.bare)
|
94
|
+
|
95
|
+
async def _set_roster(self, jid, items):
|
96
|
+
try:
|
97
|
+
await self.xmpp["xep_0356"].set_roster(jid=jid.bare, roster_items=items)
|
98
|
+
except PermissionError:
|
99
|
+
await self.xmpp["xep_0356_old"].set_roster(jid=jid.bare, roster_items=items)
|
100
|
+
|
101
|
+
def on_presence_unsubscribe(self, p: Presence):
|
102
|
+
if p.get_to() == self.xmpp.boundjid.bare:
|
103
|
+
log.debug("REMOVE: Our roster: %s", self.xmpp.client_roster)
|
104
|
+
self.xmpp["xep_0077"].api["user_remove"](None, None, p["from"], p)
|
105
|
+
self.xmpp.event("user_unregister", p)
|
106
|
+
|
107
|
+
async def on_message(self, msg: Message):
|
108
|
+
if msg["type"] == "groupchat":
|
109
|
+
return # groupchat messages are out of scope of XEP-0100
|
110
|
+
|
111
|
+
if msg["to"] == self.xmpp.boundjid.bare:
|
112
|
+
# It may be useful to exchange direct messages with the component
|
113
|
+
self.xmpp.event("gateway_message", msg)
|
114
|
+
return
|
115
|
+
|
116
|
+
if self.needs_registration and await self.get_user(msg) is None:
|
117
|
+
raise XMPPError(
|
118
|
+
"registration-required", text="You are not registered to this gateway"
|
119
|
+
)
|
120
|
+
|
121
|
+
self.xmpp.event("legacy_message", msg)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from slixmpp.plugins.base import register_plugin
|
6
|
+
|
7
|
+
from .stanza import VCardTempUpdate
|
8
|
+
from .vcard_avatar import XEP_0153
|
9
|
+
|
10
|
+
register_plugin(XEP_0153)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from slixmpp.xmlstream import ElementBase
|
6
|
+
|
7
|
+
|
8
|
+
class VCardTempUpdate(ElementBase):
|
9
|
+
name = "x"
|
10
|
+
namespace = "vcard-temp:x:update"
|
11
|
+
plugin_attrib = "vcard_temp_update"
|
12
|
+
interfaces = {"photo"}
|
13
|
+
sub_interfaces = interfaces
|
14
|
+
|
15
|
+
def set_photo(self, value):
|
16
|
+
if value is not None:
|
17
|
+
self._set_sub_text("photo", value, keep=True)
|
18
|
+
else:
|
19
|
+
self._del_sub("photo")
|
20
|
+
|
21
|
+
def get_photo(self):
|
22
|
+
photo = self.xml.find("{%s}photo" % self.namespace)
|
23
|
+
if photo is None:
|
24
|
+
return None
|
25
|
+
return photo.text
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
import logging
|
6
|
+
|
7
|
+
from slixmpp.plugins.base import BasePlugin
|
8
|
+
from slixmpp.stanza import Presence
|
9
|
+
from slixmpp.xmlstream import register_stanza_plugin
|
10
|
+
|
11
|
+
from . import VCardTempUpdate, stanza
|
12
|
+
|
13
|
+
log = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class XEP_0153(BasePlugin):
|
17
|
+
name = "xep_0153"
|
18
|
+
description = "XEP-0153: vCard-Based Avatars (slidge, just for MUCs)"
|
19
|
+
dependencies = {"xep_0054"}
|
20
|
+
stanza = stanza
|
21
|
+
|
22
|
+
def plugin_init(self):
|
23
|
+
register_stanza_plugin(Presence, VCardTempUpdate)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from slixmpp import register_stanza_plugin
|
4
|
+
from slixmpp.plugins.xep_0234.stanza import File
|
5
|
+
from slixmpp.xmlstream import ElementBase
|
6
|
+
|
7
|
+
NS = "urn:xmpp:thumbs:1"
|
8
|
+
|
9
|
+
|
10
|
+
class Thumbnail(ElementBase):
|
11
|
+
name = plugin_attrib = "thumbnail"
|
12
|
+
namespace = NS
|
13
|
+
interfaces = {"uri", "media-type", "width", "height"}
|
14
|
+
|
15
|
+
def get_width(self) -> float:
|
16
|
+
return _int_or_none(self._get_attr("width"))
|
17
|
+
|
18
|
+
def get_height(self) -> float:
|
19
|
+
return _int_or_none(self._get_attr("height"))
|
20
|
+
|
21
|
+
def set_width(self, v: int) -> None:
|
22
|
+
self._set_attr("width", str(v))
|
23
|
+
|
24
|
+
def set_height(self, v: int) -> None:
|
25
|
+
self._set_attr("height", str(v))
|
26
|
+
|
27
|
+
|
28
|
+
def _int_or_none(v) -> Optional[int]:
|
29
|
+
try:
|
30
|
+
return int(v)
|
31
|
+
except ValueError:
|
32
|
+
return None
|
33
|
+
|
34
|
+
|
35
|
+
def register_plugin():
|
36
|
+
register_stanza_plugin(File, Thumbnail)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from slixmpp.plugins import BasePlugin
|
4
|
+
|
5
|
+
from . import stanza
|
6
|
+
|
7
|
+
log = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
class XEP_0264(BasePlugin):
|
11
|
+
"""
|
12
|
+
XEP-0264: Jingle Content Thumbnails
|
13
|
+
|
14
|
+
Minimum needed for xep 0385 (Stateless inline media sharing)
|
15
|
+
"""
|
16
|
+
|
17
|
+
name = "xep_0264"
|
18
|
+
description = "XEP-0264: Jingle Content Thumbnails"
|
19
|
+
dependencies = {"xep_0234"}
|
20
|
+
stanza = stanza
|
21
|
+
|
22
|
+
def plugin_init(self):
|
23
|
+
stanza.register_plugin()
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING, NamedTuple, Optional
|
3
|
+
|
4
|
+
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
5
|
+
from slixmpp.plugins.base import BasePlugin, register_plugin
|
6
|
+
from slixmpp.plugins.xep_0292.stanza import NS, VCard4
|
7
|
+
from slixmpp.types import JidStr
|
8
|
+
|
9
|
+
from slidge.contact import LegacyContact
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from slidge.core.gateway import BaseGateway
|
13
|
+
|
14
|
+
|
15
|
+
class StoredVCard(NamedTuple):
|
16
|
+
content: VCard4
|
17
|
+
authorized_jids: set[JidStr]
|
18
|
+
|
19
|
+
|
20
|
+
class VCard4Provider(BasePlugin):
|
21
|
+
xmpp: "BaseGateway"
|
22
|
+
|
23
|
+
name = "xep_0292_provider"
|
24
|
+
description = "VCard4 Provider"
|
25
|
+
dependencies = {"xep_0030"}
|
26
|
+
|
27
|
+
def __init__(self, *a, **k):
|
28
|
+
super(VCard4Provider, self).__init__(*a, **k)
|
29
|
+
self._vcards = dict[JidStr, StoredVCard]()
|
30
|
+
|
31
|
+
def plugin_init(self):
|
32
|
+
self.xmpp.register_handler(
|
33
|
+
CoroutineCallback(
|
34
|
+
"get_vcard",
|
35
|
+
StanzaPath(f"iq@type=get/vcard"),
|
36
|
+
self.handle_vcard_get, # type:ignore
|
37
|
+
)
|
38
|
+
)
|
39
|
+
|
40
|
+
self.xmpp.plugin["xep_0030"].add_feature(NS)
|
41
|
+
|
42
|
+
def _get_cached_vcard(self, jid: JidStr, requested_by: JidStr) -> Optional[VCard4]:
|
43
|
+
vcard = self._vcards.get(JID(jid).bare)
|
44
|
+
if vcard:
|
45
|
+
if auth := vcard.authorized_jids:
|
46
|
+
if JID(requested_by).bare in auth:
|
47
|
+
return vcard.content
|
48
|
+
else:
|
49
|
+
return vcard.content
|
50
|
+
return None
|
51
|
+
|
52
|
+
async def get_vcard(self, jid: JidStr, requested_by: JidStr) -> Optional[VCard4]:
|
53
|
+
if vcard := self._get_cached_vcard(jid, requested_by):
|
54
|
+
log.debug("Found a cached vcard")
|
55
|
+
return vcard
|
56
|
+
if not hasattr(self.xmpp, "get_session_from_jid"):
|
57
|
+
return None
|
58
|
+
jid = JID(jid)
|
59
|
+
requested_by = JID(requested_by)
|
60
|
+
session = self.xmpp.get_session_from_jid(requested_by)
|
61
|
+
if session is None:
|
62
|
+
return
|
63
|
+
entity = await session.get_contact_or_group_or_participant(jid)
|
64
|
+
if isinstance(entity, LegacyContact):
|
65
|
+
log.debug("Fetching vcard")
|
66
|
+
await entity.fetch_vcard()
|
67
|
+
return self._get_cached_vcard(jid, requested_by)
|
68
|
+
return None
|
69
|
+
|
70
|
+
async def handle_vcard_get(self, iq: Iq):
|
71
|
+
r = iq.reply()
|
72
|
+
if vcard := await self.get_vcard(iq.get_to().bare, iq.get_from().bare):
|
73
|
+
r.append(vcard)
|
74
|
+
else:
|
75
|
+
r.enable("vcard")
|
76
|
+
r.send()
|
77
|
+
|
78
|
+
def set_vcard(
|
79
|
+
self,
|
80
|
+
jid: JidStr,
|
81
|
+
vcard: VCard4,
|
82
|
+
/,
|
83
|
+
authorized_jids: Optional[set[JidStr]] = None,
|
84
|
+
):
|
85
|
+
cache = self._vcards.get(jid)
|
86
|
+
new = StoredVCard(
|
87
|
+
vcard, authorized_jids if authorized_jids is not None else set()
|
88
|
+
)
|
89
|
+
self._vcards[jid] = new
|
90
|
+
if cache == new:
|
91
|
+
return
|
92
|
+
if self.xmpp["pubsub"] and authorized_jids:
|
93
|
+
for to in authorized_jids:
|
94
|
+
self.xmpp.loop.create_task(
|
95
|
+
self.xmpp["pubsub"].broadcast_vcard_event(jid, to)
|
96
|
+
)
|
97
|
+
|
98
|
+
|
99
|
+
register_plugin(VCard4Provider)
|
100
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission
|
5
|
+
from slixmpp.plugins.base import register_plugin
|
6
|
+
|
7
|
+
from .mam import XEP_0313
|
8
|
+
from .stanza import MAM, Metadata, Result
|
9
|
+
|
10
|
+
register_plugin(XEP_0313)
|
11
|
+
|
12
|
+
__all__ = ["XEP_0313", "Result", "MAM", "Metadata"]
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission
|
5
|
+
import logging
|
6
|
+
from asyncio import Future
|
7
|
+
from collections.abc import AsyncGenerator
|
8
|
+
from datetime import datetime
|
9
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple
|
10
|
+
|
11
|
+
from slixmpp import JID
|
12
|
+
from slixmpp.plugins import BasePlugin
|
13
|
+
from slixmpp.plugins.xep_0004.stanza import Form
|
14
|
+
from slixmpp.stanza import Iq, Message
|
15
|
+
from slixmpp.xmlstream import register_stanza_plugin
|
16
|
+
from slixmpp.xmlstream.handler import Collector
|
17
|
+
from slixmpp.xmlstream.matcher import MatchXMLMask
|
18
|
+
|
19
|
+
from . import stanza
|
20
|
+
|
21
|
+
log = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
class XEP_0313(BasePlugin):
|
25
|
+
"""
|
26
|
+
XEP-0313 Message Archive Management
|
27
|
+
"""
|
28
|
+
|
29
|
+
name = "xep_0313"
|
30
|
+
description = "XEP-0313: Message Archive Management"
|
31
|
+
dependencies = {"xep_0004", "xep_0030", "xep_0050", "xep_0059", "xep_0297"}
|
32
|
+
stanza = stanza
|
33
|
+
|
34
|
+
def plugin_init(self):
|
35
|
+
register_stanza_plugin(stanza.MAM, Form)
|
36
|
+
register_stanza_plugin(Iq, stanza.MAM)
|
37
|
+
register_stanza_plugin(Message, stanza.Result)
|
38
|
+
register_stanza_plugin(Iq, stanza.Fin)
|
39
|
+
register_stanza_plugin(stanza.Result, self.xmpp["xep_0297"].stanza.Forwarded)
|
40
|
+
register_stanza_plugin(stanza.MAM, self.xmpp["xep_0059"].stanza.Set)
|
41
|
+
register_stanza_plugin(stanza.Fin, self.xmpp["xep_0059"].stanza.Set)
|
42
|
+
register_stanza_plugin(Iq, stanza.Metadata)
|
43
|
+
register_stanza_plugin(stanza.Metadata, stanza.Start)
|
44
|
+
register_stanza_plugin(stanza.Metadata, stanza.End)
|
45
|
+
|
46
|
+
def retrieve(
|
47
|
+
self,
|
48
|
+
jid: Optional[JID] = None,
|
49
|
+
start: Optional[datetime] = None,
|
50
|
+
end: Optional[datetime] = None,
|
51
|
+
with_jid: Optional[JID] = None,
|
52
|
+
ifrom: Optional[JID] = None,
|
53
|
+
reverse: bool = False,
|
54
|
+
timeout: int = None,
|
55
|
+
callback: Callable[[Iq], None] = None,
|
56
|
+
iterator: bool = False,
|
57
|
+
rsm: Optional[Dict[str, Any]] = None,
|
58
|
+
) -> Awaitable:
|
59
|
+
"""
|
60
|
+
Send a MAM query and retrieve the results.
|
61
|
+
|
62
|
+
:param JID jid: Entity holding the MAM records
|
63
|
+
:param datetime start,end: MAM query temporal boundaries
|
64
|
+
:param JID with_jid: Filter results on this JID
|
65
|
+
:param JID ifrom: To change the from address of the query
|
66
|
+
:param bool reverse: Get the results in reverse order
|
67
|
+
:param int timeout: IQ timeout
|
68
|
+
:param func callback: Custom callback for handling results
|
69
|
+
:param bool iterator: Use RSM and iterate over a paginated query
|
70
|
+
:param dict rsm: RSM custom options
|
71
|
+
"""
|
72
|
+
iq, stanza_mask = self._pre_mam_retrieve(jid, start, end, with_jid, ifrom)
|
73
|
+
query_id = iq["id"]
|
74
|
+
amount = 10
|
75
|
+
if rsm:
|
76
|
+
for key, value in rsm.items():
|
77
|
+
iq["mam"]["rsm"][key] = str(value)
|
78
|
+
if key == "max":
|
79
|
+
amount = value
|
80
|
+
cb_data = {}
|
81
|
+
|
82
|
+
xml_mask = str(stanza_mask)
|
83
|
+
|
84
|
+
def pre_cb(query: Iq) -> None:
|
85
|
+
stanza_mask["mam_result"]["queryid"] = query["id"]
|
86
|
+
xml_mask = str(stanza_mask)
|
87
|
+
query["mam"]["queryid"] = query["id"]
|
88
|
+
collector = Collector("MAM_Results_%s" % query_id, MatchXMLMask(xml_mask))
|
89
|
+
self.xmpp.register_handler(collector)
|
90
|
+
cb_data["collector"] = collector
|
91
|
+
|
92
|
+
def post_cb(result: Iq) -> None:
|
93
|
+
results = cb_data["collector"].stop()
|
94
|
+
if result["type"] == "result":
|
95
|
+
result["mam"]["results"] = results
|
96
|
+
result["mam_fin"]["results"] = results
|
97
|
+
|
98
|
+
if iterator:
|
99
|
+
return self.xmpp["xep_0059"].iterate(
|
100
|
+
iq,
|
101
|
+
"mam",
|
102
|
+
"results",
|
103
|
+
amount=amount,
|
104
|
+
reverse=reverse,
|
105
|
+
recv_interface="mam_fin",
|
106
|
+
pre_cb=pre_cb,
|
107
|
+
post_cb=post_cb,
|
108
|
+
)
|
109
|
+
|
110
|
+
collector = Collector("MAM_Results_%s" % query_id, MatchXMLMask(xml_mask))
|
111
|
+
self.xmpp.register_handler(collector)
|
112
|
+
|
113
|
+
def wrapped_cb(iq: Iq) -> None:
|
114
|
+
results = collector.stop()
|
115
|
+
if iq["type"] == "result":
|
116
|
+
iq["mam"]["results"] = results
|
117
|
+
if callback:
|
118
|
+
callback(iq)
|
119
|
+
|
120
|
+
return iq.send(timeout=timeout, callback=wrapped_cb)
|
121
|
+
|
122
|
+
async def iterate(
|
123
|
+
self,
|
124
|
+
jid: Optional[JID] = None,
|
125
|
+
start: Optional[datetime] = None,
|
126
|
+
end: Optional[datetime] = None,
|
127
|
+
with_jid: Optional[JID] = None,
|
128
|
+
ifrom: Optional[JID] = None,
|
129
|
+
reverse: bool = False,
|
130
|
+
rsm: Optional[Dict[str, Any]] = None,
|
131
|
+
total: Optional[int] = None,
|
132
|
+
) -> AsyncGenerator:
|
133
|
+
"""
|
134
|
+
Iterate over each message of MAM query.
|
135
|
+
|
136
|
+
.. versionadded:: 1.8.0
|
137
|
+
|
138
|
+
:param jid: Entity holding the MAM records
|
139
|
+
:param start: MAM query start time
|
140
|
+
:param end: MAM query end time
|
141
|
+
:param with_jid: Filter results on this JID
|
142
|
+
:param ifrom: To change the from address of the query
|
143
|
+
:param reverse: Get the results in reverse order
|
144
|
+
:param rsm: RSM custom options
|
145
|
+
:param total: A number of messages received after which the query
|
146
|
+
should stop.
|
147
|
+
"""
|
148
|
+
iq, stanza_mask = self._pre_mam_retrieve(jid, start, end, with_jid, ifrom)
|
149
|
+
query_id = iq["id"]
|
150
|
+
amount = 10
|
151
|
+
|
152
|
+
if rsm:
|
153
|
+
for key, value in rsm.items():
|
154
|
+
iq["mam"]["rsm"][key] = str(value)
|
155
|
+
if key == "max":
|
156
|
+
amount = value
|
157
|
+
cb_data = {}
|
158
|
+
|
159
|
+
def pre_cb(query: Iq) -> None:
|
160
|
+
stanza_mask["mam_result"]["queryid"] = query["id"]
|
161
|
+
xml_mask = str(stanza_mask)
|
162
|
+
query["mam"]["queryid"] = query["id"]
|
163
|
+
collector = Collector("MAM_Results_%s" % query_id, MatchXMLMask(xml_mask))
|
164
|
+
self.xmpp.register_handler(collector)
|
165
|
+
cb_data["collector"] = collector
|
166
|
+
|
167
|
+
def post_cb(result: Iq) -> None:
|
168
|
+
results = cb_data["collector"].stop()
|
169
|
+
if result["type"] == "result":
|
170
|
+
result["mam"]["results"] = results
|
171
|
+
result["mam_fin"]["results"] = results
|
172
|
+
|
173
|
+
iterator = self.xmpp["xep_0059"].iterate(
|
174
|
+
iq,
|
175
|
+
"mam",
|
176
|
+
"results",
|
177
|
+
amount=amount,
|
178
|
+
reverse=reverse,
|
179
|
+
recv_interface="mam_fin",
|
180
|
+
pre_cb=pre_cb,
|
181
|
+
post_cb=post_cb,
|
182
|
+
)
|
183
|
+
recv_count = 0
|
184
|
+
|
185
|
+
async for page in iterator:
|
186
|
+
messages = [message for message in page["mam"]["results"]]
|
187
|
+
if reverse:
|
188
|
+
messages.reverse()
|
189
|
+
for message in messages:
|
190
|
+
yield message
|
191
|
+
recv_count += 1
|
192
|
+
if total is not None and recv_count >= total:
|
193
|
+
break
|
194
|
+
if total is not None and recv_count >= total:
|
195
|
+
break
|
196
|
+
|
197
|
+
def _pre_mam_retrieve(
|
198
|
+
self,
|
199
|
+
jid: Optional[JID] = None,
|
200
|
+
start: Optional[datetime] = None,
|
201
|
+
end: Optional[datetime] = None,
|
202
|
+
with_jid: Optional[JID] = None,
|
203
|
+
ifrom: Optional[JID] = None,
|
204
|
+
) -> Tuple[Iq, Message]:
|
205
|
+
"""Build the IQ and stanza mask for MAM results"""
|
206
|
+
iq = self.xmpp.make_iq_set(ito=jid, ifrom=ifrom)
|
207
|
+
query_id = iq["id"]
|
208
|
+
iq["mam"]["queryid"] = query_id
|
209
|
+
iq["mam"]["start"] = start
|
210
|
+
iq["mam"]["end"] = end
|
211
|
+
iq["mam"]["with"] = with_jid
|
212
|
+
|
213
|
+
stanza_mask = self.xmpp.Message()
|
214
|
+
|
215
|
+
auto_origin = stanza_mask.xml.find("{urn:xmpp:sid:0}origin-id")
|
216
|
+
if auto_origin is not None:
|
217
|
+
stanza_mask.xml.remove(auto_origin)
|
218
|
+
del stanza_mask["id"]
|
219
|
+
del stanza_mask["lang"]
|
220
|
+
stanza_mask["from"] = jid
|
221
|
+
stanza_mask["mam_result"]["queryid"] = query_id
|
222
|
+
|
223
|
+
return (iq, stanza_mask)
|
224
|
+
|
225
|
+
async def get_fields(self, jid: Optional[JID] = None, **iqkwargs) -> Form:
|
226
|
+
"""Get MAM query fields.
|
227
|
+
|
228
|
+
.. versionadded:: 1.8.0
|
229
|
+
|
230
|
+
:param jid: JID to retrieve the policy from.
|
231
|
+
:return: The Form of allowed options
|
232
|
+
"""
|
233
|
+
ifrom = iqkwargs.pop("ifrom", None)
|
234
|
+
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
|
235
|
+
iq.enable("mam")
|
236
|
+
result = await iq.send(**iqkwargs)
|
237
|
+
return result["mam"]["form"]
|
238
|
+
|
239
|
+
async def get_configuration_commands(
|
240
|
+
self, jid: Optional[JID], **discokwargs
|
241
|
+
) -> Future:
|
242
|
+
"""Get the list of MAM advanced configuration commands.
|
243
|
+
|
244
|
+
.. versionchanged:: 1.8.0
|
245
|
+
|
246
|
+
:param jid: JID to get the commands from.
|
247
|
+
"""
|
248
|
+
if jid is None:
|
249
|
+
jid = self.xmpp.boundjid.bare
|
250
|
+
return await self.xmpp["xep_0030"].get_items(
|
251
|
+
jid=jid, node="urn:xmpp:mam#configure", **discokwargs
|
252
|
+
)
|
253
|
+
|
254
|
+
def get_archive_metadata(self, jid: Optional[JID] = None, **iqkwargs) -> Future:
|
255
|
+
"""Get the archive metadata from a JID.
|
256
|
+
|
257
|
+
:param jid: JID to get the metadata from.
|
258
|
+
"""
|
259
|
+
ifrom = iqkwargs.pop("ifrom", None)
|
260
|
+
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
|
261
|
+
iq.enable("mam_metadata")
|
262
|
+
return iq.send(**iqkwargs)
|