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,217 @@
1
+ import re
2
+ from asyncio import Task, sleep
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Optional
5
+
6
+ from slixmpp.types import PresenceShows, PresenceTypes
7
+
8
+ from ...util.sql import CachedPresence, db
9
+ from .. import config
10
+ from .base import BaseSender
11
+
12
+
13
+ class _NoChange(Exception):
14
+ pass
15
+
16
+
17
+ _FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscribed"}
18
+
19
+
20
+ class PresenceMixin(BaseSender):
21
+ _ONLY_SEND_PRESENCE_CHANGES = False
22
+
23
+ def __init__(self, *a, **k):
24
+ super().__init__(*a, **k)
25
+ self.__update_last_seen_fallback_task: Optional[Task] = None
26
+
27
+ async def __update_last_seen_fallback(self):
28
+ await sleep(3600 * 7)
29
+ self.send_last_presence(force=True, no_cache_online=False)
30
+
31
+ def _get_last_presence(self) -> Optional[CachedPresence]:
32
+ return db.presence_get(self.jid, self.user)
33
+
34
+ def _store_last_presence(self, new: CachedPresence):
35
+ return db.presence_store(self.jid, new, self.user)
36
+
37
+ def _make_presence(
38
+ self,
39
+ *,
40
+ last_seen: Optional[datetime] = None,
41
+ force=False,
42
+ bare=False,
43
+ ptype: Optional[PresenceTypes] = None,
44
+ pstatus: Optional[str] = None,
45
+ pshow: Optional[PresenceShows] = None,
46
+ ):
47
+ if last_seen and last_seen.tzinfo is None:
48
+ last_seen = last_seen.astimezone(timezone.utc)
49
+
50
+ old = self._get_last_presence()
51
+
52
+ if ptype not in _FRIEND_REQUEST_PRESENCES:
53
+ new = CachedPresence(
54
+ last_seen=last_seen, ptype=ptype, pstatus=pstatus, pshow=pshow
55
+ )
56
+ if old != new:
57
+ if hasattr(self, "muc") and ptype == "unavailable":
58
+ db.presence_delete(self.jid, self.user)
59
+ else:
60
+ self._store_last_presence(new)
61
+ if old and not force and self._ONLY_SEND_PRESENCE_CHANGES:
62
+ if old == new:
63
+ self.session.log.debug("Presence is the same as cached")
64
+ raise _NoChange
65
+ self.session.log.debug(
66
+ "Presence is not the same as cached: %s vs %s", old, new
67
+ )
68
+
69
+ p = self.xmpp.make_presence(
70
+ pfrom=self.jid.bare if bare else self.jid,
71
+ ptype=ptype,
72
+ pshow=pshow,
73
+ pstatus=pstatus,
74
+ )
75
+ if last_seen:
76
+ # it's ugly to check for the presence of this string, but a better fix is more work
77
+ if config.LAST_SEEN_FALLBACK and not re.match(
78
+ ".*Last seen .*", p["status"]
79
+ ):
80
+ last_seen_fallback, recent = get_last_seen_fallback(last_seen)
81
+ if p["status"]:
82
+ p["status"] = p["status"] + " -- " + last_seen_fallback
83
+ else:
84
+ p["status"] = last_seen_fallback
85
+ if recent:
86
+ # if less than a week, we use sth like 'Last seen: Monday, 8:05",
87
+ # but if lasts more than a week, this is not very informative, so
88
+ # we need to force resend an updated presence status
89
+ if self.__update_last_seen_fallback_task:
90
+ self.__update_last_seen_fallback_task.cancel()
91
+ self.__update_last_seen_fallback_task = self.xmpp.loop.create_task(
92
+ self.__update_last_seen_fallback()
93
+ )
94
+ p["idle"]["since"] = last_seen
95
+ return p
96
+
97
+ def send_last_presence(self, force=False, no_cache_online=False):
98
+ if (cache := self._get_last_presence()) is None:
99
+ if force:
100
+ if no_cache_online:
101
+ self.online()
102
+ else:
103
+ self.offline()
104
+ return
105
+ self._send(
106
+ self._make_presence(
107
+ last_seen=cache.last_seen,
108
+ force=True,
109
+ ptype=cache.ptype,
110
+ pshow=cache.pshow,
111
+ pstatus=cache.pstatus,
112
+ )
113
+ )
114
+
115
+ def online(
116
+ self,
117
+ status: Optional[str] = None,
118
+ last_seen: Optional[datetime] = None,
119
+ ):
120
+ """
121
+ Send an "online" presence from this contact to the user.
122
+
123
+ :param status: Arbitrary text, details of the status, eg: "Listening to Britney Spears"
124
+ :param last_seen: For :xep:`0319`
125
+ """
126
+ try:
127
+ self._send(self._make_presence(pstatus=status, last_seen=last_seen))
128
+ except _NoChange:
129
+ pass
130
+
131
+ def away(
132
+ self,
133
+ status: Optional[str] = None,
134
+ last_seen: Optional[datetime] = None,
135
+ ):
136
+ """
137
+ Send an "away" presence from this contact to the user.
138
+
139
+ This is a global status, as opposed to :meth:`.LegacyContact.inactive`
140
+ which concerns a specific conversation, ie a specific "chat window"
141
+
142
+ :param status: Arbitrary text, details of the status, eg: "Gone to fight capitalism"
143
+ :param last_seen: For :xep:`0319`
144
+ """
145
+ try:
146
+ self._send(
147
+ self._make_presence(pstatus=status, pshow="away", last_seen=last_seen)
148
+ )
149
+ except _NoChange:
150
+ pass
151
+
152
+ def extended_away(
153
+ self,
154
+ status: Optional[str] = None,
155
+ last_seen: Optional[datetime] = None,
156
+ ):
157
+ """
158
+ Send an "extended away" presence from this contact to the user.
159
+
160
+ This is a global status, as opposed to :meth:`.LegacyContact.inactive`
161
+ which concerns a specific conversation, ie a specific "chat window"
162
+
163
+ :param status: Arbitrary text, details of the status, eg: "Gone to fight capitalism"
164
+ :param last_seen: For :xep:`0319`
165
+ """
166
+ try:
167
+ self._send(
168
+ self._make_presence(pstatus=status, pshow="xa", last_seen=last_seen)
169
+ )
170
+ except _NoChange:
171
+ pass
172
+
173
+ def busy(
174
+ self,
175
+ status: Optional[str] = None,
176
+ last_seen: Optional[datetime] = None,
177
+ ):
178
+ """
179
+ Send a "busy" (ie, "dnd") presence from this contact to the user,
180
+
181
+ :param status: eg: "Trying to make sense of XEP-0100"
182
+ :param last_seen: For :xep:`0319`
183
+ """
184
+ try:
185
+ self._send(
186
+ self._make_presence(pstatus=status, pshow="dnd", last_seen=last_seen)
187
+ )
188
+ except _NoChange:
189
+ pass
190
+
191
+ def offline(
192
+ self,
193
+ status: Optional[str] = None,
194
+ last_seen: Optional[datetime] = None,
195
+ ):
196
+ """
197
+ Send an "offline" presence from this contact to the user.
198
+
199
+ :param status: eg: "Trying to make sense of XEP-0100"
200
+ :param last_seen: For :xep:`0319`
201
+ """
202
+ try:
203
+ self._send(
204
+ self._make_presence(
205
+ pstatus=status, ptype="unavailable", last_seen=last_seen
206
+ )
207
+ )
208
+ except _NoChange:
209
+ pass
210
+
211
+
212
+ def get_last_seen_fallback(last_seen: datetime):
213
+ now = datetime.now(tz=timezone.utc)
214
+ if now - last_seen < timedelta(days=7):
215
+ return f"Last seen {last_seen:%A %H:%M GMT}", True
216
+ else:
217
+ return f"Last seen {last_seen:%b %-d %Y}", False
@@ -0,0 +1,43 @@
1
+ from typing import TYPE_CHECKING, Optional, Union
2
+
3
+ from slixmpp.plugins.xep_0004 import Form
4
+
5
+ from ...util.types import LegacyMessageType
6
+
7
+ if TYPE_CHECKING:
8
+ from ..gateway import BaseGateway
9
+
10
+
11
+ class ReactionRecipientMixin:
12
+ REACTIONS_SINGLE_EMOJI = False
13
+ xmpp: "BaseGateway" = NotImplemented
14
+
15
+ async def restricted_emoji_extended_feature(self):
16
+ available = await self.available_emojis()
17
+ if not self.REACTIONS_SINGLE_EMOJI and available is None:
18
+ return None
19
+
20
+ form = Form()
21
+ form["type"] = "result"
22
+ form.add_field("FORM_TYPE", "hidden", value="urn:xmpp:reactions:0:restrictions")
23
+ if self.REACTIONS_SINGLE_EMOJI:
24
+ form.add_field("max_reactions_per_user", value="1")
25
+ if available:
26
+ form.add_field("allowlist", value=list(available))
27
+ return form
28
+
29
+ async def available_emojis(
30
+ self, legacy_msg_id: Optional[LegacyMessageType] = None
31
+ ) -> Optional[set[str]]:
32
+ """
33
+ Override this to restrict the subset of reactions this recipient
34
+ can handle.
35
+
36
+ :return: A set of emojis or None if any emoji is allowed
37
+ """
38
+ return None
39
+
40
+
41
+ class ThreadRecipientMixin:
42
+ async def create_thread(self, xmpp_id: str) -> Union[int, str]:
43
+ return xmpp_id