Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 84 additions & 82 deletions es/xchat/cryptography-primer.mdx

Large diffs are not rendered by default.

446 changes: 242 additions & 204 deletions es/xchat/getting-started.mdx

Large diffs are not rendered by default.

103 changes: 51 additions & 52 deletions es/xchat/groups.mdx

Large diffs are not rendered by default.

116 changes: 63 additions & 53 deletions es/xchat/media.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
title: Multimedia y adjuntos
sidebarTitle: Multimedia
description: Cifra, sube, envía, descarga y descifra imágenes y archivos adjuntos en X Chat con el Chat XDK, cifrado de streams y endpoints de subida.
description: Cifra, sube, envía, descarga y descifra imágenes y archivos adjuntos en X Chat usando el cifrado de streams del Chat XDK y los endpoints de subida de multimedia.
keywords: ["X Chat media", "encrypted images", "attachments", "encrypt_stream", "media upload"]
---

Las imágenes y otros archivos usan la **misma clave de conversación** que el texto. Cifra los bytes con el Chat XDK (`encrypt_stream` / `decrypt_stream`), sube mediante las rutas **`/2/chat/media/upload`** (barra lateral **API reference → Media**) y luego adjunta **`media_hash_key`** en `encrypt_message`.

Incluye **`media.write`** con tus scopes de DM al subir. Usa ids de conversación con guión en las rutas (`:` → `-`). Prefiere el MIME/dimensiones desde los bytes **descifrados**.
Incluye **`media.write`** con tus alcances de DM al subir. Usa IDs de conversación con guiones en las rutas (`:` → `-`). Prefiere el MIME/dimensiones a partir de los bytes **descifrados**.

Este camino **no** es el modelo de multimedia de Posts (`expansions=attachments.media_keys`, `media.fields=variants`, etc.). Esos parámetros aplican a **Posts**; los blobs E2EE de X Chat se direccionan por **`media_hash_key`** y por la descarga de multimedia de X Chat.
Esta ruta **no** es el modelo de multimedia de Posts (`expansions=attachments.media_keys`, `media.fields=variants`, etc.). Esos parámetros aplican a **Posts**; los blobs de X Chat E2EE se direccionan por **`media_hash_key`** y la descarga de multimedia de X Chat.

```mermaid
flowchart LR
Expand Down Expand Up @@ -104,7 +104,7 @@ flowchart LR
</Tab>
</Tabs>

`encrypt_stream` / `decrypt_stream` procesan la carga completa en memoria. Para archivos grandes, `stream_encryptor()` / `stream_decryptor()` devuelven objetos incrementales (`StreamEncryptor` / `StreamDecryptor`): aliméntalos por fragmentos con `push` y luego llama a `finish` una vez—`finish` arroja error si el stream se truncó.
`encrypt_stream` / `decrypt_stream` procesan todo el payload en memoria. Para archivos grandes, `stream_encryptor()` / `stream_decryptor()` devuelven objetos incrementales (`StreamEncryptor` / `StreamDecryptor`): alimenta trozos con `push`, luego llama a `finish` una vez—`finish` falla si el stream se truncó.

---

Expand All @@ -116,29 +116,25 @@ flowchart LR
| Añadir | `POST` | `/2/chat/media/upload/{id}/append` |
| Finalizar | `POST` | `/2/chat/media/upload/{id}/finalize` |

Usa los cuerpos de solicitud en las páginas de OpenAPI bajo **API reference → Media**. Prefiere el tamaño del blob **cifrado** donde se requiera el tamaño. Finalizar produce **`media_hash_key`** para adjuntos y descarga. Reintenta `5xx` transitorios con backoff. Python/TypeScript pueden usar el XDK cuando existan helpers de multimedia; en caso contrario, haz POST con un Bearer token en cualquier lenguaje.
Usa los cuerpos de solicitud en las páginas OpenAPI bajo **API reference → Media**. Prefiere el tamaño del blob **cifrado** donde se requiera el tamaño. Finalize entrega **`media_hash_key`** para los adjuntos y la descarga. Reintenta los `5xx` transitorios con backoff. Python/TypeScript pueden usar el XDK cuando existan helpers de multimedia; de lo contrario, haz POST con un token Bearer en cualquier lenguaje.

---

## Enviar con un adjunto

Cifra con un adjunto de multimedia y luego haz POST del cuerpo send-message (mismo mapeo de campos que en [Primeros pasos](/xchat/getting-started#5-send-a-message)).
Cifra con un adjunto multimedia, luego haz POST del cuerpo send-message (mismo mapeo de campos que [Primeros pasos](/es/xchat/getting-started#5-send-a-message)). El SDK genera el `message_id` y lo devuelve en el payload—envía ese valor, y reutiliza el mismo payload en los reintentos para que nunca se genere un ID dos veces.

<Tabs>
<Tab title="Python">
```python
import uuid
from xdk.chat.models import SendMessageRequest

message_id = str(uuid.uuid4())
# chat has keys loaded and set_identity called (see Getting Started)
payload = chat.encrypt_message(
message_id,
sender_id,
conversation_id,
raw_conv_key,
caption or "",
conversation_key_version,
signing_key_version,
conversation_key=raw_conv_key,
conversation_key_version=conversation_key_version,
attachments=[{
"attachment_type": "media",
"media_hash_key": media_hash_key,
Expand All @@ -151,7 +147,7 @@ Cifra con un adjunto de multimedia y luego haz POST del cuerpo send-message (mis
client.chat.send_message(
conversation_id.replace(":", "-"),
SendMessageRequest(
message_id=message_id,
message_id=payload.message_id, # generated by the SDK
encoded_message_create_event=payload.encrypted_content,
encoded_message_event_signature=payload.encoded_event_signature,
),
Expand All @@ -160,37 +156,47 @@ Cifra con un adjunto de multimedia y luego haz POST del cuerpo send-message (mis
</Tab>
<Tab title="TypeScript">
```typescript
const messageId = crypto.randomUUID();
// chat has keys loaded and setIdentity called (see Getting Started)
const payload = chat.encryptMessage({
messageId,
senderId,
conversationId,
conversationKey: rawConvKey,
text: caption || '',
conversationKey: rawConvKey,
conversationKeyVersion,
signingKeyVersion,
attachments: [{
attachmentType: 'media',
mediaHashKey: mediaHashKey,
attachment_type: 'media',
media_hash_key: mediaHashKey,
width,
height,
filesizeBytes: plaintext.byteLength,
filesize_bytes: plaintext.byteLength,
filename: 'photo.jpg',
}],
});
await client.chat.sendMessage(conversationId.replace(/:/g, '-'), {
message_id: messageId,
message_id: payload.messageId, // generated by the SDK
encoded_message_create_event: payload.encryptedContent,
encoded_message_event_signature: payload.encodedEventSignature,
});
```
</Tab>
<Tab title="Rust">
```rust
// Set attachments on EncryptMessageParams per chat_xdk_core AttachmentDescriptor::Media
let payload = chat.encrypt_message(params_with_media_attachment)?;
use chat_xdk_core::{AttachmentDescriptor, EncryptMessageParams};

// chat has keys loaded and set_identity called (see Getting Started)
let mut params = EncryptMessageParams::new(&conversation_id, caption)
.with_conversation_key(conv_key.to_bytes(), &conversation_key_version);
params.attachments = Some(vec![AttachmentDescriptor::Media {
media_hash_key: media_hash_key.clone(),
width,
height,
filesize_bytes: plaintext.len() as i64,
filename: "photo.jpg".into(),
media_type: None,
duration_millis: None,
}]);
let payload = chat.encrypt_message(params)?;
let body = serde_json::json!({
"message_id": message_id,
"message_id": payload.message_id, // generated by the SDK
"encoded_message_create_event": payload.encrypted_content,
"encoded_message_event_signature": payload.encoded_event_signature,
});
Expand All @@ -203,10 +209,12 @@ Cifra con un adjunto de multimedia y luego haz POST del cuerpo send-message (mis
</Tab>
<Tab title="Go">
```go
// chat has keys loaded and SetIdentity called (see Getting Started)
payload, err := chat.EncryptMessage(chatxdk.EncryptMessageParams{
MessageID: messageID, SenderID: senderID, ConversationID: conversationID,
ConversationKey: rawConvKey, Text: caption,
ConversationKeyVersion: conversationKeyVersion, SigningKeyVersion: signingKeyVersion,
ConversationID: conversationID,
Text: caption,
ConversationKey: rawConvKey,
ConversationKeyVersion: conversationKeyVersion,
Attachments: []chatxdk.AttachmentDescriptor{{
AttachmentType: "media",
MediaHashKey: mediaHashKey,
Expand All @@ -216,49 +224,51 @@ Cifra con un adjunto de multimedia y luego haz POST del cuerpo send-message (mis
Filename: "photo.jpg",
}},
})
// POST payload.EncryptedContent / EncodedEventSignature to /2/chat/conversations/{id}/messages
// POST payload.MessageID (generated by the SDK), payload.EncryptedContent,
// and payload.EncodedEventSignature to /2/chat/conversations/{id}/messages
```
</Tab>
<Tab title="C#">
```csharp
var payload = chat.EncryptMessage(new EncryptMessageParams {
MessageId = messageId,
SenderId = senderId,
ConversationId = conversationId,
// chat has keys loaded and SetIdentity called (see Getting Started)
var payload = chat.EncryptMessage(new EncryptMessageParams(conversationId, caption ?? "")
{
ConversationKey = rawConvKey,
Text = caption ?? "",
ConversationKeyVersion = conversationKeyVersion,
SigningKeyVersion = signingKeyVersion,
// Attachments = media descriptor with MediaHashKey, Width, Height,
// FilesizeBytes, and Filename (as in the Go tab above)
Attachments = new[]
{
AttachmentDescriptor.Media(mediaHashKey, width, height, plaintext.Length, "photo.jpg"),
},
});
// POST EncryptedContent / EncodedEventSignature as for text messages
// POST payload.MessageId (generated by the SDK), payload.EncryptedContent,
// and payload.EncodedEventSignature as for text messages
```
</Tab>
<Tab title="Java">
```java
EncryptMessageParams params = new EncryptMessageParams();
params.messageId = messageId;
params.senderId = senderId;
params.conversationId = conversationId;
// chat has keys loaded and setIdentity called (see Getting Started)
EncryptMessageParams params =
new EncryptMessageParams(conversationId, caption != null ? caption : "");
params.conversationKey = rawConvKey;
params.text = caption != null ? caption : "";
params.conversationKeyVersion = conversationKeyVersion;
params.signingKeyVersion = signingKeyVersion;
// params.attachments — media type with mediaHashKey, width, height, filename
params.attachments = List.of(AttachmentDescriptor.media(
mediaHashKey, width, height, plaintext.length, "photo.jpg", null, null));
SendPayload payload = chat.encryptMessage(params);
// POST to /2/chat/conversations/{id}/messages
// POST payload.messageId (generated by the SDK), payload.encryptedContent,
// and payload.encodedEventSignature to /2/chat/conversations/{id}/messages
```
</Tab>
</Tabs>

El par de clave de conversación se puede omitir por completo: con `set_cache_keys(true)` habilitado, `encrypt_message` resuelve la clave y la versión desde el último cambio de clave verificado de la conversación (consulta [Primeros pasos](/es/xchat/getting-started)).

---

## Descargar y descifrar

Ruta: [`GET /2/chat/media/{conversation_id}/{media_hash_key}`](/x-api/chat/download-chat-media). El cuerpo de la respuesta es texto cifrado. En los mensajes entrantes, lee `media_hash_key` desde los adjuntos descifrados / `media_hashes`.
Ruta: [`GET /2/chat/media/{conversation_id}/{media_hash_key}`](/x-api/chat/download-chat-media). El cuerpo de la respuesta es texto cifrado. En mensajes entrantes, lee `media_hash_key` de los adjuntos descifrados / `media_hashes`.

**Elige la clave por la versión de clave del evento.** Cada evento de mensaje descifrado lleva la `keyVersion` (JS; `key_version` en los demás bindings) con la que se cifró su contenido. Descifra un adjunto con la clave de conversación de **esa** versión—`conversationKeys.keys[event.keyVersion]`—no la más reciente. Después de una rotación de clave (por ejemplo al agregar un miembro), la clave más reciente no puede descifrar multimedia adjunta a mensajes anteriores.
**Elige la clave por la versión de clave del evento.** Cada evento de mensaje descifrado lleva el `keyVersion` (JS; `key_version` en los otros bindings) con el que se cifró su contenido. Descifra un adjunto con la clave de conversación de **esa** versión—`conversationKeys.keys[event.keyVersion]`—no la más reciente. Después de una rotación de clave (por ejemplo la incorporación de un miembro), la última clave no puede descifrar multimedia adjunta a mensajes anteriores.

<Tabs>
<Tab title="Python">
Expand Down Expand Up @@ -358,9 +368,9 @@ Ruta: [`GET /2/chat/media/{conversation_id}/{media_hash_key}`](/x-api/chat/downl

## Consejos

- Usa la misma **versión de clave de conversación** que cuando se cifró la multimedia
- No registres multimedia en texto plano ni claves en bruto
- Usa la misma **versión de clave de conversación** con la que se cifró el contenido multimedia
- No registres en logs contenido multimedia en texto plano ni claves en bruto
- Detecta el MIME **después** de descifrar
- Clientes web: cifra/descifra en el cliente cuando sea posible; mantén los tokens de OAuth en tu servidor
- Clientes web: cifra/descifra en el cliente cuando sea posible; mantén los tokens OAuth en tu servidor

Los esquemas completos de solicitud y respuesta para cada ruta de multimedia están bajo **API reference → Media** en la barra lateral (inicializar subida, añadir chunk, finalizar subida y descargar multimedia).
Los esquemas completos de solicitud y respuesta para cada ruta de multimedia están bajo **API reference → Media** en la barra lateral (initialize upload, append chunk, finalize upload y download media).
Loading