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