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,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