slidge 0.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|