Script d'aperçu des liens sur Yakihonne

Script d'aperçu des liens sur Yakihonne

Par défaut la génération des aperçus des liens ne fonctionne pas sur Yakihonne.com. #yakihonne #nostrfr

Pour résoudre ce problème, j’ai trouvé l’astuce suivante :

  1. Installer l’extension Tampermonkey
  2. Dans les paramètres de l’extension autoriser les scripts utilisateur image
  3. Dans Tampermonkey créer le script suivant :
// ==UserScript==
// @name         Yakihonne - Aperçus URL carte compacte
// @namespace    local.yakihonne.preview
// @version      2.2
// @description  Affiche une carte compacte avec image complète à gauche et titre centré à droite
// @match        https://yakihonne.com/*
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';

    const processed = new Set();

    const style = document.createElement('style');
    style.textContent = `
        .yh-preview-card {
            display: flex;
            align-items: stretch;
            width: 100%;
            max-width: 100%;
            height: 96px;
            margin: 8px 0 10px 0;
            border: 1px solid #ddd;
            border-radius: 14px;
            overflow: hidden;
            background: #f7f3f1;
            text-decoration: none;
            cursor: pointer;
            box-sizing: border-box;
        }

        .yh-preview-card:hover {
            background: #f0ecea;
        }

        .yh-preview-image {
            width: 220px;
            min-width: 220px;
            height: 100%;
            overflow: hidden;
            background: #f7f3f1;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .yh-preview-image img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            display: block;
            background: #f7f3f1;
        }

        .yh-preview-title {
            flex: 1;
            min-width: 0;
            padding: 10px 14px;
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            color: #111;
            font-size: 16px;
            font-weight: 700;
            line-height: 1.25;
        }

        .yh-preview-title span {
            display: -webkit-box;
            -webkit-line-clamp: 3;
            -webkit-box-orient: vertical;
            overflow: hidden;
        }

        @media (max-width: 700px) {
            .yh-preview-image {
                width: 140px;
                min-width: 140px;
            }

            .yh-preview-title {
                font-size: 14px;
                padding: 8px;
            }
        }
    `;
    document.head.appendChild(style);

    function escapeHtml(text) {
        return String(text || '').replace(/[&<>"']/g, c => ({
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"',
            "'": '''
        }[c]));
    }

    function meta(doc, selector) {
        return doc.querySelector(selector)?.getAttribute('content') || '';
    }

    function absoluteUrl(value, base) {
        try {
            return new URL(value, base).href;
        } catch {
            return '';
        }
    }

    function fetchPreview(url) {
        return new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'GET',
                url,
                timeout: 15000,

                onload: res => {
                    try {
                        const doc = new DOMParser().parseFromString(
                            res.responseText,
                            'text/html'
                        );

                        const title =
                            meta(doc, 'meta[property="og:title"]') ||
                            meta(doc, 'meta[name="twitter:title"]') ||
                            doc.querySelector('title')?.textContent ||
                            url;

                        const image =
                            meta(doc, 'meta[property="og:image"]') ||
                            meta(doc, 'meta[name="twitter:image"]') ||
                            '';

                        resolve({
                            url,
                            title: title.trim(),
                            image: absoluteUrl(image, url)
                        });
                    } catch {
                        resolve(null);
                    }
                },

                onerror: () => resolve(null),
                ontimeout: () => resolve(null)
            });
        });
    }

    function createCard(data) {
        if (!data.image) return null;

        const card = document.createElement('a');

        card.className = 'yh-preview-card';
        card.href = data.url;
        card.target = '_blank';
        card.rel = 'noopener noreferrer';

        card.innerHTML = `
            <div class="yh-preview-image">
                <img src="${escapeHtml(data.image)}" loading="lazy">
            </div>
            <div class="yh-preview-title">
                <span>${escapeHtml(data.title)}</span>
            </div>
        `;

        card.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            window.open(data.url, '_blank', 'noopener,noreferrer');
        }, true);

        return card;
    }

    async function scanLinks() {
        const links = document.querySelectorAll('a[href^="http"]');

        for (const link of links) {
            const url = link.href;

            if (!url) continue;
            if (url.includes('yakihonne.com')) continue;
            if (url.startsWith('nostr:')) continue;
            if (link.closest('.yh-preview-card')) continue;

            const key = url + '|' + link.textContent;

            if (processed.has(key)) continue;
            processed.add(key);

            const preview = await fetchPreview(url);

            if (!preview) continue;

            const card = createCard(preview);

            if (!card) continue;

            if (
                !link.nextElementSibling ||
                !link.nextElementSibling.classList.contains('yh-preview-card')
            ) {
                link.insertAdjacentElement('afterend', card);
            }
        }
    }

    const observer = new MutationObserver(() => {
        clearTimeout(window.__yhPreviewTimer);

        window.__yhPreviewTimer = setTimeout(() => {
            scanLinks();
        }, 500);
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    setTimeout(scanLinks, 1500);
    setInterval(scanLinks, 3000);
})();

Normalement vous devriez obtenir le résultat suivant : image


Write a comment
No comments yet.