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.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +757 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +525 -0
  41. slidge/core/session.py +752 -0
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. 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,9 @@
1
+ from slixmpp.xmlstream import ElementBase
2
+
3
+
4
+ class Gateway(ElementBase):
5
+ namespace = "jabber:iq:gateway"
6
+ name = "query"
7
+ plugin_attrib = "gateway"
8
+ interfaces = {"desc", "prompt", "jid"}
9
+ sub_interfaces = interfaces
@@ -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,5 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from .thumbnail import XEP_0264
4
+
5
+ register_plugin(XEP_0264)
@@ -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,5 @@
1
+ from slixmpp.plugins.xep_0292 import stanza
2
+
3
+ from . import vcard4
4
+
5
+ __all__ = ("stanza", "vcard4")
@@ -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)