slidge 0.2.3.post1__py3-none-any.whl → 0.2.5__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
slidge/__version__.py CHANGED
@@ -2,4 +2,4 @@ from slidge.util.util import get_version # noqa: F401
2
2
 
3
3
  # this is modified before publish, but if someone cloned from the repo,
4
4
  # it can help
5
- __version__ = "v0.2.3.post1"
5
+ __version__ = "v0.2.5"
slidge/command/base.py CHANGED
@@ -293,12 +293,16 @@ class FormField:
293
293
  return value
294
294
 
295
295
  def __validate_list_multi(self, value: list[str]) -> Union[list[str], list[JID]]:
296
+ # COMPAT: all the "if v" and "if not v" are workarounds for https://codeberg.org/slidge/slidge/issues/43
297
+ # They should be reverted once the bug is fixed upstream, cf https://soprani.ca/todo/390
296
298
  for v in value:
297
299
  if v not in self.__acceptable_options():
300
+ if not v:
301
+ continue
298
302
  raise XMPPError("not-acceptable", f"Not a valid option: '{v}'")
299
303
  if self.type == "list-multi":
300
- return value
301
- return [JID(v) for v in value]
304
+ return [v for v in value if v]
305
+ return [JID(v) for v in value if v]
302
306
 
303
307
  def get_xml(self) -> SlixFormField:
304
308
  """
slidge/contact/contact.py CHANGED
@@ -454,7 +454,7 @@ class LegacyContact(
454
454
  except PermissionError:
455
455
  warnings.warn(
456
456
  "Slidge does not have privileges to add contacts to the roster. Refer"
457
- " to https://slidge.codeberg.page/docs/main/admin/privilege.html for"
457
+ " to https://slidge.im/docs/slidge/main/admin/privilege.html for"
458
458
  " more info."
459
459
  )
460
460
  if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
slidge/core/config.py CHANGED
@@ -187,7 +187,7 @@ MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
187
187
  CORRECTION_EMPTY_BODY_AS_RETRACTION = True
188
188
  CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
189
189
  "Treat last message correction to empty message as a retraction. "
190
- "(this is what cheogram do for retraction)"
190
+ "(this is what cheogram does for retraction)"
191
191
  )
192
192
 
193
193
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
@@ -220,3 +220,14 @@ STRIP_LEADING_EMOJI_ADHOC__DOC = (
220
220
  "Strip the leading emoji in ad-hoc command names, if present, in case you "
221
221
  "are a emoji-hater."
222
222
  )
223
+
224
+ COMPONENT_NAME: Optional[str] = None
225
+ COMPONENT_NAME__DOC = (
226
+ "Overrides the default component name with a custom one. This is seen in service discovery and as the nickname "
227
+ "of the component in chat windows."
228
+ )
229
+
230
+ WELCOME_MESSAGE: Optional[str] = None
231
+ WELCOME_MESSAGE__DOC = (
232
+ "Overrides the default welcome message received by newly registered users."
233
+ )
@@ -296,7 +296,10 @@ class MessageContentMixin(DispatcherMixin):
296
296
  # no need to carbon for groups, we just don't echo the stanza
297
297
  entity.react(legacy_id, carbon=True) # type: ignore
298
298
  await session.on_react(entity, legacy_id, [], thread=thread)
299
- raise XMPPError("not-acceptable", text=error_msg)
299
+ raise XMPPError(
300
+ "policy-violation", # type:ignore
301
+ text=error_msg,
302
+ )
300
303
 
301
304
  await session.on_react(entity, legacy_id, emojis, thread=thread)
302
305
  if isinstance(entity, LegacyMUC):
slidge/core/gateway.py CHANGED
@@ -254,6 +254,10 @@ class BaseGateway(
254
254
  http: aiohttp.ClientSession
255
255
 
256
256
  def __init__(self):
257
+ if config.COMPONENT_NAME:
258
+ self.COMPONENT_NAME = config.COMPONENT_NAME
259
+ if config.WELCOME_MESSAGE:
260
+ self.WELCOME_MESSAGE = config.WELCOME_MESSAGE
257
261
  self.log = log
258
262
  self.datetime_started = datetime.now()
259
263
  self.xmpp = self # ugly hack to work with the BaseSender mixin :/
@@ -416,8 +420,14 @@ class BaseGateway(
416
420
  await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
417
421
 
418
422
  if self.COMPONENT_AVATAR is not None:
419
- cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
420
- self.avatar_pk = cached_avatar.pk
423
+ try:
424
+ cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
425
+ except Exception as e:
426
+ log.exception("Could not set the component avatar.", exc_info=e)
427
+ cached_avatar = None
428
+ else:
429
+ assert cached_avatar is not None
430
+ self.avatar_pk = cached_avatar.pk
421
431
  else:
422
432
  cached_avatar = None
423
433
 
@@ -913,7 +923,9 @@ SLIXMPP_PLUGINS = [
913
923
  "xep_0444", # Message reactions
914
924
  "xep_0447", # Stateless File Sharing
915
925
  "xep_0461", # Message replies
926
+ "xep_0469", # Bookmark Pinning
916
927
  "xep_0490", # Message Displayed Synchronization
928
+ "xep_0492", # Chat Notification Settings
917
929
  ]
918
930
 
919
931
  LOG_STRIP_ELEMENTS = ["data", "binval"]
@@ -178,6 +178,7 @@ class AttachmentMixin(TextMessageMixin):
178
178
  file_path = temp_dir / file_name
179
179
  if file_url:
180
180
  async with self.session.http.get(file_url) as r:
181
+ r.raise_for_status()
181
182
  with file_path.open("wb") as f:
182
183
  f.write(await r.read())
183
184
 
@@ -183,7 +183,7 @@ class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
183
183
  warnings.warn(
184
184
  "Slidge does not have privileges to send message on behalf of"
185
185
  " user.Refer to"
186
- " https://slidge.codeberg.page/docs/main/admin/privilege.html"
186
+ " https://slidge.im/docs/slidge/main/admin/privilege.html"
187
187
  " for more info."
188
188
  )
189
189
 
@@ -21,9 +21,9 @@ class ReactionRecipientMixin:
21
21
  form["type"] = "result"
22
22
  form.add_field("FORM_TYPE", "hidden", value="urn:xmpp:reactions:0:restrictions")
23
23
  if self.REACTIONS_SINGLE_EMOJI:
24
- form.add_field("max_reactions_per_user", value="1")
24
+ form.add_field("max_reactions_per_user", value="1", type="number")
25
25
  if available:
26
- form.add_field("allowlist", value=list(available))
26
+ form.add_field("allowlist", value=list(available), type="text-multi")
27
27
  return form
28
28
 
29
29
  async def available_emojis(
slidge/core/pubsub.py CHANGED
@@ -207,7 +207,8 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
207
207
  ) -> PepAvatar:
208
208
  if stanza.get_to() == self.xmpp.boundjid.bare:
209
209
  item = PepAvatar()
210
- item.set_avatar_from_cache(avatar_cache.get_by_pk(self.xmpp.avatar_pk))
210
+ if hasattr(self.xmpp, "avatar_pk"):
211
+ item.set_avatar_from_cache(avatar_cache.get_by_pk(self.xmpp.avatar_pk))
211
212
  return item
212
213
 
213
214
  if contact is None:
slidge/db/avatar.py CHANGED
@@ -14,6 +14,7 @@ from multidict import CIMultiDictProxy
14
14
  from PIL.Image import Image
15
15
  from PIL.Image import open as open_image
16
16
  from sqlalchemy import select
17
+ from sqlalchemy.orm.exc import DetachedInstanceError
17
18
 
18
19
  from slidge.core import config
19
20
  from slidge.db.models import Avatar
@@ -102,6 +103,7 @@ class AvatarCache:
102
103
  if response.status == HTTPStatus.NOT_MODIFIED:
103
104
  log.debug("Using avatar cache for %s", url)
104
105
  raise NotModified
106
+ response.raise_for_status()
105
107
  return (
106
108
  open_image(io.BytesIO(await response.read())),
107
109
  response.headers,
@@ -141,7 +143,24 @@ class AvatarCache:
141
143
  )
142
144
  except NotModified:
143
145
  assert stored is not None
144
- return CachedAvatar.from_store(stored, self.dir)
146
+ try:
147
+ return CachedAvatar.from_store(stored, self.dir)
148
+ except DetachedInstanceError:
149
+ # This is an awful hack to prevent errors on startup under certain conditions,
150
+ # because we basically misused SQLAlchemy pretty bad in slidge.db.store.EngineMixin.session().
151
+ # cf https://codeberg.org/slidge/slidge/issues/36
152
+ # and https://docs.sqlalchemy.org/en/20/orm/session_basics.html#session-faq-threadsafe
153
+ # It may be related to threads as we only have reports of this for slidge-whatsapp and skidge
154
+ # which are the only implementations that uses threads.
155
+ # In any case, a proper fix implies a major refactoring in which we spawn and close SQLAlchemy
156
+ # "ORM Session"s with a reasonable, well-thought lifetime, instead of how we do it now, where we
157
+ # basically just brute-forced our way into having something usable but with poor performance.
158
+ # Databases, asyncio, and concurrency in general are hard… :(
159
+ # Oh, and getting rid of the convoluted mess that this giant method is would also probably
160
+ # be a good idea.
161
+ stored = self.store.get_by_url(avatar)
162
+ assert stored is not None
163
+ return CachedAvatar.from_store(stored, self.dir)
145
164
  else:
146
165
  img = await self._get_image(avatar)
147
166
  response_headers = None
slidge/db/store.py CHANGED
@@ -58,6 +58,8 @@ class EngineMixin:
58
58
  def __init__(self, engine: Engine):
59
59
  self._engine = engine
60
60
 
61
+ # TODO: we should not have a global Session object but instead build Sessions with different parameters
62
+ # depending on the context (startup, incoming XMPP event, incoming legacy event).
61
63
  @contextmanager
62
64
  def session(self, **session_kwargs) -> Iterator[Session]:
63
65
  global _session
@@ -70,6 +70,7 @@ class LegacyParticipant(
70
70
  is_system=False,
71
71
  role: MucRole = "participant",
72
72
  affiliation: MucAffiliation = "member",
73
+ resource: str | None = None,
73
74
  ):
74
75
  self.session = session = muc.session
75
76
  self.xmpp = session.xmpp
@@ -83,7 +84,13 @@ class LegacyParticipant(
83
84
 
84
85
  self._nickname = nickname
85
86
 
86
- self.__update_jid(nickname)
87
+ if resource is None:
88
+ self.__update_jid(nickname)
89
+ else:
90
+ self._nickname_no_illegal = resource
91
+ self.jid = JID(self.muc.jid)
92
+ self.jid.resource = resource
93
+
87
94
  log.debug("Instantiation of: %r", self)
88
95
 
89
96
  self.contact: Optional["LegacyContact"] = None
@@ -510,6 +517,7 @@ class LegacyParticipant(
510
517
  stored.nickname,
511
518
  role=stored.role,
512
519
  affiliation=stored.affiliation,
520
+ resource=stored.resource,
513
521
  )
514
522
  part.pk = stored.id
515
523
  if contact is not None:
slidge/group/room.py CHANGED
@@ -26,6 +26,7 @@ from ..core.mixins.disco import ChatterDiscoMixin
26
26
  from ..core.mixins.lock import NamedLockMixin
27
27
  from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
28
28
  from ..db.models import Room
29
+ from ..slixfix.xep_0492.stanza import WhenLiteral
29
30
  from ..util import ABCSubclassableOnceAtMost
30
31
  from ..util.types import (
31
32
  HoleBound,
@@ -994,7 +995,14 @@ class LegacyMUC(
994
995
  p["muc"]["status_codes"] = {110, 333}
995
996
  p.send()
996
997
 
997
- async def add_to_bookmarks(self, auto_join=True, invite=False, preserve=True):
998
+ async def add_to_bookmarks(
999
+ self,
1000
+ auto_join=True,
1001
+ invite=False,
1002
+ preserve=True,
1003
+ pin: bool | None = None,
1004
+ notify: WhenLiteral | None = None,
1005
+ ):
998
1006
  """
999
1007
  Add the MUC to the user's XMPP bookmarks (:xep:`0402')
1000
1008
 
@@ -1012,6 +1020,11 @@ class LegacyMUC(
1012
1020
  settings.
1013
1021
  :param preserve: preserve auto-join and bookmarks extensions
1014
1022
  set by the user outside slidge
1023
+ :param pin: Pin the group chat bookmark :xep:`0469`. Requires privileged entity.
1024
+ If set to ``None`` (default), the bookmark pinning status will be untouched.
1025
+ :param notify: Chat notification setting: :xep:`0492`. Requires privileged entity.
1026
+ If set to ``None`` (default), the setting will be untouched. Only the "global"
1027
+ notification setting is supported (ie, per client type is not possible).
1015
1028
  """
1016
1029
  item = Item()
1017
1030
  item["id"] = self.jid
@@ -1046,6 +1059,13 @@ class LegacyMUC(
1046
1059
  item["conference"]["autojoin"] = auto_join
1047
1060
 
1048
1061
  item["conference"]["nick"] = self.user_nick
1062
+
1063
+ if pin is not None:
1064
+ item["conference"]["extensions"]["pinned"] = pin
1065
+
1066
+ if notify is not None:
1067
+ item["conference"]["extensions"]["notify"].configure(notify)
1068
+
1049
1069
  iq = Iq(stype="set", sfrom=self.user_jid, sto=self.user_jid)
1050
1070
  iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0402"].stanza.NS
1051
1071
  iq["pubsub"]["publish"].append(item)
@@ -5,17 +5,30 @@
5
5
 
6
6
  import slixmpp.plugins
7
7
  from slixmpp import Iq, Message
8
- from slixmpp.exceptions import XMPPError
8
+ from slixmpp.exceptions import _DEFAULT_ERROR_TYPES, XMPPError
9
+ from slixmpp.plugins.xep_0004.stanza.field import FormField
9
10
  from slixmpp.plugins.xep_0050 import XEP_0050, Command
10
11
  from slixmpp.plugins.xep_0231 import XEP_0231
11
12
  from slixmpp.plugins.xep_0425.stanza import Moderate
13
+ from slixmpp.plugins.xep_0469.stanza import NS as PINNED_NS
14
+ from slixmpp.plugins.xep_0469.stanza import Pinned
12
15
  from slixmpp.xmlstream import StanzaBase
13
16
 
14
- from . import link_preview, xep_0077, xep_0100, xep_0153, xep_0292, xep_0356_old
17
+ from . import (
18
+ link_preview,
19
+ xep_0077,
20
+ xep_0100,
21
+ xep_0153,
22
+ xep_0292,
23
+ xep_0356_old,
24
+ xep_0492,
25
+ )
15
26
 
16
27
  # TODO: remove this when the fix is included in slixmpp
17
28
  Moderate.interfaces.add("id")
18
29
 
30
+ _DEFAULT_ERROR_TYPES["policy-violation"] = "modify" # type:ignore
31
+
19
32
 
20
33
  async def _handle_bob_iq(self, iq: Iq):
21
34
  cid = iq["bob"]["cid"]
@@ -42,6 +55,17 @@ async def _handle_bob_iq(self, iq: Iq):
42
55
  iq.send()
43
56
 
44
57
 
58
+ def set_pinned(self, val: bool):
59
+ extensions = self.parent()
60
+ if val:
61
+ extensions.enable("pinned")
62
+ else:
63
+ extensions._del_sub(f"{{{PINNED_NS}}}pinned")
64
+
65
+
66
+ Pinned.set_pinned = set_pinned
67
+
68
+
45
69
  XEP_0231._handle_bob_iq = _handle_bob_iq
46
70
 
47
71
 
@@ -75,13 +99,56 @@ def reply(self, body=None, clear=True):
75
99
  return new_message
76
100
 
77
101
 
102
+ Message.reply = reply # type: ignore
103
+
104
+
105
+ FormField.set_value_base = FormField.set_value # type:ignore
106
+
107
+
108
+ def set_value(self: FormField, value):
109
+ if not self._type:
110
+ if isinstance(value, bool):
111
+ self._type = "boolean"
112
+ elif isinstance(value, str):
113
+ self._type = "text-single"
114
+ elif isinstance(value, (list, tuple)):
115
+ self._type = "text-multi"
116
+
117
+ FormField.set_value_base(self, value)
118
+
119
+
120
+ def get_value(self, convert=True, convert_list=False):
121
+ valsXML = self.xml.findall("{%s}value" % self.namespace)
122
+ if len(valsXML) == 0:
123
+ return None
124
+ elif self._type == "boolean":
125
+ if convert:
126
+ return valsXML[0].text in self.true_values
127
+ return valsXML[0].text
128
+ elif self._type in self.multi_value_types or len(valsXML) > 1:
129
+ values = []
130
+ for valXML in valsXML:
131
+ if valXML.text is None:
132
+ valXML.text = ""
133
+ values.append(valXML.text)
134
+ if self._type == "text-multi" and convert_list:
135
+ values = "\n".join(values)
136
+ return values
137
+ else:
138
+ if valsXML[0].text is None:
139
+ return ""
140
+ return valsXML[0].text
141
+
142
+
143
+ FormField.set_value = set_value # type:ignore
144
+ FormField.get_value = get_value # type:ignore
145
+
146
+
78
147
  slixmpp.plugins.PLUGINS.extend(
79
148
  [
80
149
  "link_preview",
81
150
  "xep_0292_provider",
82
151
  "xep_0356_old",
152
+ "xep_0492",
83
153
  ]
84
154
  )
85
-
86
-
87
- Message.reply = reply # type: ignore
@@ -0,0 +1,8 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from . import stanza
4
+ from .notify import XEP_0492
5
+
6
+ register_plugin(XEP_0492)
7
+
8
+ __all__ = ["stanza", "XEP_0492"]
@@ -0,0 +1,16 @@
1
+ from slixmpp.plugins import BasePlugin
2
+ from . import stanza
3
+
4
+
5
+ class XEP_0492(BasePlugin):
6
+ """
7
+ XEP-0492: Chat notification settings
8
+ """
9
+
10
+ name = "xep_0492"
11
+ description = "XEP-0492: Chat notification settings"
12
+ dependencies = {"xep_0402"}
13
+ stanza = stanza
14
+
15
+ def plugin_init(self):
16
+ stanza.register_plugin()
@@ -0,0 +1,102 @@
1
+ from typing import Literal, Optional, cast
2
+
3
+ from slixmpp import register_stanza_plugin
4
+ from slixmpp.plugins.xep_0402.stanza import Extensions
5
+ from slixmpp.xmlstream import ElementBase
6
+
7
+ from ...util.types import ClientType
8
+
9
+ NS = "urn:xmpp:notification-settings:0"
10
+
11
+ WhenLiteral = Literal["never", "always", "on-mention"]
12
+
13
+
14
+ class Notify(ElementBase):
15
+ """
16
+ Chat notification settings element
17
+
18
+
19
+ To enable it on a Conference element, use configure() like this:
20
+
21
+ .. code-block::python
22
+
23
+ # C being a Conference element
24
+ C['extensions']["notify"].configure("always", client_type="pc")
25
+
26
+ Which will add the <notify> element to the <extensions> element.
27
+ """
28
+
29
+ namespace = NS
30
+ name = "notify"
31
+ plugin_attrib = "notify"
32
+ interfaces = {"notify"}
33
+
34
+ def configure(self, when: WhenLiteral, client_type: Optional[ClientType] = None) -> None:
35
+ """
36
+ Configure the chat notification settings for this bookmark.
37
+
38
+ This method ensures that there are no conflicting settings, e.g.,
39
+ both a <never /> and a <always /> element.
40
+ """
41
+ cls = _CLASS_MAP[when]
42
+ element = cls()
43
+ if client_type is not None:
44
+ element["client-type"] = client_type
45
+
46
+ match = client_type if client_type is not None else ""
47
+ for child in self:
48
+ if isinstance(child, _Base) and child["client-type"] == match:
49
+ self.xml.remove(child.xml)
50
+
51
+ self.append(element)
52
+
53
+ def get_config(
54
+ self, client_type: Optional[ClientType] = None
55
+ ) -> Optional[WhenLiteral]:
56
+ """
57
+ Get the chat notification settings for this bookmark.
58
+
59
+ :param client_type: Optionally, get the notification for a specific client type.
60
+ If unset, returns the global notification setting.
61
+
62
+ :return: The chat notification setting as a string, or None if unset.
63
+ """
64
+ match = client_type if client_type is not None else ""
65
+ for child in self:
66
+ if isinstance(child, _Base) and child["client-type"] == match:
67
+ return cast(WhenLiteral, child.name)
68
+ return None
69
+
70
+
71
+ class _Base(ElementBase):
72
+ namespace = NS
73
+ interfaces = {"client-type"}
74
+
75
+
76
+ class Never(_Base):
77
+ name = "never"
78
+
79
+
80
+ class Always(_Base):
81
+ name = "always"
82
+
83
+
84
+ class OnMention(_Base):
85
+ name = "on-mention"
86
+
87
+
88
+ class Advanced(ElementBase):
89
+ namespace = NS
90
+ name = plugin_attrib = "advanced"
91
+
92
+
93
+ _CLASS_MAP = {
94
+ "never": Never,
95
+ "always": Always,
96
+ "on-mention": OnMention,
97
+ }
98
+
99
+
100
+ def register_plugin():
101
+ register_stanza_plugin(Extensions, Notify)
102
+ register_stanza_plugin(Notify, Advanced)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: slidge
3
- Version: 0.2.3.post1
3
+ Version: 0.2.5
4
4
  Summary: XMPP bridging framework
5
5
  Author-email: Nicolas Cedilnik <nicoco@nicoco.fr>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -669,7 +669,8 @@ Project-URL: Homepage, https://codeberg.org/slidge/
669
669
  Project-URL: Issues, https://codeberg.org/slidge/slidge/issues
670
670
  Project-URL: Repository, https://codeberg.org/slidge/slidge/
671
671
  Project-URL: Chat room, https://conference.nicoco.fr:5281/muc_log/slidge/
672
- Project-URL: Documentation, https://slidge.codeberg.page/docs/main
672
+ Project-URL: Documentation, https://slidge.im/docs/slidge/main
673
+ Project-URL: changelog, https://codeberg.org/slidge/slidge/releases
673
674
  Keywords: xmpp,gateway,bridge,instant messaging
674
675
  Classifier: Topic :: Internet :: XMPP
675
676
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -693,14 +694,14 @@ Requires-Dist: thumbhash>=0.1.2
693
694
 
694
695
 
695
696
  [![woodpecker CI status](https://ci.codeberg.org/api/badges/14027/status.svg)](https://ci.codeberg.org/repos/14027)
696
- [![coverage](https://slidge.codeberg.page/coverage/main/coverage.svg)](https://slidge.im/coverage)
697
+ [![coverage](https://slidge.im/coverage/main/coverage.svg)](https://slidge.im/coverage/main)
697
698
 
698
699
  [![pypi version](https://badge.fury.io/py/slidge.svg)](https://pypi.org/project/slidge/)
699
700
  [![debian unstable version](https://badges.debian.net/badges/debian/unstable/python3-slidge/version.svg)](https://packages.debian.org/unstable/python3-slidge)
700
701
 
701
702
  Slidge is an XMPP (puppeteer) gateway library in python.
702
703
  It makes
703
- [writing gateways to other chat networks](https://slidge.im/core/dev/tutorial.html)
704
+ [writing gateways to other chat networks](https://slidge.im/docs/slidge/main/dev/tutorial.html)
704
705
  (*legacy modules*) as frictionless as possible.
705
706
  It supports fancy IM features, such as
706
707
  [(emoji) reactions](https://xmpp.org/extensions/xep-0444.html),
@@ -751,7 +752,7 @@ class Session(BaseSession):
751
752
  self.legacy_client.send_message(text=text, destination=chat.legacy_id)
752
753
  ```
753
754
 
754
- There's more in [the tutorial](https://slidge.codeberg.page/docs/main/dev/tutorial.html)!
755
+ There's more in [the tutorial](https://slidge.im/docs/slidge/main/dev/tutorial.html)!
755
756
 
756
757
  Installation
757
758
  ------------
@@ -766,7 +767,7 @@ bundle.
766
767
  Slidge is available on
767
768
  [codeberg](https://codeberg.org/slidge/-/packages) (python packages and containers)
768
769
  and [pypi](https://pypi.org/project/slidge/).
769
- Refer to [the docs](https://slidge.codeberg.page/docs/main/admin/install.html) for details.
770
+ Refer to [the docs](https://slidge.im/docs/slidge/main/admin/install.html) for details.
770
771
 
771
772
  About privacy
772
773
  -------------
@@ -1,24 +1,24 @@
1
1
  slidge/__init__.py,sha256=S0tUjqpZlzsr8G4Y_1Xt-KCYB07qaknTB0OwHU8k29U,1587
2
2
  slidge/__main__.py,sha256=ydjUklOoavS4YlGfjRX_8BQN2DaSbaXPMi47RkOgcFI,37
3
- slidge/__version__.py,sha256=i4LQytjT85nU1wHx0KxMZ2wULDy9KnXdNBwMO7WBmNk,171
3
+ slidge/__version__.py,sha256=4bAi8qOyNOCy78XyBLZqa2BfhBAl80d4H4U1rf4i8-k,165
4
4
  slidge/main.py,sha256=vMJzhvUxbeuIXuHxXXs6lm_ShBjXiS9B5Li5Ge4vWPo,6238
5
5
  slidge/migration.py,sha256=4BJmPIRB56_WIhRTqBFIIBXuvnhhBjjOMl4CE7jY6oc,1541
6
6
  slidge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  slidge/command/__init__.py,sha256=UYf1mjCYbZ5G7PIgaFTWSQRAzEJkQ6dTH8Fu_e_XnO0,613
8
8
  slidge/command/adhoc.py,sha256=-AO4h1N6owSuuqZon5tDL29O6qmEeAd1pcPjGCkzKRs,10065
9
9
  slidge/command/admin.py,sha256=TYrzgCIhjcTIwl1IUaFlUd3D98SPyao10gB20zo8b3Q,6187
10
- slidge/command/base.py,sha256=S-4WnxDuGE3sQTca5L6DjHVBsHqpNuf6zvXffYoP46c,13159
10
+ slidge/command/base.py,sha256=EDcEl5dJcooSmLarXI2fmBq6QtU7h-7MOM3DDsxXmTU,13447
11
11
  slidge/command/categories.py,sha256=vF0KGDV9sEn8TNkcMoDRw-u3gEyNHSXghOU2JRHQtKs,351
12
12
  slidge/command/chat_command.py,sha256=z-4qp03rK7kCh3_kEozDViwkDg_hVjHvRCiYYJxedBQ,11153
13
13
  slidge/command/register.py,sha256=BduDI31Kx8CbWWEdjybimTA5Wcfhn-Jkt8sSPsySCpo,6724
14
14
  slidge/command/user.py,sha256=aksfkRUK7xj9n0XM-IfUSS5LrTp0qIHt5K2UkBBT1UE,12099
15
15
  slidge/contact/__init__.py,sha256=WMMaHk7UW7YT9EH2LtPdkU0bHQaOp4ikBhbBQskmoc8,191
16
- slidge/contact/contact.py,sha256=BQ5Fw7InnI76S5bQi_kZL1HhtptZhegmtsKksml7tjY,23125
16
+ slidge/contact/contact.py,sha256=SiFeDoI1zuGKhVoK8O_Jm1ch9jwOsRINKgt1gijrmcc,23121
17
17
  slidge/contact/roster.py,sha256=x3speGdHbZ-VTLoQLQW4s53rBeBvW87W8ZibCCZSLDA,10300
18
18
  slidge/core/__init__.py,sha256=RG7Jj5JCJERjhqJ31lOLYV-7bH_oblClQD1KF9LsTXo,68
19
- slidge/core/config.py,sha256=WP3-ScXqdAhJBX7IRB5pBi_tAV_vE6G5W3Z-LGGULQw,7691
20
- slidge/core/gateway.py,sha256=m7XN4ACd_txLl4zoAJNJjKT0BFJu6yUfJmhVfUkV7XM,36512
21
- slidge/core/pubsub.py,sha256=oTiS5KFQJAmsgkhOsvfvthT-LkuZGQSCrrUG0JskNkI,11907
19
+ slidge/core/config.py,sha256=9oIrXYB4Fx9uVQkMYDswrd0xr0ieEH8r1EkGym-AxBE,8060
20
+ slidge/core/gateway.py,sha256=S2fLp_KLPyoixlTQzvlrlus55dHFTYp-WkLZNSWw9mE,37022
21
+ slidge/core/pubsub.py,sha256=BoeYE__ptmRAn4x55Hn_6JWRA4nM-XJgDemG5Cy5kN4,11959
22
22
  slidge/core/session.py,sha256=nQexpCd1jlHOhQPnFI4ri-5odp3N2pU5HO4l7WFetZY,28148
23
23
  slidge/core/dispatcher/__init__.py,sha256=1EXcjXietUKlxEqdrCWCV3xZ3q_DSsjHoqWrPMbtYao,84
24
24
  slidge/core/dispatcher/caps.py,sha256=vzCAXo_bhALuLEpJWtyJTzVfWx96g1AsWD8_wkoDl0Y,2028
@@ -32,7 +32,7 @@ slidge/core/dispatcher/vcard.py,sha256=Rmx-wCz6Lps0mXCO48HppNQlS3GOgMuzuw9hZYBdl
32
32
  slidge/core/dispatcher/message/__init__.py,sha256=vpDGOc_U9XvkUU_ws9n9-5M2NPJ87XGTVpuIxM7Z99k,223
33
33
  slidge/core/dispatcher/message/chat_state.py,sha256=sCdEpzbgmvBmTovNOCv9uY6v0eJZcWVvDYAGlAV3FJ4,1735
34
34
  slidge/core/dispatcher/message/marker.py,sha256=f1ezaMoHupBFZY7aUMsWLAQG7G1J9b3ihxICCkpGtis,2411
35
- slidge/core/dispatcher/message/message.py,sha256=HwauW2kGionLyDWG01OSa9a14gYzoovJuJvGbfB4nt4,15296
35
+ slidge/core/dispatcher/message/message.py,sha256=07h7OBbIh3OtSbi7DNwv5ANRTzWbwS9ICMHjS_6KpEM,15360
36
36
  slidge/core/dispatcher/muc/__init__.py,sha256=V8URHLJ_y7mk-7Id6FzRuczb1Uq_Z69fhxvzHuVLH1w,269
37
37
  slidge/core/dispatcher/muc/admin.py,sha256=mkosquhaI2iC_mFq7C08kjwjr4iVqAWp7kbOTmgUQtU,3321
38
38
  slidge/core/dispatcher/muc/mam.py,sha256=1ROVP4ZPEVEH-HR5qRV4YwHz-V15uu5gyhv1ZwwKhk8,2821
@@ -40,22 +40,22 @@ slidge/core/dispatcher/muc/misc.py,sha256=bHBjMC-Pu3jR5hAPGMzXf-C05UbACIwg38YbJU
40
40
  slidge/core/dispatcher/muc/owner.py,sha256=1a6YV7b_mmi1jC6q1ko8weeL8imQA-s-hYGPLIHd10I,3308
41
41
  slidge/core/dispatcher/muc/ping.py,sha256=lb1VQPhiUPZ19KhbofRXMVCcY6wwQ2w-asnqtANaAwA,1660
42
42
  slidge/core/mixins/__init__.py,sha256=muReAzgvENgMvlfm0Fpe6BQFfm2EMjoDe9ZhGgo6Vig,627
43
- slidge/core/mixins/attachment.py,sha256=sdRo_WMaWnWr00bM3AYyZRAsP0TlgwqkdHd1i2EAkCg,20133
43
+ slidge/core/mixins/attachment.py,sha256=rZcipXiGgo-8klumBvfgrSsU_rmS6cl1zx-zev_FZRg,20174
44
44
  slidge/core/mixins/avatar.py,sha256=BVOeH5j5tsPJ0IHUcB5ozpyh02TBPKiZMd9MYizDokA,8136
45
45
  slidge/core/mixins/base.py,sha256=MOd-pas38_52VawQVlxWtBtmTKC6My9G0ZaCeQxOJbs,748
46
46
  slidge/core/mixins/db.py,sha256=5Qpegd7D8e5TLXLLINYcf_DuVdN-7wNmsfztUuFYPcU,442
47
47
  slidge/core/mixins/disco.py,sha256=jk3Z1B6zTuisHv8VKNRJodIo0ee5btYHh2ZrlflPj_Q,3670
48
48
  slidge/core/mixins/lock.py,sha256=Vf1rrkbyNbSprr38WGfZiMgTB7AdbqH8ppFHY8N2yXE,975
49
- slidge/core/mixins/message.py,sha256=5Tt1r_8rjsV445b_28Lx73S-rW5tsP1uGPQhIKN64VM,7998
49
+ slidge/core/mixins/message.py,sha256=0dPpDQwkdAdirUodKpb9Ad7mkr5meWc7S3lf0sXPYnc,7994
50
50
  slidge/core/mixins/message_maker.py,sha256=TcCutHi0sIwL6beJNkN7XyR0aDIbA0xZyxd2Gc9ulG4,6022
51
51
  slidge/core/mixins/message_text.py,sha256=pCY4tezEuwB2ZuUyUi72i4v9AJkxp_SWF1jrFsn94Ns,8096
52
52
  slidge/core/mixins/presence.py,sha256=yywo6KAw8C7GaZSMrSMuioNfhW08MrnobHt8XbHd0q8,7891
53
- slidge/core/mixins/recipient.py,sha256=U-YppozUO8pA94jmD3-qmhkykTebPNaOVWc3JDPC9w8,1302
53
+ slidge/core/mixins/recipient.py,sha256=b0uFnpym-hOFgYxGjXT1xQcZ4YRbDSBftPcNWLzSwEI,1336
54
54
  slidge/db/__init__.py,sha256=EBDH1JSEhgqYcli2Bw11CRC749wJk8AOucgBzmhDSvU,105
55
- slidge/db/avatar.py,sha256=FfRt2Vu11ZKD9F3x1_drawvUd-TDE3mp7SE3BZ9hOOg,6467
55
+ slidge/db/avatar.py,sha256=z5e72STv8PdN6zkNyKlLqF7NFxHwCa6IjwgFpzu5ghE,8033
56
56
  slidge/db/meta.py,sha256=v1Jf-npZ28QwdGpsLQWLBHEbEP3-jnPrygRg05tJ_Iw,1831
57
57
  slidge/db/models.py,sha256=0NUJfa3lPKHhwV2zE4p7QVYCVs3yn9egFg2u9mssk5c,13964
58
- slidge/db/store.py,sha256=4QyK6rjztPdmhjsqxMookEK_WrdlzOpPFPWnCpKy2u4,46704
58
+ slidge/db/store.py,sha256=A2F8QJ88INMSdw0S8G5wDAQJFjwpeExpLuwHu8T3ZHU,46904
59
59
  slidge/db/alembic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  slidge/db/alembic/env.py,sha256=hsBlRNs0zF5diSHGRSa8Fi3qRVQDA2rJdR41AEIdvxc,1642
61
61
  slidge/db/alembic/old_user_store.py,sha256=zFOv0JEWQQK0_TMRlU4Z0G5Mc9pxvEErLyOzXmRAe5Q,5209
@@ -80,9 +80,9 @@ slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py,sha2
80
80
  slidge/group/__init__.py,sha256=yFt7cHqeaKIMN6f9ZyhhspOcJJvBtLedGv-iICG7lto,258
81
81
  slidge/group/archive.py,sha256=IPqklzo0UN3lPHckfsKW9c4nl3m_9XGY4u0eehrhe8k,5281
82
82
  slidge/group/bookmarks.py,sha256=AvFL34bEX6n3OP1Np309T5hrLK9GnjkjdyLJ3uiLZyc,6616
83
- slidge/group/participant.py,sha256=epxXqJ545ALkEYxziiobH149nlBeSZlxsNCYeI8LFhM,17357
84
- slidge/group/room.py,sha256=kPy-5bWT8215I1MPJnCCPcaDfSRMiqXx6Fta9BRpLVk,46043
85
- slidge/slixfix/__init__.py,sha256=epiEsPNTWGF7uEhbL-cJaUyvPCbku8ZGNj3hlL7ofvc,2544
83
+ slidge/group/participant.py,sha256=tGGcAkdcK6aob1YP8e9ODbdK2_dGmFNH0F2x-gHyc4M,17611
84
+ slidge/group/room.py,sha256=aJ-VIYACuEuoyKDsBFxsUZFoVpd265i0mzTOOHCNTPg,46849
85
+ slidge/slixfix/__init__.py,sha256=aEXtmxMgxQWsUoDV5NSxZKwb-Hml5XeR2FMJlnaDUng,4324
86
86
  slidge/slixfix/delivery_receipt.py,sha256=3bWdZH3-X3CZJXmnI_TpjkTUUK-EY4Ktm78lW0-40fc,1366
87
87
  slidge/slixfix/roster.py,sha256=KvDjh9q7pqaZf69H93okfib13cc95uVZUJ6rzpqmDaU,1704
88
88
  slidge/slixfix/link_preview/__init__.py,sha256=TDPTSEH5FQxgGpQpQIde-D72AHg-6YVWG-tOj4KpKmU,290
@@ -101,6 +101,9 @@ slidge/slixfix/xep_0292/vcard4.py,sha256=jL-TOW3eG2QXLduSLNq03L8HoUNmvy8kTZI5ojv
101
101
  slidge/slixfix/xep_0356_old/__init__.py,sha256=3jGWJX2m5gWgDCxcVqCsCCVPRTcfmU96yenwvAJtOKE,180
102
102
  slidge/slixfix/xep_0356_old/privilege.py,sha256=kcJzFbzhOHtQMtzOJpvvwm1pghSpealWnqhC0zc8dGo,5338
103
103
  slidge/slixfix/xep_0356_old/stanza.py,sha256=i7aqcaTg6PBhVwbHToLtlrwxBj7uO-M7VrYSyElyEKI,1229
104
+ slidge/slixfix/xep_0492/__init__.py,sha256=kjWVeX3SG_2ohHx0fuMh1gmM2G57Bl6SRo7Mfv6sglA,161
105
+ slidge/slixfix/xep_0492/notify.py,sha256=8EPSdU3rTzWkHNm8oFr0tK2PmMJ6hBAIr88GoOmHTuQ,340
106
+ slidge/slixfix/xep_0492/stanza.py,sha256=gL0ixpV07Q9eqTXIOjdLVPtim45izuwaLk0iCoZ8e7c,2649
104
107
  slidge/util/__init__.py,sha256=BELovoTMPcPPGz3D48esBr8A4BRRHXTvavfgnArBgEc,301
105
108
  slidge/util/archive_msg.py,sha256=xXAR0BI5r3d6KKWjae9594izCOv6iI03z2WLuTecNw8,1724
106
109
  slidge/util/conf.py,sha256=1j2OnOsCBar1tOObErhXR5RC3Vl3faliOZ1U8J3My58,6613
@@ -108,8 +111,8 @@ slidge/util/db.py,sha256=4LxZj8oBYgiSnyBUnF_ALjr0TblkfNQq_p28sCfkHMY,242
108
111
  slidge/util/test.py,sha256=l1VHBsw5Uzk2t7wtkfb9kWvtehcYhw1t_d567JAJFKA,14135
109
112
  slidge/util/types.py,sha256=R_xfS5mRL0XUJIoDpnaAkZlTOoLPerduXBFftaVwIAI,5489
110
113
  slidge/util/util.py,sha256=An4BRIHktZGXnu4kCwaKYaSye_PlyuxEm_4SC9YvPhc,9594
111
- slidge-0.2.3.post1.dist-info/METADATA,sha256=qv5adMikUh_9Eu-6rinlYgSMaCLYG1fF4YaQ1D1QJeg,44800
112
- slidge-0.2.3.post1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
113
- slidge-0.2.3.post1.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
114
- slidge-0.2.3.post1.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
115
- slidge-0.2.3.post1.dist-info/RECORD,,
114
+ slidge-0.2.5.dist-info/METADATA,sha256=qPrPddCJQeL3747-F2abvx04v1bGPzyFJiLHX8Xjx0E,44856
115
+ slidge-0.2.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
116
+ slidge-0.2.5.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
117
+ slidge-0.2.5.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
118
+ slidge-0.2.5.dist-info/RECORD,,