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.
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)