async def encrypted(stream): nonlocal decryption_info async for chunk in async_encrypt_attachment(stream): if isinstance(chunk, dict): decryption_info = EncryptedFile.deserialize(chunk) else: yield chunk
async def transfer_thumbnail_to_matrix( client: MautrixTelegramClient, intent: IntentAPI, thumbnail_loc: TypeLocation, video: bytes, mime: str, encrypt: bool) -> Optional[DBTelegramFile]: if not Image or not VideoFileClip: return None loc_id = _location_to_id(thumbnail_loc) if not loc_id: return None db_file = DBTelegramFile.get(loc_id) if db_file: return db_file video_ext = sane_mimetypes.guess_extension(mime) if VideoFileClip and video_ext and video: try: file, width, height = _read_video_thumbnail(video, video_ext, frame_ext="png") except OSError: return None mime_type = "image/png" else: file = await client.download_file(thumbnail_loc) width, height = None, None mime_type = magic.from_buffer(file, mime=True) decryption_info = None upload_mime_type = mime_type if encrypt: file, decryption_info_dict = encrypt_attachment(file) decryption_info = EncryptedFile.deserialize(decryption_info_dict) upload_mime_type = "application/octet-stream" content_uri = await intent.upload_media(file, upload_mime_type) if decryption_info: decryption_info.url = content_uri db_file = DBTelegramFile(id=loc_id, mxc=content_uri, mime_type=mime_type, was_converted=False, timestamp=int(time.time()), size=len(file), width=width, height=height, decryption_info=decryption_info) try: db_file.insert() except (IntegrityError, InvalidRequestError) as e: log.exception( f"{e.__class__.__name__} while saving transferred file thumbnail data. " "This was probably caused by two simultaneous transfers of the same file, " "and might (but probably won't) cause problems with thumbnails or something." ) return db_file
def _get_decryption_info(key: bytes, iv: bytes, sha256: SHA256.SHA256Hash) -> EncryptedFile: return EncryptedFile( version="v2", iv=unpaddedbase64.encode_base64(iv + b"\x00" * 8), hashes={"sha256": unpaddedbase64.encode_base64(sha256.digest())}, key=JSONWebKey(key_type="oct", algorithm="A256CTR", extractable=True, key_ops=["encrypt", "decrypt"], key=unpaddedbase64.encode_base64(key, urlsafe=True)))
async def process_hangouts_attachments( self, event: ChatMessageEvent, intent: IntentAPI) -> Optional[EventID]: attachments_pb = event._event.chat_message.message_content.attachment if len(event.attachments) > 1: self.log.warning("Can't handle more that one attachment") return None attachment = event.attachments[0] attachment_pb = attachments_pb[0] embed_item = attachment_pb.embed_item # Get the filename from the headers async with self.az.http_session.request("GET", attachment) as resp: value, params = cgi.parse_header( resp.headers["Content-Disposition"]) mime = resp.headers["Content-Type"] filename = params.get('filename', attachment.split("/")[-1]) # TODO: This also catches movies, but I can't work out how they present # differently to images if embed_item.type[0] == hangouts.ITEM_TYPE_PLUS_PHOTO: data = await self._get_remote_bytes(attachment) upload_mime = mime decryption_info = None if self.encrypted and encrypt_attachment: data, decryption_info_dict = encrypt_attachment(data) decryption_info = EncryptedFile.deserialize( decryption_info_dict) upload_mime = "application/octet-stream" mxc_url = await intent.upload_media(data, mime_type=upload_mime, filename=filename) if decryption_info: decryption_info.url = mxc_url content = MediaMessageEventContent(url=mxc_url, file=decryption_info, body=filename, info=ImageInfo(size=len(data), mimetype=mime), msgtype=MessageType.IMAGE) return await self._send_message(intent, content, timestamp=event.timestamp) return None
async def get(cls, loc_id: str, *, _thumbnail: bool = False) -> TelegramFile | None: q = ( "SELECT id, mxc, mime_type, was_converted, timestamp, size, width, height, thumbnail," " decryption_info " "FROM telegram_file WHERE id=$1") row = await cls.db.fetchrow(q, loc_id) if row is None: return None data = {**row} thumbnail_id = data.pop("thumbnail", None) if _thumbnail: # Don't allow more than one level of recursion thumbnail_id = None decryption_info = data.pop("decryption_info", None) return cls( **data, thumbnail=(await cls.get(thumbnail_id, _thumbnail=True)) if thumbnail_id else None, decryption_info=EncryptedFile.parse_json(decryption_info) if decryption_info else None, )
async def _reupload_fb_file( url: str, intent: IntentAPI, filename: Optional[str] = None, encrypt: bool = False ) -> Tuple[ContentURI, str, int, Optional[EncryptedFile]]: if not url: raise ValueError('URL not provided') async with aiohttp.ClientSession() as session: resp = await session.get(url) data = await resp.read() mime = magic.from_buffer(data, mime=True) upload_mime_type = mime decryption_info = None if encrypt and encrypt_attachment: data, decryption_info_dict = encrypt_attachment(data) decryption_info = EncryptedFile.deserialize(decryption_info_dict) upload_mime_type = "application/octet-stream" url = await intent.upload_media(data, mime_type=upload_mime_type, filename=filename) if decryption_info: decryption_info.url = url return url, mime, len(data), decryption_info
def process_result_value(self, value: str, dialect) -> Optional[EncryptedFile]: if value is not None: return EncryptedFile.parse_json(value) return None
def process_bind_param(self, value: EncryptedFile, dialect) -> Optional[str]: if value is not None: return value.json() return None
async def _unlocked_transfer_file_to_matrix( client: MautrixTelegramClient, intent: IntentAPI, loc_id: str, location: TypeLocation, thumbnail: TypeThumbnail, is_sticker: bool, tgs_convert: Optional[dict], filename: Optional[str], encrypt: bool, parallel_id: Optional[int]) -> Optional[DBTelegramFile]: db_file = DBTelegramFile.get(loc_id) if db_file: return db_file if parallel_id and isinstance(location, Document) and (not is_sticker or not tgs_convert): db_file = await parallel_transfer_to_matrix(client, intent, loc_id, location, filename, encrypt, parallel_id) mime_type = location.mime_type file = None else: try: file = await client.download_file(location) except (LocationInvalidError, FileIdInvalidError): return None except (AuthBytesInvalidError, AuthKeyInvalidError, SecurityError) as e: log.exception(f"{e.__class__.__name__} while downloading a file.") return None width, height = None, None mime_type = magic.from_buffer(file, mime=True) image_converted = False # A weird bug in alpine/magic makes it return application/octet-stream for gzips... if is_sticker and tgs_convert and ( mime_type == "application/gzip" or (mime_type == "application/octet-stream" and magic.from_buffer(file).startswith("gzip"))): mime_type, file, width, height = await convert_tgs_to( file, tgs_convert["target"], **tgs_convert["args"]) thumbnail = None image_converted = mime_type != "application/gzip" if mime_type == "image/webp": new_mime_type, file, width, height = convert_image( file, source_mime="image/webp", target_type="png", thumbnail_to=(256, 256) if is_sticker else None) image_converted = new_mime_type != mime_type mime_type = new_mime_type thumbnail = None decryption_info = None upload_mime_type = mime_type if encrypt and encrypt_attachment: file, decryption_info_dict = encrypt_attachment(file) decryption_info = EncryptedFile.deserialize(decryption_info_dict) upload_mime_type = "application/octet-stream" content_uri = await intent.upload_media(file, upload_mime_type) if decryption_info: decryption_info.url = content_uri db_file = DBTelegramFile(id=loc_id, mxc=content_uri, decryption_info=decryption_info, mime_type=mime_type, was_converted=image_converted, timestamp=int(time.time()), size=len(file), width=width, height=height) if thumbnail and (mime_type.startswith("video/") or mime_type == "image/gif"): if isinstance(thumbnail, (PhotoSize, PhotoCachedSize)): thumbnail = thumbnail.location try: db_file.thumbnail = await transfer_thumbnail_to_matrix( client, intent, thumbnail, file, mime_type, encrypt) except FileIdInvalidError: log.warning(f"Failed to transfer thumbnail for {thumbnail!s}", exc_info=True) try: db_file.insert() except (IntegrityError, InvalidRequestError) as e: log.exception( f"{e.__class__.__name__} while saving transferred file data. " "This was probably caused by two simultaneous transfers of the same file, " "and should not cause any problems.") return db_file