Mercurial > libervia-backend
comparison sat/plugins/plugin_misc_attach.py @ 3922:0ff265725489
plugin XEP-0447: handle attachment and download:
- plugin XEP-0447 can now be used in message attachments and to retrieve an attachment
- plugin attach: `attachment` being processed is added to `extra` so the handler can inspect it
- plugin attach: `size` is added to attachment
- plugin download: a whole attachment dict is now used in `download` and
`file_download`/`file_download_complete`. `download_uri` can be used as a shortcut when
just a URI is used. In addition to URI scheme handler, whole attachment handlers can now
be registered with `register_download_handler`
- plugin XEP-0363: `file_http_upload` `XEP-0363_upload_size` triggers have been renamed to
`XEP-0363_upload_pre_slot` and is now using a dict with arguments, allowing for the size
but also the filename to be modified, which is necessary for encryption (filename may
be hidden from URL this way).
- plugin XEP-0446: fix wrong element name
- plugin XEP-0447: source handler can now be registered (`url-data` is registered by
default)
- plugin XEP-0447: source parsing has been put in a separated `parse_sources_elt` method,
as it may be useful to do it independently (notably with XEP-0448)
- plugin XEP-0447: parse received message and complete attachments when suitable
- plugin XEP-0447: can now be used with message attachments
- plugin XEP-0447: can now be used with attachments download
- renamed `options` arguments to `extra` for consistency
- some style change (progressive move from legacy camelCase to PEP8 snake_case)
- some typing
rel 379
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 06 Oct 2022 16:02:05 +0200 |
parents | 3ef988734869 |
children | 78b5f356900c |
comparison
equal
deleted
inserted
replaced
3921:cc2705225778 | 3922:0ff265725489 |
---|---|
14 # GNU Affero General Public License for more details. | 14 # GNU Affero General Public License for more details. |
15 | 15 |
16 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
19 from collections import namedtuple | |
20 import mimetypes | |
19 from pathlib import Path | 21 from pathlib import Path |
20 from collections import namedtuple | 22 import shutil |
23 import tempfile | |
24 from typing import Callable, Optional | |
25 | |
21 from twisted.internet import defer | 26 from twisted.internet import defer |
22 import mimetypes | 27 |
23 import tempfile | |
24 import shutil | |
25 from sat.core.i18n import _ | |
26 from sat.core import exceptions | 28 from sat.core import exceptions |
27 from sat.core.constants import Const as C | 29 from sat.core.constants import Const as C |
30 from sat.core.core_types import SatXMPPEntity | |
31 from sat.core.i18n import _ | |
28 from sat.core.log import getLogger | 32 from sat.core.log import getLogger |
29 from sat.tools import utils | 33 from sat.tools import utils |
30 from sat.tools import image | 34 from sat.tools import image |
31 | 35 |
32 | 36 |
35 | 39 |
36 PLUGIN_INFO = { | 40 PLUGIN_INFO = { |
37 C.PI_NAME: "File Attach", | 41 C.PI_NAME: "File Attach", |
38 C.PI_IMPORT_NAME: "ATTACH", | 42 C.PI_IMPORT_NAME: "ATTACH", |
39 C.PI_TYPE: C.PLUG_TYPE_MISC, | 43 C.PI_TYPE: C.PLUG_TYPE_MISC, |
44 C.PI_MODES: C.PLUG_MODE_BOTH, | |
40 C.PI_DEPENDENCIES: ["UPLOAD"], | 45 C.PI_DEPENDENCIES: ["UPLOAD"], |
41 C.PI_MAIN: "AttachPlugin", | 46 C.PI_MAIN: "AttachPlugin", |
42 C.PI_HANDLER: "no", | 47 C.PI_HANDLER: "no", |
43 C.PI_DESCRIPTION: _("""Attachments handler"""), | 48 C.PI_DESCRIPTION: _("""Attachments handler"""), |
44 } | 49 } |
150 log.debug(f"Cleaning temporary directory at {dir_path}") | 155 log.debug(f"Cleaning temporary directory at {dir_path}") |
151 shutil.rmtree(dir_path) | 156 shutil.rmtree(dir_path) |
152 | 157 |
153 return data | 158 return data |
154 | 159 |
155 async def uploadFiles(self, client, data, upload_cb=None): | 160 async def upload_files( |
161 self, | |
162 client: SatXMPPEntity, | |
163 data: dict, | |
164 upload_cb: Optional[Callable] = None | |
165 ): | |
156 """Upload file, and update attachments | 166 """Upload file, and update attachments |
157 | 167 |
158 invalid attachments will be removed | 168 invalid attachments will be removed |
159 @param client: | 169 @param client: |
160 @param data(dict): message data | 170 @param data(dict): message data |
161 @param upload_cb(coroutine, Deferred, None): method to use for upload | 171 @param upload_cb(coroutine, Deferred, None): method to use for upload |
162 if None, upload method from UPLOAD plugin will be used. | 172 if None, upload method from UPLOAD plugin will be used. |
163 Otherwise, following kwargs will be use with the cb: | 173 Otherwise, following kwargs will be used with the cb: |
164 - client | 174 - client |
165 - filepath | 175 - filepath |
166 - filename | 176 - filename |
167 - options | 177 - options |
168 the method must return a tuple similar to UPLOAD plugin's upload method, | 178 the method must return a tuple similar to UPLOAD plugin's upload method, |
177 to_delete = [] | 187 to_delete = [] |
178 attachments = data["extra"]["attachments"] | 188 attachments = data["extra"]["attachments"] |
179 | 189 |
180 for attachment in attachments: | 190 for attachment in attachments: |
181 try: | 191 try: |
182 # we pop path because we don't want it to be stored, as the image can be | 192 # we pop path because we don't want it to be stored, as the file can be |
183 # only in a temporary location | 193 # only in a temporary location |
184 path = Path(attachment.pop("path")) | 194 path = Path(attachment.pop("path")) |
185 except KeyError: | 195 except KeyError: |
186 log.warning("no path in attachment: {attachment}") | 196 log.warning("no path in attachment: {attachment}") |
187 to_delete.append(attachment) | 197 to_delete.append(attachment) |
196 try: | 206 try: |
197 name = attachment["name"] | 207 name = attachment["name"] |
198 except KeyError: | 208 except KeyError: |
199 name = attachment["name"] = path.name | 209 name = attachment["name"] = path.name |
200 | 210 |
201 options = {} | 211 attachment["size"] = path.stat().st_size |
212 | |
213 extra = { | |
214 "attachment": attachment | |
215 } | |
202 progress_id = attachment.pop("progress_id", None) | 216 progress_id = attachment.pop("progress_id", None) |
203 if progress_id: | 217 if progress_id: |
204 options["progress_id"] = progress_id | 218 extra["progress_id"] = progress_id |
205 check_certificate = self.host.memory.getParamA( | 219 check_certificate = self.host.memory.getParamA( |
206 "check_certificate", "Connection", profile_key=client.profile) | 220 "check_certificate", "Connection", profile_key=client.profile) |
207 if not check_certificate: | 221 if not check_certificate: |
208 options['ignore_tls_errors'] = True | 222 extra['ignore_tls_errors'] = True |
209 log.warning( | 223 log.warning( |
210 _("certificate check disabled for upload, this is dangerous!")) | 224 _("certificate check disabled for upload, this is dangerous!")) |
211 | 225 |
212 __, upload_d = await upload_cb( | 226 __, upload_d = await upload_cb( |
213 client=client, | 227 client=client, |
214 filepath=path, | 228 filepath=path, |
215 filename=name, | 229 filename=name, |
216 options=options, | 230 extra=extra, |
217 ) | 231 ) |
218 uploads_d.append(upload_d) | 232 uploads_d.append(upload_d) |
219 | 233 |
220 for attachment in to_delete: | 234 for attachment in to_delete: |
221 attachments.remove(attachment) | 235 attachments.remove(attachment) |
244 | 258 |
245 async def defaultCanHandle(self, client, data): | 259 async def defaultCanHandle(self, client, data): |
246 return True | 260 return True |
247 | 261 |
248 async def defaultAttach(self, client, data): | 262 async def defaultAttach(self, client, data): |
249 await self.uploadFiles(client, data) | 263 await self.upload_files(client, data) |
250 # TODO: handle xhtml-im | 264 # TODO: handle xhtml-im |
251 body_elt = next(data["xml"].elements(C.NS_CLIENT, "body")) | 265 body_elt = data["xml"].body |
266 if body_elt is None: | |
267 body_elt = data["xml"].addElement("body") | |
252 attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] | 268 attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] |
253 if attachments: | 269 if attachments: |
254 body_links = '\n'.join(a['url'] for a in attachments) | 270 body_links = '\n'.join(a['url'] for a in attachments) |
255 if str(body_elt).strip(): | 271 if str(body_elt).strip(): |
256 # if there is already a body, we add a line feed before the first link | 272 # if there is already a body, we add a line feed before the first link |