"; } else { const bloggerWidgetContainer = currentScriptElement ? currentScriptElement.closest('.widget.HTML') : null; if (bloggerWidgetContainer) { // If placeholder is not directly previous, try to find it within the current Blogger HTML widget const placeholderInWidget = bloggerWidgetContainer.querySelector('.featured-content-module-placeholder'); if (placeholderInWidget) { widgetRootElement = placeholderInWidget; widgetRootElement.id = mainWidgetContainerId; widgetRootElement.className = 'featured-content-module-wrapper'; widgetRootElement.innerHTML = ""; } } if (!widgetRootElement) { const tempLoadingDiv = document.querySelector("div.widget.HTML[id='" + (currentScriptElement && currentScriptElement.closest('.widget') ? currentScriptElement.closest('.widget').id : '') + "'] .fcm-loading"); if(tempLoadingDiv) tempLoadingDiv.textContent = "Error de inicialización del widget (placeholder no encontrado)."; console.error("FCM Widget: Placeholder div not found or not correctly placed."); return; } } let blogHomepageUrl = ''; if (typeof BLOG_CMT_domain == 'string' && BLOG_CMT_domain !== '') { blogHomepageUrl = window.location.protocol + '//' + BLOG_CMT_domain; } else if (typeof _WidgetManager != 'undefined' && _WidgetManager._bfo && _WidgetManager._bfo.baseUrl) { blogHomepageUrl = _WidgetManager._bfo.baseUrl.replace(/\/$/, ''); } else { blogHomepageUrl = window.location.origin; } let featuredTag = 'DESTACADO_POR_DEFECTO_INTERNO'; // Default internal tag // Try to get tag from a div.fcm-widget-config-tag within the same parent as the placeholder if (widgetRootElement && widgetRootElement.parentElement) { const configTagDivSibling = widgetRootElement.parentElement.querySelector(".fcm-widget-config-tag"); if (configTagDivSibling && configTagDivSibling.textContent && configTagDivSibling.textContent.trim() !== "") { featuredTag = configTagDivSibling.textContent.trim(); } else { // Fallback for older structures or if config is inside the Blogger widget container but outside placeholder's direct parent const bloggerWidgetContainer = currentScriptElement.closest('.widget.HTML'); if (bloggerWidgetContainer) { const configTagDivInBloggerWidget = bloggerWidgetContainer.querySelector(".fcm-widget-config-tag"); if (configTagDivInBloggerWidget && configTagDivInBloggerWidget.textContent && configTagDivInBloggerWidget.textContent.trim() !== "") { featuredTag = configTagDivInBloggerWidget.textContent.trim(); } } } } const NUM_POSTS = 3; const DEFAULT_THUMBNAIL_URL = "https://via.placeholder.com/1200x675.png?text=Sin+Imagen"; window[callbackFunctionName] = function(json) { const postsDisplayArea = document.getElementById(postsDisplayAreaId); if (!postsDisplayArea) { return; } postsDisplayArea.innerHTML = ''; function escapeHtml(unsafe) { if (typeof unsafe !== 'string') return ''; const div = document.createElement('div'); div.appendChild(document.createTextNode(unsafe)); return div.innerHTML; } function getPostImage(entry) { // 1. Check media$thumbnail (Blogger's provided thumbnail) if (entry.media$thumbnail && entry.media$thumbnail.url) { let thumbnailUrl = entry.media$thumbnail.url; // If it's a YouTube thumbnail, attempt to get a higher quality version if (thumbnailUrl.includes("img.youtube.com/vi/")) { const videoIdMatch = thumbnailUrl.match(/\/vi\/([a-zA-Z0-9_-]{11})\//); if (videoIdMatch && videoIdMatch[1]) { // Prefer sddefault.jpg (640x480) or hqdefault.jpg (480x360) // You can also try maxresdefault.jpg, but it might not always exist. return `https://img.youtube.com/vi/${videoIdMatch[1]}/sddefault.jpg`; } // If regex fails, return the original YouTube thumbnail (it might be hqdefault already) return thumbnailUrl; } else { // For regular Blogger images, try to get a larger size (s1200) return thumbnailUrl.replace(/\/s[0-9]+(-c)?\//, '/s1200/'); } } // 2. Fallback: Search for high-resolution images in post content (e.g. blogger_img_proxy) if (entry.content && entry.content.$t) { const content = entry.content.$t; // Regex for blogger_img_proxy, trying to get a large version const proxyRegex = /(https:\/\/lh3\.googlecontent\.com\/blogger_img_proxy\/[^"']+(?:=w\d+-h\d+[^"']*)?)/g; let matches = content.match(proxyRegex); if (matches && matches.length > 0) { let bestMatch = matches[0]; if (bestMatch.includes("=")) { bestMatch = bestMatch.replace(/=w\d+[^"']*/, '=s1200').replace(/=s\d+[^"']*/, '=s1200'); } else { bestMatch += "=s1200"; } return bestMatch; } // General image regex if no proxy found (less reliable for quality) const imgRegex = /]+src="([^">]+)"/ig; // Added 'i' for case-insensitive let firstImgSrc = null; let matchResult; // Find the first valid image source that isn't a known low-quality placeholder (like feedburner) while((matchResult = imgRegex.exec(content)) !== null) { if (matchResult[1] && !matchResult[1].includes("feeds.feedburner.com")) { firstImgSrc = matchResult[1]; break; } } if (firstImgSrc) { let imgSrc = firstImgSrc; // Attempt to resize if it's a Blogger image if (imgSrc.includes("googlecontent.com/") && !imgSrc.includes("blogger_img_proxy")) { imgSrc = imgSrc.replace(/\/s[0-9]+(-c)?\//, '/s1200/'); } return imgSrc; } } // 3. Final fallback return DEFAULT_THUMBNAIL_URL; } function createSnippet(content, length = 100) { if (!content) return ''; const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; // Decodes HTML entities let text = tempDiv.textContent || tempDiv.innerText || ""; text = text.replace(/\s+/g, ' ').trim(); // Normalize whitespace return text.length > length ? escapeHtml(text.substring(0, length)) + '...' : escapeHtml(text); } function createPostItemHtml(postEntry, itemClass, snippetLength) { const postTitle = postEntry.title.$t; const postUrlEntry = postEntry.link.find(link => link.rel === 'alternate'); const postUrl = postUrlEntry ? postUrlEntry.href : '#'; // Fallback URL const imageUrl = getPostImage(postEntry); const snippetText = createSnippet(postEntry.summary && postEntry.summary.$t ? postEntry.summary.$t : (postEntry.content ? postEntry.content.$t : ''), snippetLength); return ( '' + '
' + '' + escapeHtml(postTitle) + '' + '
' + '
' + '

' + escapeHtml(postTitle) + ' u31x

' + (snippetText ? '

' + snippetText + '

' : '') + '
' + '
' ); } if (!json.feed || !json.feed.entry || json.feed.entry.length === 0) { postsDisplayArea.innerHTML = "
No se encontraron posts con la etiqueta '" + escapeHtml(featuredTag) + "'.
"; return; } const posts = json.feed.entry; let finalHtmlContent = ""; if (posts[0]) { finalHtmlContent += createPostItemHtml(posts[0], "fcm-main", 120); } if (posts.length > 1) { let secondaryHtml = ""; for (let i = 1; i < Math.min(posts.length, NUM_POSTS); i++) { secondaryHtml += createPostItemHtml(posts[i], "fcm-secondary-item", 70); } if (secondaryHtml) { finalHtmlContent += "
" + secondaryHtml + "
"; } } if (finalHtmlContent === "") { postsDisplayArea.innerHTML = "
No hay posts para mostrar con la etiqueta '" + escapeHtml(featuredTag) + "'.
"; } else { postsDisplayArea.innerHTML = finalHtmlContent; } }; // Fin de window[callbackFunctionName] if (!blogHomepageUrl) { if (widgetRootElement) widgetRootElement.innerHTML = "
Error: No se pudo determinar la URL del blog.
"; return; } const feedUrl = blogHomepageUrl + "/feeds/posts/default/-/" + encodeURIComponent(featuredTag) + "?alt=json-in-script" + "&max-results=" + NUM_POSTS + "&callback=" + callbackFunctionName; const feedScript = document.createElement('script'); feedScript.type = 'text/javascript'; feedScript.src = feedUrl; feedScript.onerror = function() { const postsDisplayArea = document.getElementById(postsDisplayAreaId); if (postsDisplayArea) postsDisplayArea.innerHTML = "
Error al cargar los datos de los posts.
"; }; document.body.appendChild(feedScript); })(); //]]>
Russian drones and missiles target Ukraine's eastern city of Kharkiv, killing 3, officials say

Russian drones and missiles target Ukraine's eastern city of Kharkiv, killing 3, officials say 3e713p

The barrage — the latest in near daily widescale attacks — included aerial glide bombs that have become part of a fierce Russian onslaught on Ukraine…
More
Colombian authorities carry out rescues after rafts and kayak capsize in deadly flash flooding

Colombian authorities carry out rescues after rafts and kayak capsize in deadly flash flooding z2u1x

Several rafts and a kayak capsized Friday on the Güejar River in central Colombia due to a sudden flood that left at least four people dead, the Nati…
More
Riot police and anti-ICE protesters square off in LA

Riot police and anti-ICE protesters square off in LA 6v2j4g

Protests erupted in Los Angeles after ICE agents targeted several locations as a part of President Trump's crackdown on illegal immigration.
More
Google's DolphinGemma: AI Unlocks the Secrets of Dolphin Communication

Google's DolphinGemma: AI Unlocks the Secrets of Dolphin Communication f5z5b

In a groundbreaking intersection of artificial intelligence and marine biology, Google has unveiled DolphinGemma, an advanced AI model designed to an…
More
CCTV video shows moment of Russian drone strike in Kharkiv, Ukraine

CCTV video shows moment of Russian drone strike in Kharkiv, Ukraine 2w404s

Seventeen people were wounded in a Russian drone strike on the eastern Ukrainian city of Kharkiv early Thursday, including children, a pregnant woman…
More
Reaction to recovery of bodies of two Israeli-American hostages from Gaza

Reaction to recovery of bodies of two Israeli-American hostages from Gaza 2x185m

Israel has recovered the bodies of two Israeli-American hostages taken in Hamas’ October 7, 2023, attack that ignited the war in the Gaza Strip.
More
Trump announces travel ban and restrictions on 19 countries

Trump announces travel ban and restrictions on 19 countries 4hk3e

President Donald Trump has signed a travel ban on 12 countries, primarily in Africa and the Middle East, from coming to America.
More
Trump suspends entry of international students studying at Harvard

Trump suspends entry of international students studying at Harvard 5b6qq

US President Donald Trump suspended for an initial six months the entry into the United States of foreign nationals seeking to study or participate i…
More
The BTS effect: K-beauty brands target US despite tariffs

The BTS effect: K-beauty brands target US despite tariffs 6u3o5n

On the back of the popularity for cultural exports such as K-Pop and their roaring online sales through Amazon, South Korea's cosmetic startups a…
More
`; container.appendChild(feedSection); const targetElement = document.getElementById(feedConfig.targetElementId); if (!targetElement) { console.error(`Elemento target no encontrado: ${feedConfig.targetElementId}`); const errorDiv = document.createElement('div'); errorDiv.className = 'error-rss'; errorDiv.innerHTML = `Error de configuración: Contenedor para "${sanitizeHtml(feedConfig.title)}" no encontrado.`; // Adjuntar al feedSection en lugar de al container principal para errores por feed feedSection.querySelector('.rss-items-wrapper').innerHTML = errorDiv.outerHTML; return; } try { const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedConfig.url)}`; const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`Error de red/servidor (${response.status}) al ar rss2json para ${sanitizeHtml(feedConfig.title)}.`); } const data = await response.json(); if (data.status !== 'ok') { // Intenta mostrar el mensaje de error de rss2json si está disponible const apiErrorMessage = data.message ? `API Error: ${data.message}` : `Error desconocido de rss2json`; throw new Error(`${apiErrorMessage} para ${sanitizeHtml(feedConfig.title)}.`); } if (!data.items || data.items.length === 0) { targetElement.innerHTML = '
No se encontraron entradas en este feed.
'; return; } targetElement.innerHTML = ''; // Limpiar "Cargando" const items = data.items.slice(0, maxItemsPerFeed); items.forEach((item, index) => { const title = sanitizeHtml(item.title || 'Sin título'); const link = item.link || '#'; const rawSnippet = item.description || item.content || ''; // rss2json pone el contenido en description const cleanSnippet = sanitizeHtml(rawSnippet); const truncatedSnippet = truncateText(cleanSnippet, snippetMaxLength); const imageUrl = extractImageUrl(item); // Usa la función modificada const itemDiv = document.createElement('div'); itemDiv.className = 'rss-item'; // onerror para imagen: intenta cargar el placeholder si la imagen principal falla const imageOnErrorScript = `this.onerror=null; this.src='${placeholderImageUrl}'; this.classList.add('image-load-error');`; // Si quieres el layout de "1 grande, 2 pequeños" DENTRO de CADA feed // necesitarías modificar la lógica de cómo se añaden los items aquí. // Por ahora, mantendré la lógica original de este script que parece ser: // - El primer item (.featured-item) se añade directamente. // - Los siguientes items se agrupan en un .small-item-container con .small-item. // Esta lógica ya estaba en tu script, la he mantenido. let itemHTML = ` ${title}

${title} 6j6g1m

${truncatedSnippet}

`; // La lógica de featured-item y small-item-container ya estaba en tu script. // Si quieres el layout 1 grande, 2 pequeños, 1 grande, 2 pequeños etc. // esta lógica debería funcionar para el primer grupo (1 grande, N-1 pequeños). // Si maxItemsPerFeed es 3, tendrás 1 grande y 2 pequeños. if (index === 0 && maxItemsPerFeed > 1) { // Solo si hay más de 1 item, el primero es "featured" itemDiv.classList.add('featured-rss-item'); // Renombrada para evitar colisión con AFCM itemDiv.innerHTML = itemHTML; targetElement.appendChild(itemDiv); } else { // Agrupar los siguientes en un contenedor si no existe ya para este feed let smallItemsPairContainer = targetElement.querySelector('.secondary-rss-items-pair'); if (!smallItemsPairContainer) { smallItemsPairContainer = document.createElement('div'); smallItemsPairContainer.className = 'secondary-rss-items-pair'; // Contenedor para pares targetElement.appendChild(smallItemsPairContainer); } const secondaryItemDiv = document.createElement('div'); secondaryItemDiv.className = 'rss-item secondary-rss-item'; // Ítem individual del par secondaryItemDiv.innerHTML = itemHTML; smallItemsPairContainer.appendChild(secondaryItemDiv); } }); } catch (error) { console.error(`Error al obtener o procesar el feed "${sanitizeHtml(feedConfig.title)}":`, error.message, error); targetElement.innerHTML = `
No se pudo cargar: ${sanitizeHtml(feedConfig.title)}.
${sanitizeHtml(error.message)}
`; } } if (mainWidgetContainer) { if (feedsConfig && feedsConfig.length > 0) { feedsConfig.forEach((config) => { // No es necesario `async` aquí en el forEach si `fetchAndDisplayFeed` es `async` fetchAndDisplayFeed(config, mainWidgetContainer); // No esperamos aquí, dejamos que se ejecuten en paralelo }); // El separador
se añade después de que todos los fetchAndDisplayFeed se hayan INICIADO, // no necesariamente completado. Si el orden es crítico, se necesitaría un enfoque diferente. // Para simplificar y asegurar que el
se añade después de cada sección de feed: // (Esta parte del separador es un poco compleja de hacer bien con async/await en un forEach, // la forma original de añadirlo podría ser más robusta si el orden importa visualmente) // Por ahora, lo eliminamos de aquí para no complicar y lo gestionamos con CSS (gap en #dual-rss) } else { mainWidgetContainer.innerHTML = "
No hay feeds configurados para mostrar.
"; } } else { console.error("Contenedor principal del widget 'dual-rss' no encontrado."); } }); //]]>