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,180 +0,0 @@
1
-
2
- # Slixmpp: The Slick XMPP Library
3
- # Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
4
- # This file is part of Slixmpp.
5
- # See the file LICENSE for copying permission.
6
- from slixmpp.xmlstream import ET, ElementBase
7
-
8
-
9
- class Command(ElementBase):
10
-
11
- """
12
- XMPP's Adhoc Commands provides a generic workflow mechanism for
13
- interacting with applications. The result is similar to menu selections
14
- and multi-step dialogs in normal desktop applications. Clients do not
15
- need to know in advance what commands are provided by any particular
16
- application or agent. While adhoc commands provide similar functionality
17
- to Jabber-RPC, adhoc commands are used primarily for human interaction.
18
-
19
- Also see <http://xmpp.org/extensions/xep-0050.html>
20
-
21
- Example command stanzas:
22
-
23
- ::
24
-
25
- <iq type="set">
26
- <command xmlns="http://jabber.org/protocol/commands"
27
- node="run_foo"
28
- action="execute" />
29
- </iq>
30
-
31
- <iq type="result">
32
- <command xmlns="http://jabber.org/protocol/commands"
33
- node="run_foo"
34
- sessionid="12345"
35
- status="executing">
36
- <actions>
37
- <complete />
38
- </actions>
39
- <note type="info">Information!</note>
40
- <x xmlns="jabber:x:data">
41
- <field var="greeting"
42
- type="text-single"
43
- label="Greeting" />
44
- </x>
45
- </command>
46
- </iq>
47
-
48
- Stanza Interface:
49
- ::
50
-
51
- action -- The action to perform.
52
- actions -- The set of allowable next actions.
53
- node -- The node associated with the command.
54
- notes -- A list of tuples for informative notes.
55
- sessionid -- A unique identifier for a command session.
56
- status -- May be one of: canceled, completed, or executing.
57
-
58
- """
59
-
60
- name = 'command'
61
- namespace = 'http://jabber.org/protocol/commands'
62
- plugin_attrib = 'command'
63
- interfaces = {'action', 'sessionid', 'node',
64
- 'status', 'actions', 'notes'}
65
- actions = {'cancel', 'complete', 'execute', 'next', 'prev'}
66
- statuses = {'canceled', 'completed', 'executing'}
67
- next_actions = {'prev', 'next', 'complete'}
68
-
69
- def get_action(self):
70
- """
71
- Return the value of the action attribute.
72
-
73
- If the Iq stanza's type is "set" then use a default
74
- value of "execute".
75
- """
76
- if self.parent()['type'] == 'set':
77
- return self._get_attr('action', default='execute')
78
- return self._get_attr('action')
79
-
80
- def set_actions(self, values):
81
- """
82
- Assign the set of allowable next actions.
83
-
84
- :param values: A list containing any combination of:
85
- 'prev', 'next', and 'complete'
86
- """
87
- self.del_actions()
88
- if values:
89
- self._set_sub_text('{%s}actions' % self.namespace, '', True)
90
- actions = self.xml.find('{%s}actions' % self.namespace)
91
- for val in values:
92
- if val in self.next_actions:
93
- action = ET.Element('{%s}%s' % (self.namespace, val))
94
- actions.append(action)
95
-
96
- def get_actions(self):
97
- """
98
- Return the set of allowable next actions.
99
- """
100
- actions = set()
101
- actions_xml = self.xml.find('{%s}actions' % self.namespace)
102
- if actions_xml is not None:
103
- for action in self.next_actions:
104
- action_xml = actions_xml.find('{%s}%s' % (self.namespace,
105
- action))
106
- if action_xml is not None:
107
- actions.add(action)
108
- return actions
109
-
110
- def del_actions(self):
111
- """
112
- Remove all allowable next actions.
113
- """
114
- self._del_sub('{%s}actions' % self.namespace)
115
-
116
- def get_notes(self):
117
- """
118
- Return a list of note information.
119
-
120
- Example:
121
- [('info', 'Some informative data'),
122
- ('warning', 'Use caution'),
123
- ('error', 'The command ran, but had errors')]
124
- """
125
- notes = []
126
- notes_xml = self.xml.findall('{%s}note' % self.namespace)
127
- for note in notes_xml:
128
- notes.append((note.attrib.get('type', 'info'),
129
- note.text))
130
- return notes
131
-
132
- def set_notes(self, notes):
133
- """
134
- Add multiple notes to the command result.
135
-
136
- Each note is a tuple, with the first item being one of:
137
- 'info', 'warning', or 'error', and the second item being
138
- any human readable message.
139
-
140
- Example:
141
- [('info', 'Some informative data'),
142
- ('warning', 'Use caution'),
143
- ('error', 'The command ran, but had errors')]
144
-
145
-
146
- Arguments:
147
- notes -- A list of tuples of note information.
148
- """
149
- self.del_notes()
150
- for note in notes:
151
- self.add_note(note[1], note[0])
152
-
153
- def del_notes(self):
154
- """
155
- Remove all notes associated with the command result.
156
- """
157
- notes_xml = self.xml.findall('{%s}note' % self.namespace)
158
- for note in notes_xml:
159
- self.xml.remove(note)
160
-
161
- def add_note(self, msg='', ntype='info'):
162
- """
163
- Add a single note annotation to the command.
164
-
165
- Arguments:
166
- msg -- A human readable message.
167
- ntype -- One of: 'info', 'warning', 'error'
168
- """
169
- xml = ET.Element('{%s}note' % self.namespace)
170
- xml.attrib['type'] = ntype
171
- xml.text = msg
172
- self.xml.append(xml)
173
-
174
-
175
- class Note(ElementBase):
176
- name = 'note'
177
- namespace = 'http://jabber.org/protocol/commands'
178
- plugin_attrib = 'note'
179
- interfaces = {'type'}
180
-
@@ -1,71 +0,0 @@
1
- # Slixmpp: The Slick XMPP Library
2
- # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
- # This file is part of Slixmpp.
4
- # See the file LICENSE for copying permission.
5
- from __future__ import unicode_literals
6
-
7
- from typing import ClassVar, Set
8
-
9
- from slixmpp.xmlstream import ElementBase
10
-
11
-
12
- class Register(ElementBase):
13
-
14
- namespace = 'jabber:iq:register'
15
- name = 'query'
16
- plugin_attrib = 'register'
17
- interfaces = {'username', 'password', 'email', 'nick', 'name',
18
- 'first', 'last', 'address', 'city', 'state', 'zip',
19
- 'phone', 'url', 'date', 'misc', 'text', 'key',
20
- 'registered', 'remove', 'instructions', 'fields'}
21
- sub_interfaces = interfaces
22
- form_fields = {'username', 'password', 'email', 'nick', 'name',
23
- 'first', 'last', 'address', 'city', 'state', 'zip',
24
- 'phone', 'url', 'date', 'misc', 'text', 'key'}
25
-
26
- def get_registered(self):
27
- present = self.xml.find('{%s}registered' % self.namespace)
28
- return present is not None
29
-
30
- def get_remove(self):
31
- present = self.xml.find('{%s}remove' % self.namespace)
32
- return present is not None
33
-
34
- def set_registered(self, value):
35
- if value:
36
- self.add_field('registered')
37
- else:
38
- del self['registered']
39
-
40
- def set_remove(self, value):
41
- if value:
42
- self.add_field('remove')
43
- else:
44
- del self['remove']
45
-
46
- def add_field(self, value):
47
- self._set_sub_text(value, '', keep=True)
48
-
49
- def get_fields(self):
50
- fields = set()
51
- for field in self.form_fields:
52
- if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
53
- fields.add(field)
54
- return fields
55
-
56
- def set_fields(self, fields):
57
- del self['fields']
58
- for field in fields:
59
- self._set_sub_text(field, '', keep=True)
60
-
61
- def del_fields(self):
62
- for field in self.form_fields:
63
- self._del_sub(field)
64
-
65
-
66
- class RegisterFeature(ElementBase):
67
-
68
- name = 'register'
69
- namespace = 'http://jabber.org/features/iq-register'
70
- plugin_attrib = name
71
- interfaces: ClassVar[Set[str]] = set()
@@ -1 +0,0 @@
1
- from . import stanza, vcard4
@@ -1,167 +0,0 @@
1
- import datetime
2
- from typing import Optional
3
-
4
- from slixmpp import ElementBase, Iq, register_stanza_plugin
5
-
6
- NS = "urn:ietf:params:xml:ns:vcard-4.0"
7
-
8
-
9
- class _VCardElementBase(ElementBase):
10
- namespace = NS
11
-
12
-
13
- class VCard4(_VCardElementBase):
14
- name = plugin_attrib = "vcard"
15
- interfaces = {"full_name", "given", "surname", "birthday"}
16
-
17
- def set_full_name(self, full_name: str):
18
- self["fn"]["text"] = full_name
19
-
20
- def get_full_name(self):
21
- return self["fn"]["text"]
22
-
23
- def set_given(self, given: str):
24
- self["n"]["given"] = given
25
-
26
- def get_given(self):
27
- return self["n"]["given"]
28
-
29
- def set_surname(self, surname: str):
30
- self["n"]["surname"] = surname
31
-
32
- def get_surname(self):
33
- return self["n"]["surname"]
34
-
35
- def set_birthday(self, birthday: datetime.date):
36
- self["bday"]["date"] = birthday
37
-
38
- def get_birthday(self):
39
- return self["bday"]["date"]
40
-
41
- def add_tel(self, number: str, name: Optional[str] = None):
42
- tel = Tel()
43
- if name:
44
- tel["parameters"]["type_"]["text"] = name
45
- tel["uri"] = f"tel:{number}"
46
- self.append(tel)
47
-
48
- def add_address(
49
- self, country: Optional[str] = None, locality: Optional[str] = None
50
- ):
51
- adr = Adr()
52
- if locality:
53
- adr["locality"] = locality
54
- if country:
55
- adr["country"] = country
56
- self.append(adr)
57
-
58
- def add_nickname(self, nick: str):
59
- el = Nickname()
60
- el["text"] = nick
61
- self.append(el)
62
-
63
- def add_note(self, note: str):
64
- el = Note()
65
- el["text"] = note
66
- self.append(el)
67
-
68
- def add_impp(self, impp: str):
69
- el = Impp()
70
- el["uri"] = impp
71
- self.append(el)
72
-
73
- def add_url(self, url: str):
74
- el = Url()
75
- el["uri"] = url
76
- self.append(el)
77
-
78
- def add_email(self, email: str):
79
- el = Email()
80
- el["text"] = email
81
- self.append(el)
82
-
83
-
84
- class _VCardTextElementBase(_VCardElementBase):
85
- interfaces = {"text"}
86
- sub_interfaces = {"text"}
87
-
88
-
89
- class Fn(_VCardTextElementBase):
90
- name = plugin_attrib = "fn"
91
-
92
-
93
- class Nickname(_VCardTextElementBase):
94
- name = plugin_attrib = "nickname"
95
-
96
-
97
- class Note(_VCardTextElementBase):
98
- name = plugin_attrib = "note"
99
-
100
-
101
- class _VCardUriElementBase(_VCardElementBase):
102
- interfaces = {"uri"}
103
- sub_interfaces = {"uri"}
104
-
105
-
106
- class Url(_VCardUriElementBase):
107
- name = plugin_attrib = "url"
108
-
109
-
110
- class Impp(_VCardUriElementBase):
111
- name = plugin_attrib = "impp"
112
-
113
-
114
- class Email(_VCardTextElementBase):
115
- name = plugin_attrib = "email"
116
-
117
-
118
- class N(_VCardElementBase):
119
- name = "n"
120
- plugin_attrib = "n"
121
- interfaces = sub_interfaces = {"given", "surname", "additional"}
122
-
123
-
124
- class BDay(_VCardElementBase):
125
- name = plugin_attrib = "bday"
126
- interfaces = {"date"}
127
-
128
- def set_date(self, date: datetime.date):
129
- d = Date()
130
- d.xml.text = date.strftime("%Y-%m-%d")
131
- self.append(d)
132
-
133
- def get_date(self):
134
- for elem in self.xml:
135
- try:
136
- return datetime.date.fromisoformat(elem.text)
137
- except ValueError:
138
- return None
139
-
140
-
141
- class Date(_VCardElementBase):
142
- name = "date"
143
-
144
-
145
- class Tel(_VCardUriElementBase):
146
- name = plugin_attrib = "tel"
147
-
148
-
149
- class Parameters(_VCardElementBase):
150
- name = plugin_attrib = "parameters"
151
-
152
-
153
- class Type(_VCardTextElementBase):
154
- name = "type"
155
- plugin_attrib = "type_"
156
-
157
-
158
- class Adr(_VCardElementBase):
159
- name = plugin_attrib = "adr"
160
- interfaces = sub_interfaces = {"locality", "country"}
161
-
162
-
163
- register_stanza_plugin(Parameters, Type)
164
- register_stanza_plugin(Tel, Parameters)
165
- for p in N, Fn, Nickname, Note, Url, Impp, Email, BDay, Tel, Adr:
166
- register_stanza_plugin(VCard4, p, iterable=True)
167
- register_stanza_plugin(Iq, VCard4)
@@ -1,74 +0,0 @@
1
- import logging
2
- from dataclasses import dataclass, field
3
- from typing import Optional
4
-
5
- from slixmpp import JID, ComponentXMPP, CoroutineCallback, Iq, StanzaPath
6
- from slixmpp.plugins.base import BasePlugin, register_plugin
7
- from slixmpp.types import JidStr
8
-
9
- from .stanza import NS, VCard4
10
-
11
-
12
- @dataclass
13
- class StoredVCard:
14
- content: VCard4
15
- authorized_jids: set[JidStr] = field(default_factory=set)
16
-
17
-
18
- class VCard4Provider(BasePlugin):
19
- xmpp: ComponentXMPP
20
-
21
- name = "xep_0292_provider"
22
- description = "VCard4 Provider"
23
- dependencies = {"xep_0030"}
24
-
25
- def __init__(self, *a, **k):
26
- super(VCard4Provider, self).__init__(*a, **k)
27
- self._vcards = dict[JidStr, StoredVCard]()
28
-
29
- def plugin_init(self):
30
- self.xmpp.register_handler(
31
- CoroutineCallback(
32
- "get_vcard",
33
- StanzaPath(f"iq@type=get/vcard"),
34
- self.handle_vcard_get, # type:ignore
35
- )
36
- )
37
-
38
- self.xmpp.plugin["xep_0030"].add_feature(NS)
39
-
40
- def get_vcard(self, jid: JidStr, requested_by: JidStr) -> Optional[VCard4]:
41
- vcard = self._vcards.get(JID(jid).bare)
42
- if vcard:
43
- if auth := vcard.authorized_jids:
44
- if JID(requested_by).bare in auth:
45
- return vcard.content
46
- else:
47
- return vcard.content
48
- return None
49
-
50
- async def handle_vcard_get(self, iq: Iq):
51
- r = iq.reply()
52
- if vcard := self.get_vcard(iq.get_to().bare, iq.get_from().bare):
53
- r.append(vcard)
54
- else:
55
- r.enable("vcard")
56
- r.send()
57
-
58
- def set_vcard(
59
- self,
60
- jid: JidStr,
61
- vcard: VCard4,
62
- /,
63
- authorized_jids: Optional[set[JidStr]] = None,
64
- ):
65
- self._vcards[jid] = StoredVCard(
66
- vcard, authorized_jids if authorized_jids is not None else set()
67
- )
68
- if self.xmpp["pubsub"] and authorized_jids:
69
- for to in authorized_jids:
70
- self.xmpp["pubsub"].broadcast_vcard_event(jid, to)
71
-
72
-
73
- register_plugin(VCard4Provider)
74
- log = logging.getLogger(__name__)
@@ -1,7 +0,0 @@
1
- from slixmpp.plugins.base import register_plugin
2
-
3
- from . import stanza
4
- from .privilege import XEP_0356
5
- from .stanza import Perm, Privilege
6
-
7
- register_plugin(XEP_0356)
@@ -1,35 +0,0 @@
1
- import dataclasses
2
- from enum import Enum
3
-
4
-
5
- class RosterAccess(str, Enum):
6
- NONE = "none"
7
- GET = "get"
8
- SET = "set"
9
- BOTH = "both"
10
-
11
-
12
- class MessagePermission(str, Enum):
13
- NONE = "none"
14
- OUTGOING = "outgoing"
15
-
16
-
17
- class IqPermission(str, Enum):
18
- NONE = "none"
19
- GET = "get"
20
- SET = "set"
21
- BOTH = "both"
22
-
23
-
24
- class PresencePermission(str, Enum):
25
- NONE = "none"
26
- MANAGED_ENTITY = "managed_entity"
27
- ROSTER = "roster"
28
-
29
-
30
- @dataclasses.dataclass
31
- class Permissions:
32
- roster = RosterAccess.NONE
33
- message = MessagePermission.NONE
34
- iq = MessagePermission.NONE
35
- presence = PresencePermission.NONE
@@ -1,160 +0,0 @@
1
- import logging
2
- import typing
3
- from collections import defaultdict
4
-
5
- from slixmpp import JID, Iq, Message
6
- from slixmpp.plugins.base import BasePlugin
7
- from slixmpp.types import JidStr
8
- from slixmpp.xmlstream import StanzaBase
9
- from slixmpp.xmlstream.handler import Callback
10
- from slixmpp.xmlstream.matcher import StanzaPath
11
-
12
- from . import stanza
13
- from .permissions import MessagePermission, Permissions, RosterAccess
14
-
15
- log = logging.getLogger(__name__)
16
-
17
-
18
- # noinspection PyPep8Naming
19
- class XEP_0356(BasePlugin):
20
- """
21
- XEP-0356: Privileged Entity
22
-
23
- Events:
24
-
25
- ::
26
-
27
- privileges_advertised -- Received message/privilege from the server
28
- """
29
-
30
- name = "xep_0356"
31
- description = "XEP-0356: Privileged Entity (slidge)"
32
- dependencies = {"xep_0297"}
33
- stanza = stanza
34
-
35
- granted_privileges: defaultdict[JidStr, Permissions] = defaultdict(Permissions)
36
-
37
- def plugin_init(self):
38
- if not self.xmpp.is_component:
39
- log.error("XEP 0356 is only available for components")
40
- return
41
-
42
- stanza.register()
43
-
44
- self.xmpp.register_handler(
45
- Callback(
46
- "Privileges",
47
- StanzaPath("message/privilege"),
48
- self._handle_privilege,
49
- )
50
- )
51
-
52
- def plugin_end(self):
53
- self.xmpp.remove_handler("Privileges")
54
-
55
- def _handle_privilege(self, msg: StanzaBase):
56
- """
57
- Called when the XMPP server advertise the component's privileges.
58
-
59
- Stores the privileges in this instance's granted_privileges attribute (a dict)
60
- and raises the privileges_advertised event
61
- """
62
- for perm in msg["privilege"]["perms"]:
63
- setattr(
64
- self.granted_privileges[msg.get_from()], perm["access"], perm["type"]
65
- )
66
- log.debug(f"Privileges: {self.granted_privileges}")
67
- self.xmpp.event("privileges_advertised")
68
-
69
- def send_privileged_message(self, msg: Message):
70
- if self.granted_privileges[msg.get_from().domain].message != MessagePermission.OUTGOING:
71
- raise PermissionError(
72
- "The server hasn't authorized us to send messages on behalf of other users"
73
- )
74
- else:
75
- self._make_privileged_message(msg).send()
76
-
77
- def _make_privileged_message(self, msg: Message):
78
- server = msg.get_from().domain
79
- wrapped = self.xmpp.make_message(mto=server, mfrom=self.xmpp.boundjid.bare)
80
- wrapped["privilege"]["forwarded"].append(msg)
81
- return wrapped
82
-
83
- def _make_get_roster(self, jid: typing.Union[JID, str], **iq_kwargs):
84
- return self.xmpp.make_iq_get(
85
- queryxmlns="jabber:iq:roster",
86
- ifrom=self.xmpp.boundjid.bare,
87
- ito=jid,
88
- **iq_kwargs,
89
- )
90
-
91
- def _make_set_roster(
92
- self,
93
- jid: typing.Union[JID, str],
94
- roster_items: dict,
95
- **iq_kwargs,
96
- ):
97
- iq = self.xmpp.make_iq_set(
98
- ifrom=self.xmpp.boundjid.bare,
99
- ito=jid,
100
- **iq_kwargs,
101
- )
102
- iq["roster"]["items"] = roster_items
103
- return iq
104
-
105
- async def get_roster(self, jid: typing.Union[JID, str], **send_kwargs) -> Iq:
106
- """
107
- Return the roster of user on the server the component has privileged access to.
108
-
109
- Raises ValueError if the server did not advertise the corresponding privileges
110
-
111
- :param jid: user we want to fetch the roster from
112
- """
113
- if isinstance(jid, str):
114
- jid = JID(jid)
115
- if self.granted_privileges[jid.domain].roster not in (
116
- RosterAccess.GET,
117
- RosterAccess.BOTH,
118
- ):
119
- raise PermissionError(
120
- "The server did not grant us privileges to get rosters"
121
- )
122
- else:
123
- return await self._make_get_roster(jid).send(**send_kwargs)
124
-
125
- async def set_roster(
126
- self, jid: typing.Union[JID, str], roster_items: dict, **send_kwargs
127
- ) -> Iq:
128
- """
129
- Return the roster of user on the server the component has privileged access to.
130
-
131
- Raises ValueError if the server did not advertise the corresponding privileges
132
-
133
- :param jid: user we want to add or modify roster items
134
- :param roster_items: a dict containing the roster items' JIDs as keys and
135
- nested dicts containing names, subscriptions and groups.
136
- Example:
137
- {
138
- "friend1@example.com": {
139
- "name": "Friend 1",
140
- "subscription": "both",
141
- "groups": ["group1", "group2"],
142
- },
143
- "friend2@example.com": {
144
- "name": "Friend 2",
145
- "subscription": "from",
146
- "groups": ["group3"],
147
- },
148
- }
149
- """
150
- if isinstance(jid, str):
151
- jid = JID(jid)
152
- if self.granted_privileges[jid.domain].roster not in (
153
- RosterAccess.GET,
154
- RosterAccess.BOTH,
155
- ):
156
- raise PermissionError(
157
- "The server did not grant us privileges to set rosters"
158
- )
159
- else:
160
- return await self._make_set_roster(jid, roster_items).send(**send_kwargs)