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