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.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  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 +2 -0
  15. slidge/core/cache.py +121 -39
  16. slidge/core/config.py +116 -11
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -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 +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  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 +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,3 +0,0 @@
1
- from .bookmarks import LegacyBookmarks
2
- from .participant import LegacyParticipant
3
- from .room import LegacyMUC, MucType
@@ -1,74 +0,0 @@
1
- from typing import Generic, Type
2
-
3
- from slixmpp import JID
4
- from slixmpp.jid import _unescape_node
5
-
6
- from slidge.core.contact import ESCAPE_TABLE
7
- from slidge.util import SubclassableOnce
8
- from slidge.util.types import LegacyGroupIdType, LegacyMUCType, SessionType
9
-
10
- from .room import LegacyMUC
11
-
12
-
13
- class LegacyBookmarks(
14
- Generic[SessionType, LegacyMUCType, LegacyGroupIdType], metaclass=SubclassableOnce
15
- ):
16
- def __init__(self, session: SessionType):
17
- self.session = session
18
- self.xmpp = session.xmpp
19
- self.user = session.user
20
- self.log = session.log
21
-
22
- self._mucs_by_legacy_id = dict[LegacyGroupIdType, LegacyMUC]()
23
- self._mucs_by_bare_jid = dict[str, LegacyMUC]()
24
-
25
- self._muc_class: Type[LegacyMUC] = LegacyMUC.get_self_or_unique_subclass()
26
-
27
- def set_username(self, nick: str):
28
- self._muc_class.user_nick = nick
29
-
30
- def __iter__(self):
31
- return iter(self._mucs_by_legacy_id.values())
32
-
33
- async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
34
- return str(legacy_id).translate(ESCAPE_TABLE)
35
-
36
- async def jid_local_part_to_legacy_id(self, local_part: str):
37
- return _unescape_node(local_part)
38
-
39
- async def by_jid(self, jid: JID):
40
- bare = jid.bare
41
- muc = self._mucs_by_bare_jid.get(bare)
42
- if muc is None:
43
- self.session.log.debug(
44
- "Attempting to create new MUC instance for JID %s", jid
45
- )
46
- local_part = jid.node
47
- legacy_id = await self.jid_local_part_to_legacy_id(local_part)
48
- self.session.log.debug("%r is group %r", local_part, legacy_id)
49
- muc = self._muc_class(self.session, legacy_id=legacy_id, jid=JID(bare))
50
- self.session.log.debug("MUC created: %r", muc)
51
- self._mucs_by_legacy_id[legacy_id] = muc
52
- self._mucs_by_bare_jid[bare] = muc
53
- else:
54
- self.session.log.debug("Found MUC: %s -- %s", muc, type(muc))
55
- return muc
56
-
57
- async def by_legacy_id(self, legacy_id: LegacyGroupIdType):
58
- muc = self._mucs_by_legacy_id.get(legacy_id)
59
- if muc is None:
60
- self.session.log.debug(
61
- "Create new MUC instance for legacy ID %s", legacy_id
62
- )
63
- local = await self.legacy_id_to_jid_local_part(legacy_id)
64
- jid = JID(f"{local}@{self.xmpp.boundjid}")
65
- muc = self._muc_class(
66
- self.session,
67
- legacy_id=legacy_id,
68
- jid=jid,
69
- )
70
- self.log.debug("MUC CLASS: %s", self._muc_class)
71
-
72
- self._mucs_by_legacy_id[legacy_id] = muc
73
- self._mucs_by_bare_jid[jid.bare] = muc
74
- return muc
@@ -1,152 +0,0 @@
1
- import string
2
- from copy import copy
3
- from datetime import datetime
4
- from typing import Generic, Optional, Union
5
-
6
- from slixmpp import JID, InvalidJID, Message, Presence
7
- from slixmpp.types import MessageTypes
8
-
9
- from slidge.core.contact import LegacyContact
10
- from slidge.core.mixins import MessageMixin, PresenceMixin
11
- from slidge.util import SubclassableOnce
12
- from slidge.util.types import LegacyMessageType, LegacyMUCType
13
-
14
-
15
- class LegacyParticipant(
16
- Generic[LegacyMUCType], PresenceMixin, MessageMixin, metaclass=SubclassableOnce
17
- ):
18
- mtype: MessageTypes = "groupchat"
19
- USE_STANZA_ID = True
20
- STRIP_SHORT_DELAY = False
21
-
22
- def __init__(self, muc: LegacyMUCType, nickname: str, is_user=False):
23
- self.muc = muc
24
- self.session = session = muc.session
25
- self.log = session.log
26
- self.user = session.user
27
- self.xmpp = session.xmpp
28
- self.role = "participant"
29
- self.affiliation = "member"
30
- self.is_user = is_user
31
-
32
- self.nickname = nickname
33
- self.log.debug("NEW PARTICIPANT: %r", nickname)
34
-
35
- j: JID = copy(self.muc.jid) # type:ignore
36
- try:
37
- j.resource = nickname
38
- except InvalidJID:
39
- j.resource = (
40
- "".join(x for x in nickname if x in string.printable)
41
- + " [renamed by slidge]"
42
- )
43
- self.jid = j
44
-
45
- self.log.debug("NEW PARTICIPANT: %r", self)
46
- self.contact: Optional["LegacyContact"] = None
47
-
48
- def __repr__(self):
49
- return f"<{self.__class__} {self.nickname} of {self.muc}>"
50
-
51
- def _make_presence(
52
- self,
53
- *,
54
- last_seen: Optional[datetime] = None,
55
- status_codes: Optional[set[int]] = None,
56
- **presence_kwargs,
57
- ):
58
- p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
59
- p["muc"]["affiliation"] = self.affiliation
60
- p["muc"]["role"] = self.role
61
- self.log.debug("Presence - contact: %r", self.contact)
62
- if self.contact:
63
- p["muc"]["jid"] = self.contact.jid
64
- codes = status_codes or set()
65
- if self.is_user:
66
- codes.add(110)
67
- p["muc"]["status_codes"] = codes
68
- return p
69
-
70
- def _send(
71
- self,
72
- stanza: Union[Message, Presence],
73
- full_jid: Optional[JID] = None,
74
- **send_kwargs,
75
- ):
76
- if full_jid:
77
- stanza["to"] = full_jid
78
- stanza.send()
79
- else:
80
- for user_full_jid in self.muc.user_full_jids():
81
- stanza = copy(stanza)
82
- stanza["to"] = user_full_jid
83
- stanza.send()
84
-
85
- def send_initial_presence(
86
- self,
87
- full_jid: JID,
88
- status: Optional[str] = None,
89
- last_seen: Optional[datetime] = None,
90
- nick_change=False,
91
- presence_id: Optional[str] = None,
92
- ):
93
- """
94
- Called when the user joins a MUC, as a mechanism
95
- to indicate to the joining XMPP client the list of "participants".
96
- (done internally by slidge)
97
-
98
- :param full_jid: Set this to only send to a specific user XMPP resource.
99
- :param status: a presence message, eg "having a bug, watching the game"
100
- :param last_seen: when the participant was last online :xep:`0319` (Last User Interaction in Presence)
101
- :param nick_change: Used when the user joins and the MUC renames them (code 210)
102
- :param presence_id: set the presence ID. used internally by slidge
103
- """
104
- # MUC status codes: https://xmpp.org/extensions/xep-0045.html#registrar-statuscodes
105
- p = self._make_presence(
106
- pstatus=status,
107
- last_seen=last_seen,
108
- status_codes={210} if nick_change else set(),
109
- )
110
- if presence_id:
111
- p["id"] = presence_id
112
- self._send(p, full_jid)
113
-
114
- def send_text(
115
- self,
116
- body: str,
117
- legacy_msg_id: Optional[LegacyMessageType] = None,
118
- *,
119
- when: Optional[datetime] = None,
120
- reply_to_msg_id: Optional[LegacyMessageType] = None,
121
- reply_to_fallback_text: Optional[str] = None,
122
- reply_self=False,
123
- reply_to_author: Optional["LegacyParticipant"] = None,
124
- **kwargs,
125
- ):
126
- """
127
- The participant sends a message in their corresponding group chat.
128
-
129
- :param body:
130
- :param legacy_msg_id:
131
- :param when:
132
- :param reply_to_msg_id: Quote another message (:xep:`0461`)
133
- :param reply_to_fallback_text: Fallback text for clients not supporting :xep:`0461`
134
- :param reply_self: Set to true is this is a self quote
135
- :param reply_to_author: The participant that was quoted
136
- """
137
- if reply_self:
138
- reply_to_jid = self.jid
139
- elif reply_to_author:
140
- reply_to_jid = reply_to_author.jid
141
- else:
142
- reply_to_jid = None
143
- super().send_text(
144
- body=body,
145
- legacy_msg_id=legacy_msg_id,
146
- when=when,
147
- reply_to_msg_id=reply_to_msg_id,
148
- reply_to_fallback_text=reply_to_fallback_text,
149
- reply_to_jid=reply_to_jid,
150
- hints={"markable"},
151
- **kwargs,
152
- )
slidge/core/muc/room.py DELETED
@@ -1,348 +0,0 @@
1
- import logging
2
- from copy import copy
3
- from datetime import datetime
4
- from enum import Enum
5
- from typing import TYPE_CHECKING, AsyncIterable, Generic, Optional
6
-
7
- from slixmpp import JID, Iq, Message, Presence
8
- from slixmpp.stanza import Error as BaseError
9
- from slixmpp.xmlstream import ET
10
-
11
- from ...util import ABCSubclassableOnceAtMost
12
- from ...util.types import (
13
- LegacyGroupIdType,
14
- LegacyMessageType,
15
- LegacyParticipantType,
16
- SessionType,
17
- )
18
- from ..mixins.base import ReactionRecipientMixin
19
- from ..mixins.disco import BaseDiscoMixin
20
-
21
- if TYPE_CHECKING:
22
- from ..contact import LegacyContact
23
- from ..gateway import BaseGateway
24
-
25
-
26
- class Error(BaseError):
27
- namespace = "jabber:component:accept"
28
-
29
-
30
- class MucType(int, Enum):
31
- GROUP = 0
32
- CHANNEL = 1
33
-
34
-
35
- ADMIN_NS = "http://jabber.org/protocol/muc#admin"
36
-
37
-
38
- class LegacyMUC(
39
- Generic[SessionType, LegacyGroupIdType, LegacyParticipantType, LegacyMessageType],
40
- BaseDiscoMixin,
41
- ReactionRecipientMixin,
42
- metaclass=ABCSubclassableOnceAtMost,
43
- ):
44
- user_nick = "SlidgeUser"
45
- subject_date: Optional[datetime] = None
46
- n_participants: Optional[int] = None
47
- max_history_fetch = 100
48
- description = ""
49
-
50
- type = MucType.CHANNEL
51
- is_group = True
52
-
53
- DISCO_TYPE = "text"
54
- DISCO_CATEGORY = "conference"
55
- DISCO_NAME = "unnamed-room"
56
-
57
- def __init__(self, session: SessionType, legacy_id: LegacyGroupIdType, jid: JID):
58
- from .participant import LegacyParticipant
59
-
60
- self.session = session
61
- self.xmpp: "BaseGateway" = session.xmpp
62
- self.user = session.user
63
- self.log = session.log
64
-
65
- self.legacy_id = legacy_id
66
- self.jid = jid
67
-
68
- self.user_resources = set[str]()
69
-
70
- self.Participant = LegacyParticipant.get_self_or_unique_subclass()
71
-
72
- self.DISCO_NAME = str(legacy_id)
73
-
74
- self.xmpp.add_event_handler(
75
- "presence_unavailable", self._on_presence_unavailable
76
- )
77
-
78
- self._subject = ""
79
- self.subject_setter = "unknown"
80
-
81
- def __repr__(self):
82
- return f"<MUC '{self.legacy_id}' - {self.jid}>"
83
-
84
- def _on_presence_unavailable(self, p: Presence):
85
- pto = p.get_to()
86
- if pto.bare != self.jid.bare:
87
- return
88
-
89
- pfrom = p.get_from()
90
- if pfrom.bare != self.user.bare_jid:
91
- return
92
- if (resource := pfrom.resource) in (resources := self.user_resources):
93
- if pto.resource != self.user_nick:
94
- self.log.warning(
95
- "Received 'leave group' request but with wrong nickname. %s", p
96
- )
97
- resources.remove(resource)
98
- else:
99
- self.log.warning(
100
- "Received 'leave group' request but resource was not listed. %s", p
101
- )
102
-
103
- @property
104
- def subject(self):
105
- return self._subject
106
-
107
- @subject.setter
108
- def subject(self, s: str):
109
- if s != self._subject:
110
- self.update_subject(s)
111
- self._subject = s
112
-
113
- def update_subject(self, subject: Optional[str] = None):
114
- self._subject = subject or ""
115
- for r in self.user_resources:
116
- to = copy(self.user.jid)
117
- to.resource = r
118
- self._make_subject_message(to).send()
119
-
120
- def features(self):
121
- features = [
122
- "http://jabber.org/protocol/muc",
123
- "http://jabber.org/protocol/muc#stable_id",
124
- "http://jabber.org/protocol/muc#self-ping-optimization",
125
- "urn:xmpp:sid:0",
126
- "muc_persistent",
127
- ]
128
- if self.type == MucType.GROUP:
129
- features.extend(["muc_membersonly", "muc_nonanonymous", "muc_hidden"])
130
- elif self.type == MucType.CHANNEL:
131
- features.extend(["muc_open", "muc_semianonymous", "muc_public"])
132
- return features
133
-
134
- def extended_features(self):
135
- is_group = self.type == MucType.GROUP
136
-
137
- form = self.xmpp.plugin["xep_0004"].make_form(ftype="result")
138
-
139
- form.add_field(
140
- "FORM_TYPE", "hidden", value="http://jabber.org/protocol/muc#roominfo"
141
- )
142
- form.add_field("muc#roomconfig_persistentroom", "boolean", value=True)
143
- form.add_field("muc#roomconfig_changesubject", "boolean", value=False)
144
- form.add_field("muc#maxhistoryfetch", value=str(self.max_history_fetch))
145
- form.add_field("muc#roominfo_subjectmod", "boolean", value=False)
146
-
147
- if (n := self.n_participants) is not None:
148
- form.add_field("muc#roominfo_occupants", value=str(n))
149
-
150
- if d := self.description:
151
- form.add_field("muc#roominfo_description", value=d)
152
-
153
- if s := self.subject:
154
- form.add_field("muc#roominfo_subject", value=s)
155
-
156
- form.add_field("muc#roomconfig_membersonly", "boolean", value=is_group)
157
- form.add_field("muc#roomconfig_whois", "boolean", value=is_group)
158
- form.add_field("muc#roomconfig_publicroom", "boolean", value=not is_group)
159
- form.add_field("muc#roomconfig_allowpm", "boolean", value=False)
160
-
161
- return form
162
-
163
- def _no_nickname_error(self, join_presence: Presence):
164
- presence = self.xmpp.make_presence(
165
- ptype="error", pto=join_presence.get_from(), pfrom=self.jid
166
- )
167
- # Error.namespace
168
- presence["id"] = join_presence["id"]
169
- error = Error()
170
- error["by"] = self.jid
171
- error["condition"] = "jid-malformed"
172
- error["type"] = "modify"
173
- presence.append(error)
174
- presence.send()
175
-
176
- def _make_subject_message(self, user_full_jid: JID):
177
- subject_setter = copy(self.jid)
178
- log.debug("subject setter: %s", self.subject_setter)
179
- subject_setter.resource = self.subject_setter
180
- msg = self.xmpp.make_message(
181
- mto=user_full_jid,
182
- mfrom=subject_setter,
183
- mtype="groupchat",
184
- )
185
- msg["delay"].set_stamp(self.subject_date or datetime.now().astimezone())
186
- msg["subject"] = self.subject or str(self.DISCO_NAME)
187
- return msg
188
-
189
- def shutdown(self):
190
- user_jid = copy(self.jid)
191
- user_jid.resource = self.user_nick
192
- for user_full_jid in self.user_full_jids():
193
- presence = self.xmpp.make_presence(
194
- pfrom=user_jid, pto=user_full_jid, ptype="unavailable"
195
- )
196
- presence["muc"]["affiliation"] = "none"
197
- presence["muc"]["role"] = "none"
198
- presence["muc"]["status_codes"] = {110, 332}
199
- presence.send()
200
-
201
- def handle_ping(self, iq: Iq):
202
- if iq.get_from().resource in self.user_resources:
203
- iq.reply().send()
204
- else:
205
- reply = iq.reply()
206
- reply["type"] = "error"
207
- error = Error()
208
- error["by"] = self.jid
209
- error["condition"] = "not-acceptable"
210
- error["type"] = "cancel"
211
- reply.append(error)
212
- reply.send()
213
-
214
- def user_full_jids(self):
215
- for r in self.user_resources:
216
- j = copy(self.user.jid)
217
- j.resource = r
218
- yield j
219
-
220
- @property
221
- def user_muc_jid(self):
222
- user_muc_jid = copy(self.jid)
223
- user_muc_jid.resource = self.user_nick
224
- return user_muc_jid
225
-
226
- def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
227
- return self.session.sent.get(
228
- legacy_id
229
- ) or self.session.legacy_msg_id_to_xmpp_msg_id(legacy_id)
230
-
231
- async def echo(self, m: Message, legacy_msg_id: Optional[LegacyMessageType] = None):
232
- self.log.debug("Echoing %s -- %s", m, legacy_msg_id)
233
-
234
- origin_id = m.get_origin_id()
235
- self.log.debug(f"Origin: %r ", origin_id)
236
-
237
- m.set_from(self.user_muc_jid)
238
- for user_full_jid in self.user_full_jids():
239
- self.log.debug("Echoing to %s", user_full_jid)
240
- msg = copy(m)
241
- msg.set_to(user_full_jid)
242
- msg.set_id(m.get_id())
243
-
244
- if origin_id:
245
- set_origin_id(msg, origin_id)
246
- if legacy_msg_id:
247
- msg["stanza_id"]["id"] = str(legacy_msg_id)
248
- msg["stanza_id"]["by"] = self.jid
249
- msg.send()
250
-
251
- async def join(self, join_presence: Presence):
252
- user_full_jid = join_presence.get_from()
253
- requested_nickname = join_presence.get_to().resource
254
-
255
- if not requested_nickname:
256
- self._no_nickname_error(join_presence)
257
- return
258
-
259
- client_resource = user_full_jid.resource
260
- if not client_resource:
261
- self._no_nickname_error(join_presence)
262
- return
263
-
264
- self.log.debug(
265
- "Resource %s of %s wants to join room %s with nickname %s",
266
- client_resource,
267
- self.user,
268
- self.legacy_id,
269
- requested_nickname,
270
- )
271
-
272
- async for participant in self.get_participants():
273
- participant.send_initial_presence(full_jid=user_full_jid)
274
- user_nick = self.user_nick
275
- user_participant = await self.get_user_participant()
276
- user_participant.send_initial_presence(
277
- user_full_jid,
278
- presence_id=join_presence["id"],
279
- nick_change=user_nick != requested_nickname,
280
- )
281
-
282
- history_params = join_presence["muc_join"]["history"]
283
- maxchars = int_or_none(history_params["maxchars"])
284
- maxstanzas = int_or_none(history_params["maxstanzas"])
285
- seconds = int_or_none(history_params["seconds"])
286
- since = int_or_none(history_params["since"])
287
- if equals_zero(maxchars) or equals_zero(maxstanzas):
288
- log.debug("Joining client does not want any MUC history")
289
- else:
290
- log.debug("Filling history %s")
291
- await self.fill_history(
292
- user_full_jid,
293
- maxchars=maxchars,
294
- maxstanzas=maxstanzas,
295
- seconds=seconds,
296
- since=since,
297
- )
298
- self._make_subject_message(user_full_jid).send()
299
- self.user_resources.add(client_resource)
300
-
301
- async def get_user_participant(self) -> LegacyParticipantType:
302
- return self.Participant(self, self.user_nick, is_user=True)
303
-
304
- async def get_participant(self, nickname: str) -> LegacyParticipantType:
305
- return self.Participant(self, nickname)
306
-
307
- async def get_participant_by_contact(
308
- self, c: "LegacyContact"
309
- ) -> LegacyParticipantType:
310
- p = self.Participant(self, c.name)
311
- p.contact = c
312
- return p
313
-
314
- async def get_participants(self) -> AsyncIterable[LegacyParticipantType]:
315
- yield NotImplemented
316
-
317
- async def fill_history(
318
- self,
319
- full_jid: JID,
320
- maxchars: Optional[int] = None,
321
- maxstanzas: Optional[int] = None,
322
- seconds: Optional[int] = None,
323
- since: Optional[int] = None,
324
- ):
325
- raise NotImplementedError
326
-
327
-
328
- def set_origin_id(msg: Message, origin_id: str):
329
- sub = ET.Element("{urn:xmpp:sid:0}origin-id")
330
- sub.attrib["id"] = origin_id
331
- msg.xml.append(sub)
332
-
333
-
334
- def int_or_none(x):
335
- try:
336
- return int(x)
337
- except ValueError:
338
- return None
339
-
340
-
341
- def equals_zero(x):
342
- if x is None:
343
- return False
344
- else:
345
- return x == 0
346
-
347
-
348
- log = logging.getLogger(__name__)
@@ -1,121 +0,0 @@
1
- import functools
2
- import logging
3
- from typing import Optional
4
-
5
- import discord as di
6
- from slixmpp import JID
7
- from slixmpp.exceptions import XMPPError
8
-
9
- from slidge import *
10
-
11
- from ...util import BiDict
12
- from .session import Session
13
-
14
-
15
- class Config:
16
- DISCORD_VERBOSE = False
17
- DISCORD_VERBOSE__DOC = (
18
- "Let the discord lib at the same loglevel as others loggers. "
19
- "By default, it's set it to WARNING because it's *really* verbose."
20
- )
21
-
22
-
23
- class Gateway(BaseGateway[Session]):
24
- COMPONENT_NAME = "Discord (slidge)"
25
- COMPONENT_TYPE = "discord"
26
- COMPONENT_AVATAR = "https://www.usff.fr/wp-content/uploads/2018/05/Discord_logo.png"
27
-
28
- REGISTRATION_INSTRUCTIONS = (
29
- "Have a look at https://discordpy-self.readthedocs.io/en/latest/token.html"
30
- )
31
- REGISTRATION_FIELDS = [FormField("token", label="Discord token", required=True)]
32
-
33
- ROSTER_GROUP = "Discord"
34
-
35
- def __init__(self):
36
- super().__init__()
37
- if not Config.DISCORD_VERBOSE:
38
- log.debug("Disabling discord info logs")
39
- logging.getLogger("discord.gateway").setLevel(logging.WARNING)
40
- logging.getLogger("discord.client").setLevel(logging.WARNING)
41
-
42
- async def validate(
43
- self, user_jid: JID, registration_form: dict[str, Optional[str]]
44
- ):
45
- try:
46
- await di.Client().login(registration_form.get("token"))
47
- except di.LoginFailure as e:
48
- raise ValueError(str(e))
49
-
50
-
51
- class Contact(LegacyContact[Session, "str"]):
52
- MARKS = False
53
-
54
- @functools.cached_property
55
- def discord_user(self) -> di.User:
56
- logging.debug("Searching for user: %s", self.legacy_id)
57
- if (u := self.session.discord.get_user(self.legacy_id)) is None:
58
- raise XMPPError(
59
- "not-found", text=f"Cannot find the discord user {self.legacy_id}"
60
- )
61
- return u
62
-
63
- @functools.cached_property
64
- def direct_channel_id(self):
65
- return self.discord_user.dm_channel.id
66
-
67
- async def update_reactions(self, m: di.Message):
68
- legacy_reactions = []
69
- user = self.discord_user
70
- for r in m.reactions:
71
- async for u in r.users():
72
- if u == user:
73
- legacy_reactions.append(r.emoji)
74
- self.react(m.id, legacy_reactions)
75
-
76
- async def update_info(self):
77
- u = self.discord_user
78
- self.name = name = u.display_name
79
- self.avatar = str(u.avatar_url)
80
-
81
- try:
82
- profile = await u.profile()
83
- except di.Forbidden:
84
- log.debug("Forbidden to fetch the profile of %s", u)
85
- except di.HTTPException as e:
86
- log.debug("HTTP exception %s when fetch the profile of %s", e, u)
87
- else:
88
- self.set_vcard(full_name=name, note=profile.bio)
89
-
90
- # TODO: use the relationship here
91
- # relationship = u.relationship
92
-
93
-
94
- class Roster(LegacyRoster["Session", Contact, int]):
95
- def __init__(self, *a, **k):
96
- super().__init__(*a, **k)
97
-
98
- async def by_discord_user(self, u: di.User):
99
- return await self.by_legacy_id(u.id)
100
-
101
- async def jid_username_to_legacy_id(self, username: str):
102
- try:
103
- user_id = int(username)
104
- except ValueError:
105
- raise XMPPError(
106
- "bad-request",
107
- text=f"Not a valid discord ID: {username}",
108
- )
109
- else:
110
- if self.session.discord.get_user(user_id) is None:
111
- raise XMPPError(
112
- "item-not-found",
113
- text=f"No discord user was found with ID: {username}",
114
- )
115
- return user_id
116
-
117
- async def legacy_id_to_jid_username(self, discord_user_id: int) -> str:
118
- return str(discord_user_id)
119
-
120
-
121
- log = logging.getLogger(__name__)