Repository navigation
Fuentes externas
English · Español
Todo lo que la app descarga de fuera de Scryfall pasa por un único cliente HTTP reforzado, fuentes::Externo (src-tauri/src/fuentes/mod.rs): una lista cerrada de sitios y prefijos de ruta, una nueva comprobación en cada redirección, un tope de bytes aplicado mientras se lee en flujo y unas imágenes que siempre se decodifican y se vuelven a codificar. Encima hay tres fuentes de datos: la base de datos de fichas de Cockatrice (cartas que Scryfall no tiene) y dos índices de nombres (MTGJSON sin conexión y magicthegathering.io en línea) para traducir nombres de carta de otros idiomas.
Límites: Scryfall tiene su propio cliente con sus propias reglas (Integración con Scryfall). Los packs de marcos, las letras y los temas también usan Externo, pero se documentan con sus funcionalidades (Packs de marcos, Temas y skins).
| Fichero | Responsabilidad |
|---|---|
src-tauri/src/fuentes/mod.rs |
PERMITIDOS, permitida(), url_permitida(), Externo (bajar, bajar_como_navegador, bajar_a_fichero), limpiar_imagen(), carta_cockatrice(), buscar_cockatrice(), nombres_ingleses(), resumen_cockatrice(), meta_cockatrice(). |
src-tauri/src/fuentes/cockatrice.rs |
Catalogo, FichaCockatrice, catalogo(): parsea e indexa tokens.xml. |
src-tauri/src/fuentes/mtgjson.rs |
Índice de nombres en otros idiomas sin conexión (SQLite), descargar(), info(), quitar(), nombres_ingleses(). |
src-tauri/src/fuentes/mtgio.rs |
Búsqueda de nombres en línea contra magicthegathering.io (último recurso). |
src-tauri/src/commands/fuentes.rs |
Comandos de Ajustes names_index_info, names_index_download, names_index_remove; evento names://progress. |
src-tauri/src/commands/cards.rs |
card_fetch_externa, card_search/card_prints (añaden resultados de Cockatrice), deck_tokens_detect. |
src-tauri/src/commands/identify.rs |
card_link_externa (metadatos para vincular una carta propia a una ficha externa). |
src-tauri/src/state.rs |
AppState.externo: Arc<Externo> (un cliente para toda la app). |
src/features/settings/nombres-idiomas.tsx |
Interfaz de Ajustes para el índice de MTGJSON (descarga, progreso, eliminación). |
Por qué existe (justificación deducida del código y de sus comentarios; los comentarios dicen qué se aplica, no un modelo de amenazas): varias de estas URL vienen de datos de terceros (por ejemplo, las URL de imagen dentro del tokens.xml de Cockatrice, o un destino de redirección elegido por un servidor), así que la app nunca debe poder ser dirigida a un host arbitrario, nunca debe almacenar en memoria datos sin límite y nunca debe guardar tal cual bytes controlados por un atacante.
Decisión y mecanismo, capa por capa:
-
Lista cerrada (
PERMITIDOS). Una URL solo se permite si el esquema eshttps, no hay puerto explícito y el host es exactamente igual a una entrada y la ruta empieza por el prefijo de esa entrada (permitida()). La igualdad exacta del host rechaza imitaciones comoi.imgur.com.evil.com; la regla de no puerto rechaza:8443. Test:solo_lo_de_la_lista. -
Las redirecciones se vuelven a comprobar.
Externo::nuevo()instalaPolicy::custom: más de 3 saltos es un error; una redirección a una URL permitida se sigue; a una no permitida la redirección se detiene, así que la respuesta 3xx vuelve y se notifica como un fallo (estado no exitoso).bajar_contambién vuelve a comprobar lar.url()final. -
Tope de tamaño, en flujo. Primero se comprueba la cabecera
Content-Lengthcuando está presente; después el cuerpo se lee en trozos y la descarga se corta en cuanto superaríatope(InvalidInput,motivo = grande). Un servidor que mienta sobre la longitud, o que la omita, no puede hacer que la app almacene más del tope. -
Los bytes nunca se guardan tal como llegan. Las imágenes pasan por
limpiar_imagen()(más abajo): decodificar y luego codificar de nuevo. Los catálogos se parsean y solo se conservan los datos parseados; un fichero descargado solo se guarda en disco si se parsea (Catalogo::leer(&x)?antes deescribir_atomico). -
Integridad donde hay un hash. El zip de MTGJSON se comprueba contra el SHA-256 que MTGJSON publica a su lado; las descargas de packs de marcos y letras con commit fijo comprueban un SHA-256 por fichero a partir de los manifiestos incluidos (
src-tauri/assets/packs-descargables.json,src-tauri/assets/letras-libres.json,src-tauri/crates/proxy-core/assets/marcos/original/imagenes.json). -
Fallo suave. Toda fuente opcional se degrada a «sin resultado» (lista vacía,
Errregistrado contracing::warn!) en lugar de romper el flujo principal.
| Host | Prefijo de ruta | Lo usa |
|---|---|---|
raw.githubusercontent.com |
/Cockatrice/Magic-Token/ |
Catálogo de fichas (cockatrice.rs) |
raw.githubusercontent.com |
/SlightlyCircuitous/image-storage/ |
Imágenes de fichas a las que enlaza el catálogo |
cdn.staticneo.com |
/w/mtg/ |
Imágenes de fichas a las que enlaza el catálogo |
i.imgur.com |
/ |
Imágenes de fichas a las que enlaza el catálogo |
api.magicthegathering.io |
/v1/cards |
mtgio.rs |
raw.githubusercontent.com |
/PoolFlamingo/proxy-builder-packs/ |
Packs de marcos descargables (diseno/descargables.rs) |
mtgjson.com |
/api/v5/ |
mtgjson.rs |
tweakcn.com |
/r/themes/ |
Importación de temas (temas.rs) |
fonts.googleapis.com |
/css |
Letras de los temas, CSS (bajar_como_navegador) |
fonts.gstatic.com |
/s/ |
Letras de los temas, ficheros woff2 |
Añadir una fuente significa añadir primero una fila aquí; no hay otro modo de llegar a la red a través de Externo. url_permitida() también acepta //host/ruta sin esquema (algunos catálogos lo usan) anteponiendo https:.
A modo de orientación, otras llamadas salientes tienen su propia protección:
| Quien llama | Mecanismo |
|---|---|
scryfall/client.rs |
Host fijo api.scryfall.com, límite de ritmo, reintentos. Imágenes solo desde URL que devolvió Scryfall. |
commands/cards.rs import_archidekt
|
reqwest normal a archidekt.com/api/decks/{id}/ donde {id} es un entero parseado (Parseo de la lista del mazo). |
ocr.rs descargar_modelos
|
reqwest normal al host de los modelos de OCR; sin comprobación de hash (Identificación de cartas y OCR). |
diseno/original.rs, diseno/letras_libres.rs
|
reqwest normal a un commit fijo de GitHub, con SHA-256 verificado por fichero. |
| Método | Comportamiento |
|---|---|
Externo::nuevo() |
Construye el cliente: user agent ProxyBuilder/<version>, tiempo de espera de 20 s, la política de redirección de arriba. |
bajar(url, tope) -> Vec<u8> |
Comprobación de la lista de permitidos, GET, comprobación del estado (NotFound para 404, Network en otro caso), tope de tamaño, cuerpo completo en memoria. |
bajar_como_navegador(url, tope) |
Igual, pero envía un user agent de navegador de escritorio; Google Fonts solo sirve woff2 a los navegadores que conoce. |
bajar_a_fichero(url, ruta, tope, progreso) -> sha256 hex |
Escribe en flujo a un fichero con un tiempo de espera de 600 s, calcula el hash mientras escribe, notifica (bytes, total?) y borra el fichero parcial si se supera el tope. Se usa para el zip de 52 MB de MTGJSON. |
Constantes: MAX_IMAGEN = 15 MiB, LADO_MAXIMO = 12 000 px, PNG_GRANDE = 2 MiB, tamaño de salida 745 x 1040 (el tamaño de los PNG de Scryfall).
-
imagesize::blob_sizelee solo la cabecera; si falla ->ErrorCode::Image(motivo = no_es_imagen); si cualquiera de los lados supera los 12 000 px ->Image(demasiado_grande), de modo que una bomba de descompresión se rechaza antes de decodificar. -
decode::decode(deproxy-core::imaging) la decodifica por completo a RGBA. - Si la entrada era un JPEG (bytes mágicos
FF D8), o es «grande» (más de 2 MiB o más de 1500 px de alto): se redimensiona a 745 x 1040 con Lanczos3 cuando es mayor que eso y se codifica como JPEG con calidad 95. - En otro caso se codifica como PNG.
Obsérvese que el paso 3 redimensiona a la caja exacta de 745 x 1040 (imageops::resize), es decir, una imagen grande se fuerza a la proporción de la carta en lugar de dejar bandas (intención deducida: la app estira las imágenes a la casilla de la carta, véase Imagen y PDF). Test: las_imagenes_se_vuelven_a_codificar (un PNG sigue siendo PNG, un JPEG sigue siendo JPEG, un fichero HTML es Image).
El repositorio Magic-Token de Cockatrice (tokens.xml) lista aproximadamente un millar de fichas; unas 80 de ellas no tienen imagen en Scryfall (por ejemplo «Kelp», el Muro de plantas 0/1 que crea Wall of Kelp). Esas son las que importa la app.
-
Filtro. Al parsear, se descarta cualquier ficha con al menos una URL de imagen que contenga
scryfall.io: Scryfall ya la sirve y aparecería dos veces. Las fichas sin ninguna imagen se conservan en el catálogo pero nunca se ofrecen (todos los consumidores comprueban!imagenes.is_empty()). -
FichaCockatrice:id,nombre,tipo,texto,fr(fuerza/resistencia),colores,imagenes: Vec<(set, url)>,creadores(las cartasreverse-relatedque la crean).linea_de_tipo()antepone «Token » cuando falta, como las líneas de tipo de Scryfall. -
Id estable. El XML no tiene ids, así que
id= los primeros 8 bytes (16 caracteres hex) del SHA-256 de nombre, tipo, texto y F/R (huella). No debe cambiar entre descargas porque los mazos lo guardan enCardSource::Custom { externa: Some(RefExterna { fuente: Cockatrice, id }) }. -
Ciclo de vida del catálogo (
catalogo()). UnArc<Catalogo>por sesión en unMutex<Option<...>>estático. Se carga desdeapp_cache_dir/cockatrice-tokens.xmlsi tiene menos de 7 días; si no, se descarga (tope de 8 MiB), se valida parseándolo y se guarda de forma atómica. Si la descarga falla se usa el fichero viejo; si no hay ninguno, se propaga el error. La caché puede borrarse libremente. -
Índices. Por nombre normalizado (
exactas) y por creador normalizado (creadas_por, que prueba el nombre completo de la carta y cada cara separada por//).buscar(texto)necesita 3+ caracteres y ordena: nombre exacto, prefijo, prefijo de palabra y subcadena. -
El parseo es tolerante (
roxmltree): los campos que faltan pasan a ser cadenas vacías; un XML inválido esInvalidInput(motivo = cockatrice), testun_xml_roto_no_vale. Fixture:src-tauri/src/fixtures/cockatrice-tokens.xml.
| Lugar | Cómo |
|---|---|
| Búsqueda y variantes de «Añadir carta» |
card_search / card_prints añaden filas CardSummary con fuente = Cockatrice (miniatura mediante pbimg://.../t/, descargada por el manejador del protocolo a través de Externo). Solo para el idioma inglés / sin definir y no en el modo de solo alta resolución, ya que las imágenes de Cockatrice no son de alta resolución. |
| Resolver una lista |
Guardar::externa_por_nombre (una línea que Scryfall no encontró pero Cockatrice tiene por nombre exacto) y externas_creadas_por (fichas creadas por las cartas del mazo, cuando «añadir fichas» está activado). Implementado en EnLaApp en commands/cards.rs. |
| «Detectar fichas» |
deck_tokens_detect también propone fichas de Cockatrice creadas por cartas del mazo, saltándose los nombres ya presentes. |
| Vincular imágenes propias |
identify.rs añade a la lista de candidatas las coincidencias de nombre exacto de Cockatrice; card_link_externa devuelve el CardMeta para vincular (Identificación de cartas y OCR). |
| Añadir la carta |
card_fetch_externa(fuente, id) -> carta_cockatrice(). |
Para cada URL de imagen de la ficha, en orden: Externo::bajar(url, MAX_IMAGEN), luego limpiar_imagen, luego blobs.put_bytes(..., ImageOrigin::Upload). El primer éxito devuelve una DeckCard con CardSource::Custom { scryfall_id: None, externa: Some(...) }, front = la imagen almacenada, meta de meta_cockatrice (layout token, idioma en, set = código de set de la primera imagen, línea de tipo). Si todas las URL fallan, se devuelve el último error, o NotFound con motivo = sin_imagen.
Nota de diseño: es una carta propia (custom) con una referencia externa en lugar de una variante nueva de CardSource, de modo que las versiones antiguas de la app abren el mazo y ven una carta propia normal (Modelo del mazo). El exportador trata esas cartas como fichas y las deja fuera de las listas de texto (Parseo de la lista del mazo).
Ambas fuentes responden a una misma pregunta: dado un nombre de carta escrito en el idioma xx («Muro de algas», idioma es), ¿cuál es el nombre en inglés? Después el resolvedor pide a Scryfall ese nombre en inglés. fuentes::nombres_ingleses(state, nombre, idioma) es el único punto de entrada:
flowchart TD
A["nombres_ingleses(nombre, idioma)"] --> B{"¿índice de MTGJSON descargado?"}
B -- sí --> C["mtgjson::nombres_ingleses<br/>consulta SQLite, puede quedar vacía"]
C --> R["devolver (sin más reservas)"]
B -- no --> D["mtgio::nombres_ingleses<br/>HTTPS, tope de 2 MiB, memo de sesión"]
D --> R
Obsérvese la primera rama: cuando el índice existe pero no tiene coincidencia, la respuesta es una lista vacía; entonces no se consulta magicthegathering.io.
Quienes llaman: resolve.rs paso 1b (mediante Guardar::nombre_ingles, usando el primer resultado) e identify.rs cuando Scryfall no encuentra nada para el texto leído o escrito.
-
Qué se almacena. De
AtomicCards.json.zip(unos 52 MB) solo las tripletas(nombre extranjero normalizado, código de idioma de Scryfall, nombre en inglés), enapp_cache_dir/mtgjson/nombres.sqlite(tablanombres, índicenombres_extranjero). MTGJSON no tiene fichas que Scryfall no tenga, así que son solo nombres. Los nombres de idioma se mapean encodigo()(español, francés, alemán, italiano, portugués, japonés, coreano, ruso, chino simplificado/tradicional, fyrexiano). -
Flujo de descarga (
descargar): pedirAtomicCards.json.zip.sha256(tope de 1 KiB) -> escribir el zip en flujo a un fichero conbajar_a_fichero(tope de 120 MiB, fase de progresoBajando) -> comparar el SHA-256 con el publicado (si no coincide: borrar el zip y fallar conNetwork,motivo = huella) -> enspawn_blocking: abrir el zip, leer la entradaAtomicCards.jsoncon un tope de 1 GiB, reunir las tripletas (leer_nombres, faseLeyendo) -> escribir un fichero SQLite temporal y renombrarlo sobre el viejo (escribir_base) -> borrar el zip. La conexión de solo lectura en caché se cierra (cerrar_base) antes del reemplazo. -
Búsqueda.
SELECT DISTINCT ingles FROM nombres WHERE extranjero = ?1 ORDER BY (idioma = ?2) DESC, ingles: primero los nombres en el idioma pedido, pero una coincidencia en cualquier otro idioma también cuenta. DevuelveNone(noSome(vec![])) cuando el fichero no existe, que es lo que hace que quien llama recurra a la fuente en línea. -
Ajustes.
names_index_info->NombresIdiomasInfo { descargado, fecha, nombres, tamano }(tamanoes una estimación fija de 52 MiB);names_index_download(marca un trabajo en marcha constate.trabajo()y emitenames://progresscon{ fase: bajando|leyendo, hecho, total });names_index_removeborra la carpeta. Interfaz:src/features/settings/nombres-idiomas.tsx. - Por qué es opcional: la descarga es grande y la mayoría de los usuarios solo necesitan inglés; sin ella, la reserva en línea sigue funcionando para las listas que usan nombres en otros idiomas.
- Consulta:
GET /v1/cards?name=<text>&language=<lang>&pageSize=10, mapeando los códigos de Scryfall a los nombres de idioma de la API (idioma_api; el inglés no tiene, así que no devuelve nada). - La respuesta se parsea de forma tolerante (
foreignNames); los nombres en inglés de las cartas cuyo nombre extranjero es igual a la consulta van primero, los demás después. - Los resultados se memorizan durante la sesión (se vacían al llegar a 2 000 entradas). Sin red o con una respuesta incorrecta, se obtiene una lista vacía y un aviso; la documentación la califica de lenta y poco fiable, de ahí «último recurso». Test:
el_nombre_ingles_de_la_exacta_primerocon el fixturesrc-tauri/src/fixtures/mtgio-muro-de-algas.json.
| Condición | Resultado |
|---|---|
| URL fuera de la lista de permitidos, esquema/puerto incorrecto, mal formada |
InvalidInput con motivo = host, url
|
| Cuerpo por encima del tope |
InvalidInput, motivo = grande
|
| HTTP 404 / otro no 2xx / redirección rechazada |
NotFound / Network (con url, estado) |
| No es una imagen, o lados por encima de 12 000 px |
Image (motivo = no_es_imagen / demasiado_grande) |
| XML/zip del catálogo ilegible |
InvalidInput (motivo = cockatrice / mtgjson) |
| Hash de MTGJSON no coincide |
Network (motivo = huella) |
| Falla una consulta opcional (catálogo de Cockatrice, mtgio) | Se registra, se trata como sin resultados |
src-tauri/src/fuentes/mod.rs (lista de permitidos, recodificación), cockatrice.rs (filtrado, búsqueda, creadores, XML roto), mtgio.rs (parseo), mtgjson.rs (indice_de_nombres: construcción, búsqueda, idioma de reserva, eliminación). Existen tests de red en vivo pero están marcados #[ignore] (módulos en_vivo). Los caminos del resolvedor que usan estas fuentes se prueban con falsos en src-tauri/src/scryfall/resolve.rs.
- Nuevo recurso descargable: añade su host/prefijo a
PERMITIDOS(pruébalo ensolo_lo_de_la_lista), usaExterno::bajarcon un tope explícito y nunca escribas los bytes en bruto: parsea o fija y verifica un SHA-256. - Nueva fuente de imágenes: pasa siempre los bytes por
limpiar_imagenantes deblobs.put_bytes. - Nueva fuente de cartas: añade una variante de
FuenteCarta(src-tauri/crates/proxy-core/src/model.rs), imitacarta_cockatrice/meta_cockatrice/resumen_cockatricey amplíacard_fetch_externaycard_link_externa.
- Integración con Scryfall - la fuente principal y cómo la resolución usa estas reservas.
- Identificación de cartas y OCR - vincular cartas propias, que también busca en estas fuentes.
-
Packs de marcos y Temas y skins - otros usuarios de
Externo. - Almacenamiento y blobs - dónde se almacenan las imágenes limpias y qué cachés son desechables.
- Ajustes y shell - la pantalla de Ajustes que aloja el índice de nombres.
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Set-Browser · Explorador de sets
Reference · Referencia