slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +54 -31
- slidge/__main__.py +51 -5
- 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 +2 -0
- slidge/core/cache.py +121 -39
- slidge/core/config.py +116 -11
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +895 -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 +795 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +9 -1
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +6 -19
- slidge/core/mixins/disco.py +66 -15
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +254 -252
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +128 -31
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +275 -116
- slidge/core/session.py +586 -518
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +458 -0
- slidge/group/room.py +1103 -0
- slidge/migration.py +18 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/{util → slixfix}/xep_0077/register.py +1 -2
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
- 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/{util → slixfix}/xep_0356_old/privilege.py +9 -7
- 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 +4 -6
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +25 -4
- slidge/util/db.py +23 -69
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +136 -86
- slidge/util/types.py +155 -14
- slidge/util/util.py +225 -51
- slidge-0.1.2.dist-info/METADATA +111 -0
- slidge-0.1.2.dist-info/RECORD +96 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
- slidge/core/adhoc.py +0 -492
- slidge/core/chat_command.py +0 -197
- slidge/core/contact.py +0 -441
- slidge/core/disco.py +0 -59
- slidge/core/gateway.py +0 -899
- slidge/core/muc/__init__.py +0 -3
- slidge/core/muc/bookmarks.py +0 -74
- slidge/core/muc/participant.py +0 -152
- slidge/core/muc/room.py +0 -348
- slidge/plugins/discord/__init__.py +0 -121
- slidge/plugins/discord/client.py +0 -121
- slidge/plugins/discord/session.py +0 -172
- slidge/plugins/dummy.py +0 -334
- slidge/plugins/facebook.py +0 -591
- slidge/plugins/hackernews.py +0 -209
- slidge/plugins/mattermost/__init__.py +0 -1
- slidge/plugins/mattermost/api.py +0 -288
- slidge/plugins/mattermost/gateway.py +0 -417
- slidge/plugins/mattermost/websocket.py +0 -248
- slidge/plugins/signal/__init__.py +0 -4
- slidge/plugins/signal/config.py +0 -4
- slidge/plugins/signal/contact.py +0 -104
- slidge/plugins/signal/gateway.py +0 -379
- slidge/plugins/signal/group.py +0 -76
- slidge/plugins/signal/session.py +0 -515
- slidge/plugins/signal/txt.py +0 -13
- slidge/plugins/signal/util.py +0 -32
- slidge/plugins/skype.py +0 -310
- slidge/plugins/steam.py +0 -400
- slidge/plugins/telegram/__init__.py +0 -6
- slidge/plugins/telegram/client.py +0 -325
- slidge/plugins/telegram/config.py +0 -21
- slidge/plugins/telegram/contact.py +0 -154
- slidge/plugins/telegram/gateway.py +0 -182
- slidge/plugins/telegram/group.py +0 -184
- slidge/plugins/telegram/session.py +0 -275
- slidge/plugins/telegram/util.py +0 -153
- slidge/plugins/whatsapp/__init__.py +0 -6
- slidge/plugins/whatsapp/config.py +0 -17
- slidge/plugins/whatsapp/contact.py +0 -33
- slidge/plugins/whatsapp/event.go +0 -455
- slidge/plugins/whatsapp/gateway.go +0 -156
- slidge/plugins/whatsapp/gateway.py +0 -69
- slidge/plugins/whatsapp/go.mod +0 -17
- slidge/plugins/whatsapp/go.sum +0 -22
- slidge/plugins/whatsapp/session.go +0 -371
- slidge/plugins/whatsapp/session.py +0 -370
- slidge/util/xep_0030/__init__.py +0 -13
- slidge/util/xep_0030/disco.py +0 -811
- slidge/util/xep_0030/stanza/__init__.py +0 -7
- slidge/util/xep_0030/stanza/info.py +0 -270
- slidge/util/xep_0030/stanza/items.py +0 -147
- slidge/util/xep_0030/static.py +0 -467
- slidge/util/xep_0050/adhoc.py +0 -631
- slidge/util/xep_0050/stanza.py +0 -180
- slidge/util/xep_0077/stanza.py +0 -71
- slidge/util/xep_0292/__init__.py +0 -1
- slidge/util/xep_0292/stanza.py +0 -167
- slidge/util/xep_0292/vcard4.py +0 -74
- slidge/util/xep_0356/__init__.py +0 -7
- slidge/util/xep_0356/permissions.py +0 -35
- slidge/util/xep_0356/privilege.py +0 -160
- slidge/util/xep_0356/stanza.py +0 -44
- slidge/util/xep_0461/__init__.py +0 -6
- slidge/util/xep_0461/reply.py +0 -48
- slidge/util/xep_0461/stanza.py +0 -80
- slidge-0.1.0rc1.dist-info/METADATA +0 -171
- slidge-0.1.0rc1.dist-info/RECORD +0 -99
- /slidge/{plugins/__init__.py → py.typed} +0 -0
- /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
import warnings
|
2
|
+
from copy import copy
|
3
|
+
from datetime import datetime, timezone
|
4
|
+
from typing import TYPE_CHECKING, Iterable, Optional, cast
|
5
|
+
from uuid import uuid4
|
6
|
+
|
7
|
+
from slixmpp import Message
|
8
|
+
from slixmpp.types import MessageTypes
|
9
|
+
|
10
|
+
from ...slixfix.link_preview.stanza import LinkPreview as LinkPreviewStanza
|
11
|
+
from ...util.db import GatewayUser
|
12
|
+
from ...util.types import (
|
13
|
+
ChatState,
|
14
|
+
LegacyMessageType,
|
15
|
+
LinkPreview,
|
16
|
+
MessageReference,
|
17
|
+
ProcessingHint,
|
18
|
+
)
|
19
|
+
from .. import config
|
20
|
+
from .base import BaseSender
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from ...group.participant import LegacyParticipant
|
24
|
+
|
25
|
+
|
26
|
+
class MessageMaker(BaseSender):
|
27
|
+
mtype: MessageTypes = NotImplemented
|
28
|
+
_can_send_carbon: bool = NotImplemented
|
29
|
+
STRIP_SHORT_DELAY = False
|
30
|
+
USE_STANZA_ID = False
|
31
|
+
|
32
|
+
def _make_message(
|
33
|
+
self,
|
34
|
+
state: Optional[ChatState] = None,
|
35
|
+
hints: Iterable[ProcessingHint] = (),
|
36
|
+
legacy_msg_id: Optional[LegacyMessageType] = None,
|
37
|
+
when: Optional[datetime] = None,
|
38
|
+
reply_to: Optional[MessageReference] = None,
|
39
|
+
carbon=False,
|
40
|
+
link_previews: Optional[Iterable[LinkPreview]] = None,
|
41
|
+
**kwargs,
|
42
|
+
):
|
43
|
+
body = kwargs.pop("mbody", None)
|
44
|
+
mfrom = kwargs.pop("mfrom", self.jid)
|
45
|
+
mto = kwargs.pop("mto", None)
|
46
|
+
thread = kwargs.pop("thread", None)
|
47
|
+
if carbon and self._can_send_carbon:
|
48
|
+
# the msg needs to have jabber:client as xmlns, so
|
49
|
+
# we don't want to associate with the XML stream
|
50
|
+
msg_cls = Message
|
51
|
+
else:
|
52
|
+
msg_cls = self.xmpp.Message # type:ignore
|
53
|
+
msg = msg_cls(
|
54
|
+
sfrom=mfrom,
|
55
|
+
stype=kwargs.pop("mtype", None) or self.mtype,
|
56
|
+
sto=mto,
|
57
|
+
**kwargs,
|
58
|
+
)
|
59
|
+
if body:
|
60
|
+
msg["body"] = body
|
61
|
+
state = "active"
|
62
|
+
if thread:
|
63
|
+
known_threads = self.session.threads.inverse # type:ignore
|
64
|
+
msg["thread"] = known_threads.get(thread) or str(thread)
|
65
|
+
if state:
|
66
|
+
msg["chat_state"] = state
|
67
|
+
for hint in hints:
|
68
|
+
msg.enable(hint)
|
69
|
+
self._set_msg_id(msg, legacy_msg_id)
|
70
|
+
self._add_delay(msg, when)
|
71
|
+
if link_previews:
|
72
|
+
self._add_link_previews(msg, link_previews)
|
73
|
+
if reply_to:
|
74
|
+
self._add_reply_to(msg, reply_to)
|
75
|
+
return msg
|
76
|
+
|
77
|
+
def _set_msg_id(
|
78
|
+
self, msg: Message, legacy_msg_id: Optional[LegacyMessageType] = None
|
79
|
+
):
|
80
|
+
if legacy_msg_id is not None:
|
81
|
+
i = self._legacy_to_xmpp(legacy_msg_id)
|
82
|
+
msg.set_id(i)
|
83
|
+
if self.USE_STANZA_ID:
|
84
|
+
msg["stanza_id"]["id"] = i
|
85
|
+
msg["stanza_id"]["by"] = self.muc.jid # type: ignore
|
86
|
+
elif self.USE_STANZA_ID:
|
87
|
+
msg["stanza_id"]["id"] = str(uuid4())
|
88
|
+
msg["stanza_id"]["by"] = self.muc.jid # type: ignore
|
89
|
+
|
90
|
+
def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
|
91
|
+
return self.session.sent.get(legacy_id) or self.session.legacy_to_xmpp_msg_id(
|
92
|
+
legacy_id
|
93
|
+
)
|
94
|
+
|
95
|
+
def _add_delay(self, msg: Message, when: Optional[datetime]):
|
96
|
+
if when:
|
97
|
+
if when.tzinfo is None:
|
98
|
+
when = when.astimezone(timezone.utc)
|
99
|
+
if self.STRIP_SHORT_DELAY:
|
100
|
+
delay = datetime.now().astimezone(timezone.utc) - when
|
101
|
+
if delay < config.IGNORE_DELAY_THRESHOLD:
|
102
|
+
return
|
103
|
+
msg["delay"].set_stamp(when)
|
104
|
+
msg["delay"].set_from(self.xmpp.boundjid.bare)
|
105
|
+
|
106
|
+
def _add_reply_to(self, msg: Message, reply_to: MessageReference):
|
107
|
+
xmpp_id = self._legacy_to_xmpp(reply_to.legacy_id)
|
108
|
+
msg["reply"]["id"] = xmpp_id
|
109
|
+
|
110
|
+
muc = getattr(self, "muc", None)
|
111
|
+
|
112
|
+
if entity := reply_to.author:
|
113
|
+
if isinstance(entity, GatewayUser):
|
114
|
+
if muc:
|
115
|
+
jid = copy(muc.jid)
|
116
|
+
jid.resource = fallback_nick = muc.user_nick
|
117
|
+
msg["reply"]["to"] = jid
|
118
|
+
else:
|
119
|
+
msg["reply"]["to"] = entity.jid
|
120
|
+
# TODO: here we should use preferably use the PEP nick of the user
|
121
|
+
# (but it doesn't matter much)
|
122
|
+
fallback_nick = entity.jid.local
|
123
|
+
else:
|
124
|
+
if muc:
|
125
|
+
if hasattr(entity, "muc"):
|
126
|
+
# TODO: accept a Contact here and use muc.get_participant_by_legacy_id()
|
127
|
+
# a bit of work because right now this is a sync function
|
128
|
+
entity = cast("LegacyParticipant", entity)
|
129
|
+
fallback_nick = entity.nickname
|
130
|
+
else:
|
131
|
+
warnings.warn(
|
132
|
+
"The author of a message reference in a MUC must be a"
|
133
|
+
" Participant instance, not a Contact"
|
134
|
+
)
|
135
|
+
fallback_nick = entity.name
|
136
|
+
else:
|
137
|
+
fallback_nick = entity.name
|
138
|
+
msg["reply"]["to"] = entity.jid
|
139
|
+
else:
|
140
|
+
fallback_nick = None
|
141
|
+
|
142
|
+
if fallback := reply_to.body:
|
143
|
+
msg["reply"].add_quoted_fallback(fallback, fallback_nick)
|
144
|
+
|
145
|
+
@staticmethod
|
146
|
+
def _add_link_previews(msg: Message, link_previews: Iterable[LinkPreview]):
|
147
|
+
for preview in link_previews:
|
148
|
+
element = LinkPreviewStanza()
|
149
|
+
for i, name in enumerate(preview._fields):
|
150
|
+
val = preview[i]
|
151
|
+
if not val:
|
152
|
+
continue
|
153
|
+
element[name] = val
|
154
|
+
msg.append(element)
|
slidge/core/mixins/presence.py
CHANGED
@@ -1,43 +1,115 @@
|
|
1
|
-
|
2
|
-
from
|
1
|
+
import re
|
2
|
+
from asyncio import Task, sleep
|
3
|
+
from datetime import datetime, timedelta, timezone
|
3
4
|
from typing import Optional
|
4
5
|
|
6
|
+
from slixmpp.types import PresenceShows, PresenceTypes
|
7
|
+
|
8
|
+
from ...util.sql import CachedPresence, db
|
5
9
|
from .. import config
|
6
10
|
from .base import BaseSender
|
7
11
|
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
class _NoChange(Exception):
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
_FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscribed"}
|
13
18
|
|
14
19
|
|
15
20
|
class PresenceMixin(BaseSender):
|
16
|
-
|
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)
|
17
36
|
|
18
37
|
def _make_presence(
|
19
38
|
self,
|
20
39
|
*,
|
21
40
|
last_seen: Optional[datetime] = None,
|
22
|
-
|
41
|
+
force=False,
|
42
|
+
bare=False,
|
43
|
+
ptype: Optional[PresenceTypes] = None,
|
44
|
+
pstatus: Optional[str] = None,
|
45
|
+
pshow: Optional[PresenceShows] = None,
|
23
46
|
):
|
24
|
-
|
25
|
-
last_seen=last_seen
|
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,
|
26
74
|
)
|
27
|
-
p = self.xmpp.make_presence(pfrom=self.jid, **presence_kwargs)
|
28
75
|
if last_seen:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
+
)
|
33
94
|
p["idle"]["since"] = last_seen
|
34
95
|
return p
|
35
96
|
|
36
|
-
def
|
37
|
-
if (cache := self.
|
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()
|
38
104
|
return
|
39
105
|
self._send(
|
40
|
-
self._make_presence(
|
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
|
+
)
|
41
113
|
)
|
42
114
|
|
43
115
|
def online(
|
@@ -51,7 +123,10 @@ class PresenceMixin(BaseSender):
|
|
51
123
|
:param status: Arbitrary text, details of the status, eg: "Listening to Britney Spears"
|
52
124
|
:param last_seen: For :xep:`0319`
|
53
125
|
"""
|
54
|
-
|
126
|
+
try:
|
127
|
+
self._send(self._make_presence(pstatus=status, last_seen=last_seen))
|
128
|
+
except _NoChange:
|
129
|
+
pass
|
55
130
|
|
56
131
|
def away(
|
57
132
|
self,
|
@@ -67,9 +142,12 @@ class PresenceMixin(BaseSender):
|
|
67
142
|
:param status: Arbitrary text, details of the status, eg: "Gone to fight capitalism"
|
68
143
|
:param last_seen: For :xep:`0319`
|
69
144
|
"""
|
70
|
-
|
71
|
-
self.
|
72
|
-
|
145
|
+
try:
|
146
|
+
self._send(
|
147
|
+
self._make_presence(pstatus=status, pshow="away", last_seen=last_seen)
|
148
|
+
)
|
149
|
+
except _NoChange:
|
150
|
+
pass
|
73
151
|
|
74
152
|
def extended_away(
|
75
153
|
self,
|
@@ -85,7 +163,12 @@ class PresenceMixin(BaseSender):
|
|
85
163
|
:param status: Arbitrary text, details of the status, eg: "Gone to fight capitalism"
|
86
164
|
:param last_seen: For :xep:`0319`
|
87
165
|
"""
|
88
|
-
|
166
|
+
try:
|
167
|
+
self._send(
|
168
|
+
self._make_presence(pstatus=status, pshow="xa", last_seen=last_seen)
|
169
|
+
)
|
170
|
+
except _NoChange:
|
171
|
+
pass
|
89
172
|
|
90
173
|
def busy(
|
91
174
|
self,
|
@@ -93,14 +176,17 @@ class PresenceMixin(BaseSender):
|
|
93
176
|
last_seen: Optional[datetime] = None,
|
94
177
|
):
|
95
178
|
"""
|
96
|
-
Send a "busy" presence from this contact to the user,
|
179
|
+
Send a "busy" (ie, "dnd") presence from this contact to the user,
|
97
180
|
|
98
181
|
:param status: eg: "Trying to make sense of XEP-0100"
|
99
182
|
:param last_seen: For :xep:`0319`
|
100
183
|
"""
|
101
|
-
|
102
|
-
self.
|
103
|
-
|
184
|
+
try:
|
185
|
+
self._send(
|
186
|
+
self._make_presence(pstatus=status, pshow="dnd", last_seen=last_seen)
|
187
|
+
)
|
188
|
+
except _NoChange:
|
189
|
+
pass
|
104
190
|
|
105
191
|
def offline(
|
106
192
|
self,
|
@@ -113,8 +199,19 @@ class PresenceMixin(BaseSender):
|
|
113
199
|
:param status: eg: "Trying to make sense of XEP-0100"
|
114
200
|
:param last_seen: For :xep:`0319`
|
115
201
|
"""
|
116
|
-
|
117
|
-
self.
|
118
|
-
|
202
|
+
try:
|
203
|
+
self._send(
|
204
|
+
self._make_presence(
|
205
|
+
pstatus=status, ptype="unavailable", last_seen=last_seen
|
206
|
+
)
|
119
207
|
)
|
120
|
-
|
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
|