[{"data":1,"prerenderedAt":360},["ShallowReactive",2],{"\u002Fprojects\u002Freddit-archive\u002F":3,"posts":246},{"id":4,"title":5,"body":6,"description":181,"extension":182,"featured":183,"images":184,"links":203,"meta":210,"navigation":183,"order":211,"path":212,"seo":213,"stack":214,"stats":230,"stem":242,"tag":243,"url":244,"__hash__":245},"projects\u002Fprojects\u002Freddit-archive.md","Reddit Archive",{"type":7,"value":8,"toc":169},"minimark",[9,14,70,74,79,105,109,116,120,127,131],[10,11,13],"h2",{"id":12},"qué-hace","Qué hace",[15,16,17,25,36,42,48,54,60],"ul",{},[18,19,20,24],"li",{},[21,22,23],"strong",{},"Lo borrado, de vuelta."," En el perfil de cualquier usuario, un botón trae sus publicaciones y comentarios del archivo de Arctic Shift, incluidos los que ya no están en Reddit o los que se editaron después.",[18,26,27,30,31,35],{},[21,28,29],{},"Posts completos."," En la página de un post borrado muestra el título y el texto originales y, si quieres, todo el árbol de comentarios. También funciona con los enlaces cortos ",[32,33,34],"code",{},"\u002Fs\u002F"," de la app.",[18,37,38,41],{},[21,39,40],{},"Exportar a un usuario."," Lo cargado o toda la actividad del usuario, página a página, a JSON, Markdown o CSV. Un post con sus comentarios se descarga o se copia en JSON.",[18,43,44,47],{},[21,45,46],{},"Buscar y filtrar."," Búsqueda en el texto, filtros por subreddit y fechas, y orden por más recientes, más antiguos o puntuación.",[18,49,50,53],{},[21,51,52],{},"Estadísticas."," Subreddits donde más participa, sus flairs en cada uno y un gráfico de actividad por día, semana, mes o año. Al pasar por encima de un subreddit, su descripción, suscriptores y normas.",[18,55,56,59],{},[21,57,58],{},"Sin confusiones."," Todo lo que viene del archivo va en un panel propio con la etiqueta \"Archivado\" y la fuente. Lo nativo de Reddit no se toca. Si la cuenta está eliminada o suspendida, lo avisa.",[18,61,62,65,66,69],{},[21,63,64],{},"Solo cuando lo pides."," No hace ninguna petición al archivo hasta que pulsas ",[21,67,68],{},"Ver archivo",", salvo que actives la carga automática. Sin telemetría ni servidores propios. Español, inglés y chino.",[10,71,73],{"id":72},"cómo-está-hecho","Cómo está hecho",[75,76,78],"h3",{"id":77},"un-cliente-para-una-api-que-no-controlas","Un cliente para una API que no controlas",[80,81,82,83,90,91,94,95,94,98,94,101,104],"p",{},"Toda la información sale de ",[84,85,89],"a",{"href":86,"rel":87},"https:\u002F\u002Farctic-shift.photon-reddit.com",[88],"nofollow","Arctic Shift",", un archivo público de Reddit. Cada respuesta se valida antes de usarla: primero la forma del sobre, luego cada elemento, y después Zod la normaliza a un modelo propio con la procedencia marcada. Los errores no se lanzan: son resultados tipados (",[32,92,93],{},"http",", ",[32,96,97],{},"timeout",[32,99,100],{},"schema",[32,102,103],{},"not_found","…) que la interfaz traduce a un mensaje.",[75,106,108],{"id":107},"background-caché-en-dos-niveles-y-rate-limit","Background, caché en dos niveles y rate limit",[80,110,111,112,115],{},"Las búsquedas pasan del content script al service worker por mensajes. Hay una caché en memoria por pestaña y otra en ",[32,113,114],{},"chrome.storage.session"," con caducidad, que solo guarda respuestas correctas. El cliente lee las cabeceras de rate limit y, si quedan pocas peticiones, espera antes de lanzar la siguiente.",[75,117,119],{"id":118},"un-panel-en-shadow-dom-encima-de-una-spa","Un panel en Shadow DOM encima de una SPA",[80,121,122,123,126],{},"El panel es una app de Vue 3 montada en un shadow root encima del feed. Un ",[32,124,125],{},"MutationObserver"," lo vuelve a montar si Reddit repinta la página, y otro sigue su tema claro u oscuro.",[10,128,130],{"id":129},"problemas-que-resolví","Problemas que resolví",[15,132,133,139,149,159],{},[18,134,135,138],{},[21,136,137],{},"\"Más antiguos\" que no lo era."," Ordenaba solo lo ya cargado, que eran las páginas más recientes. Ahora se pide al servidor en orden ascendente y se pagina hacia delante con un cursor.",[18,140,141,144,145,148],{},[21,142,143],{},"Detectar cuentas borradas."," El aviso buscaba palabras como \"suspended\" en el texto de la página y saltaba con cualquier comentario que las tuviera. Ahora se pregunta a Reddit por ",[32,146,147],{},"about.json",".",[18,150,151,154,155,158],{},[21,152,153],{},"HTML de terceros."," El Markdown archivado se convierte con marked y se limpia con DOMPurify, quitando también los estilos, porque un ",[32,156,157],{},"position: fixed"," podía tapar la página entera.",[18,160,161,164,165,168],{},[21,162,163],{},"CSV que se ejecuta."," Un comentario que empieza por ",[32,166,167],{},"="," se convierte en una fórmula al abrir el CSV en una hoja de cálculo. Las columnas de texto se protegen, pero no las numéricas, para no romper puntuaciones negativas.",{"title":170,"searchDepth":171,"depth":171,"links":172},"",2,[173,174,180],{"id":12,"depth":171,"text":13},{"id":72,"depth":171,"text":73,"children":175},[176,178,179],{"id":77,"depth":177,"text":78},3,{"id":107,"depth":177,"text":108},{"id":118,"depth":177,"text":119},{"id":129,"depth":171,"text":130},"Extensión de Chrome que recupera, desde el archivo público de Arctic Shift, los posts y comentarios borrados o editados de Reddit y los muestra dentro de la propia página, siempre marcados como archivados. Perfiles, posts con su árbol de comentarios, estadísticas por subreddit y exportación de toda la actividad de un usuario a JSON, Markdown o CSV. No pide nada hasta que pulsas el botón.","md",true,[185,190,195,199],{"src":186,"alt":187,"width":188,"height":189},"\u002Fimages\u002Fposts\u002Freddit-archive\u002Fportada.webp","El panel de Reddit Archive sobre un perfil de demostración con una cuenta eliminada, mostrando sus publicaciones archivadas",1280,900,{"src":191,"alt":192,"width":193,"height":194},"\u002Fimages\u002Fposts\u002Freddit-archive\u002Fpost-comentarios.webp","Una publicación borrada recuperada del archivo, con su árbol de comentarios debajo",820,1360,{"src":196,"alt":197,"width":193,"height":198},"\u002Fimages\u002Fposts\u002Freddit-archive\u002Festadisticas.webp","La pestaña de estadísticas con los subreddits más activos, los flairs del autor y un gráfico de actividad por meses",968,{"src":200,"alt":201,"width":193,"height":202},"\u002Fimages\u002Fposts\u002Freddit-archive\u002Fexportar.webp","El menú Exportar con JSON, Markdown y CSV, tanto para lo cargado como para cargar todo y exportar",420,[204,207],{"label":205,"url":206},"Cómo se usa","\u002Fposts\u002Freddit-archive-lo-borrado-de-reddit",{"label":208,"url":209},"Por dentro","\u002Fposts\u002Freddit-archive-por-dentro",{},11,"\u002Fprojects\u002Freddit-archive",{"title":5,"description":181},[215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],"WXT","Manifest V3","Vue 3","TypeScript","Zod","Arctic Shift API","Chrome Storage","Shadow DOM","marked","DOMPurify","Vitest","happy-dom","Playwright","ESLint","Prettier",[231,234,237,239],{"value":232,"label":233},"11","endpoints de Arctic Shift",{"value":235,"label":236},"3","formatos de exportación",{"value":235,"label":238},"idiomas",{"value":240,"label":241},"494","tests (Vitest y Playwright)","projects\u002Freddit-archive","reddit-archive","https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Freddit-archive\u002Feiocepcbcfpeiehcgdoghdgonmjgconn","dwbcZf0CZd7IVS2aWslRAr3a__6HeCv_E2cSLjC27Rw",[247,258,267,274,280,288,293,302,310,315,322,327,335,341,347,353],{"title":248,"description":249,"date":250,"path":251,"tags":252,"type":257},"ABR Offline Converter: convierte imágenes, vídeo, PDF y datos sin subir nada a ningún servidor","Un conversor de archivos que funciona entero en tu navegador. Imágenes, vídeo a GIF, vídeo a audio, CSV a JSON, PDF, fuentes y subtítulos, y un Taller del libro que convierte tus capítulos en Markdown en un EPUB con portada e índice. Qué hace y cómo se usa.","2026-10-24","\u002Fposts\u002Foffline-converter-convierte-sin-subir-nada",[253,254,255,256],"offline-converter","privacidad","productividad","proyectos","guia",{"title":259,"description":260,"date":250,"path":261,"tags":262,"type":266},"ABR Offline Converter por dentro: un conversor sin servidor, de ffmpeg.wasm a un EPUB hecho a mano","Cómo funciona un conversor de imágenes, vídeo, PDF, fuentes, subtítulos, texto y datos que no tiene backend. Workers, ffmpeg compilado a WebAssembly, codificadores BMP, ICO y TIFF escritos a mano, 811 páginas prerenderizadas y un Taller del libro que genera EPUB3 sin pandoc.","\u002Fposts\u002Foffline-converter-por-dentro",[253,263,264,265],"arquitectura","typescript","nuxt","tecnico",{"title":268,"description":269,"date":270,"path":271,"tags":272,"type":257},"Simple TTS: escucha cualquier artículo de la web en voz alta, sin salir de tu navegador","Una extensión gratuita para Chrome y Edge que lee en voz alta lo que selecciones o el artículo entero con las voces del propio navegador. Sin cuentas, sin servidores y sin APIs externas. Qué hace y cómo se usa.","2026-10-23","\u002Fposts\u002Fsimple-tts-escucha-cualquier-web",[273,254,255,256],"simple-tts",{"title":275,"description":276,"date":270,"path":277,"tags":278,"type":266},"Simple TTS por dentro: mi primera extensión, de JavaScript a WXT con Vue","Cómo pasó Simple TTS de tres archivos de JavaScript a una extensión Manifest V3 con WXT, Vue 3 y TypeScript. Qué me hizo cambiar en cada etapa, cómo se mantiene vivo un service worker durante una lectura larga, por qué leía la frase del día anterior y cómo se consigue que el resaltado vaya a la par de la voz.","\u002Fposts\u002Fsimple-tts-por-dentro",[273,263,264,279],"vue",{"title":281,"description":282,"date":283,"path":284,"tags":285,"type":266},"TranscribeLocal por dentro: Whisper en el navegador y cómo dejó de inventarse frases","Cómo ejecuta TranscribeLocal Whisper, la separación de hablantes y un modelo de lenguaje dentro del navegador con Transformers.js, por qué Whisper se inventaba frases en los silencios, qué hace falta para transcribir audios largos y por qué un service worker cortaba las descargas a los cinco minutos.","2026-10-22","\u002Fposts\u002Ftranscribe-local-por-dentro",[286,287,263,264,265],"transcribe-local","ia",{"title":289,"description":290,"date":283,"path":291,"tags":292,"type":257},"TranscribeLocal: transcribe y saca subtítulos de cualquier vídeo, sin salir de tu navegador","Una app web que convierte audio y vídeo en texto y subtítulos con Whisper, distingue quién habla y hace resúmenes, todo en tu ordenador. Sin subir nada a ningún servidor, sin límites de duración y gratis. Qué hace y cómo se usa.","\u002Fposts\u002Ftranscribe-local-subtitulos-en-tu-navegador",[286,287,254,256],{"title":294,"description":295,"date":296,"path":297,"tags":298,"type":257},"PokeStore: un tema de WordPress a medida para tiendas de cartas coleccionables","Un tema de WordPress y WooCommerce hecho desde cero para tiendas de Pokémon y One Piece TCG. Un recorrido por la tienda como la ve quien compra, de la portada a la lista de deseos, y por lo que puede ajustar quien la lleva.","2026-10-21","\u002Fposts\u002Fpokestore-tema-wordpress-para-tiendas-tcg",[299,300,301,256],"pokestore","wordpress","woocommerce",{"title":303,"description":304,"date":305,"path":306,"tags":307,"type":257},"YouTube Like Shortcut: da like con una tecla, como en TikTok","Una extensión de Chrome que da like al vídeo o Short de YouTube que estás viendo al pulsar la L. Cómo se instala, qué opciones tiene y por qué no se dispara mientras escribes.","2026-10-20","\u002Fposts\u002Fyoutube-like-shortcut-like-con-una-tecla",[308,309,255,256],"youtube-like-shortcut","youtube",{"title":311,"description":312,"date":305,"path":313,"tags":314,"type":266},"YouTube Like Shortcut por dentro: pulsar un botón en una web que cambia sin avisar","Cómo una extensión de una sola función llega antes que el reproductor de YouTube, encuentra el botón de like entre variantes ocultas y layouts que cambian con el ancho de la ventana, y se asegura de que el like ha entrado aunque YouTube esté reconstruyendo la página.","\u002Fposts\u002Fyoutube-like-shortcut-por-dentro",[308,309,263,264],{"title":316,"description":317,"date":318,"path":319,"tags":320,"type":257},"AI Upscaler: amplía imágenes y quita fondos con IA, sin salir de tu navegador","Una extensión de Chrome que amplía imágenes ×2 o ×4 y elimina fondos con inteligencia artificial, todo en tu equipo. Sin subir nada a ningún servidor, sin cuentas y gratis. Qué hace y cómo se usa.","2026-10-19","\u002Fposts\u002Fai-upscaler-imagenes-con-ia-en-tu-navegador",[321,287,254,256],"ai-upscaler",{"title":323,"description":324,"date":318,"path":325,"tags":326,"type":266},"AI Upscaler por dentro: dos redes neuronales en una extensión de Chrome","Cómo ejecuta AI Upscaler Real-ESRGAN y U²-Net dentro del navegador con ONNX Runtime Web, por qué la imagen se procesa en mosaicos con margen, qué hubo que parchear en el modelo de quitar fondos y qué obstáculos pone una extensión Manifest V3 a los workers y al WebAssembly.","\u002Fposts\u002Fai-upscaler-por-dentro",[321,287,263,264,279],{"title":328,"description":329,"date":330,"path":331,"tags":332,"type":257},"ABR Cipher: cifrado seguro, completamente en tu navegador","Una suite de cifrado que no envía nada a ningún servidor. Cifrar textos y archivos, compartir secretos con un enlace que caduca, guardar contraseñas y destruir archivos. Qué hace y cómo se usa.","2026-10-18","\u002Fposts\u002Fabr-cipher-cifrado-en-tu-navegador",[333,334,254,256],"cipher","seguridad",{"title":336,"description":337,"date":330,"path":338,"tags":339,"type":266},"ABR Cipher por dentro: criptografía sin servidor, de la URL al disco","Por qué ABR Cipher solo usa la Web Crypto API, cómo viaja un secreto en el fragmento de una URL, cómo se cifra un archivo de varios GB en el navegador y qué hace falta para destruir un archivo de verdad, con un compañero en Rust que lee NTFS.","\u002Fposts\u002Fabr-cipher-por-dentro",[333,334,263,264,340],"rust",{"title":342,"description":343,"date":330,"path":344,"tags":345,"type":266},"ABReader por dentro: el lector que no encontré, construido a mi medida","Voces neuronales que se ejecutan en el navegador, sincronización entre dispositivos sin servidor y once formatos detrás de un mismo contrato. Las decisiones técnicas de ABReader y por qué las tomé.","\u002Fposts\u002Fabreader-por-dentro",[346,263,264,265],"abreader",{"title":348,"description":349,"date":330,"path":350,"tags":351,"type":257},"ABReader: tu biblioteca de libros electrónicos en el navegador","Un lector de libros electrónicos que funciona en el navegador, sin cuentas ni servidores. Qué hace, cómo se usa y cómo probarlo en un minuto.","\u002Fposts\u002Fabreader-tu-biblioteca-en-el-navegador",[346,352,256],"lectura",{"title":354,"description":355,"date":330,"path":356,"tags":357,"type":266},"Cómo construí este blog: estático, sin servidor y sin coste de hosting","Por qué elegí Nuxt y Markdown en lugar de WordPress, cómo separo el código privado del sitio publicado y qué se rompió en el primer despliegue.","\u002Fposts\u002Farquitectura-de-este-blog",[265,279,358,359],"devops","meta",1792847364252]