Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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)))
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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,
     )
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
 def process_result_value(self, value: str,
                          dialect) -> Optional[EncryptedFile]:
     if value is not None:
         return EncryptedFile.parse_json(value)
     return None
Ejemplo n.º 8
0
 def process_bind_param(self, value: EncryptedFile,
                        dialect) -> Optional[str]:
     if value is not None:
         return value.json()
     return None
Ejemplo n.º 9
0
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