Lecteur Media

Intègre TOUS les lecteurs pour Instagram, Facebook, Twitter, TikTok, Webmshare, (et 50 autres) sur JVC.

// ==UserScript==
// @name         Lecteur Media
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @description  Intègre TOUS les lecteurs pour Instagram, Facebook, Twitter, TikTok, Webmshare, (et 50 autres) sur JVC.
// @author       FaceDePet
// @match        https://www.jeuxvideo.com/forums/*
// @match        https://jvarchive.com/*
// @match        https://jvarchive.st/*
// @icon         https://cdn-icons-png.flaticon.com/512/4187/4187272.png
// @grant        GM_addStyle
// @grant        GM.xmlHttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // =========================================================================
    // == HELPERS
    // =========================================================================

    /**
     * Gère le redimensionnement dynamique des iframes en écoutant les événements `postMessage`.
     */
    const IframeResizeManager = {
        isInitialized: false,
        handlers: {},

        register: function(origin, handler) {
            this.handlers[origin] = handler;
        },

        init: function() {
            if (this.isInitialized) {
                return;
            }
            window.addEventListener('message', this._handleMessage.bind(this));
            this.isInitialized = true;
        },

        _handleMessage: function(event) {
            const handler = this.handlers[event.origin];
            if (handler) {
                handler.process(event);
            }
        }
    };

    // --- Définition des Handlers  ---

    const twitterResizeHandler = {
        maxHeight: 520,

        process: function(event) {
            let data;
            if (typeof event.data === 'string') {
                try { data = JSON.parse(event.data); } catch (e) { return; }
            } else if (typeof event.data === 'object' && event.data !== null) {
                data = event.data;
            } else {
                return;
            }

            const embedData = data['twttr.embed'];
            if (embedData?.method === 'twttr.private.resize' && embedData.params?.[0]?.height) {
                this._resizeIframe(embedData.params[0], event.source);
            }
        },

        _resizeIframe: function(params, sourceWindow) {
            const heightFromTwitter = params.height;
            if (heightFromTwitter <= 0 || !sourceWindow) return;

            const finalHeight = Math.min(heightFromTwitter, this.maxHeight);
            const iframes = document.querySelectorAll('iframe.iframe-twitter');

            for (const iframe of iframes) {
                if (iframe.contentWindow === sourceWindow) {
                    iframe.style.height = `${finalHeight}px`;
                    iframe.scrolling = (heightFromTwitter > this.maxHeight) ? 'yes' : 'no';
                    break;
                }
            }
        }
    };

    const redditResizeHandler = {
        process: function(event) {
            let data;
            if (typeof event.data === 'string') {
                try { data = JSON.parse(event.data); } catch (e) { return; }
            } else if (typeof event.data === 'object' && event.data !== null) {
                data = event.data;
            } else {
                return;
            }

            if (data && data.type === 'resize.embed' && typeof data.data === 'number') {
                const height = data.data;
                this._resizeIframe(height, event.source);
            }
        },

        _resizeIframe: function(height, sourceWindow) {
            if (height <= 0 || !sourceWindow) return;
            const iframes = document.querySelectorAll('iframe.iframe-reddit');

            for (const iframe of iframes) {
                if (iframe.contentWindow === sourceWindow) {
                    iframe.style.height = `${height}px`;
                    break;
                }
            }
        }
    };

    IframeResizeManager.register('https://platform.twitter.com', twitterResizeHandler);
    IframeResizeManager.register('https://embed.reddit.com', redditResizeHandler);

    // =========================================================================
    // == STYLES GLOBAUX
    // =========================================================================
        GM_addStyle(`
        .bloc-embed {
            margin: 1em 0;
            display: flex;
            justify-content: left;
        }
        .iframe-embed, .video-embed, .image-embed, .thumbnail-embed, .facebook-embed-placeholder {
            max-width: 550px;
            width: 100%;
            border-radius: 9px;
            border: none;
            display: block;
            background-color: #1c1c1c;
        }

        html:not(.theme-light) .facebook-embed-placeholder {
            border: 1px solid #444;
        }
        html.theme-light .facebook-embed-placeholder {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        .iframe-twitter {
            height: 500px;
            background-color: transparent;
            transition: height 0.4s ease-in-out;
        }

        .iframe-youtube {
            aspect-ratio: 3 / 2;
            height: auto;
        }
        .iframe-giphy {
            aspect-ratio: 16 / 9;
            height: auto;
        }

        .iframe-streamable {
            height: auto;
        }

        .iframe-youtube, .iframe-streamable, .iframe-tiktok, .iframe-twitch, .iframe-vocaroo, .iframe-reddit, .iframe-giphy {
          max-height: 80vh;
        }
        .iframe-vertical-content {
            max-width: 320px;
            max-height: 65vh;
        }
        .iframe-youtube-short {
            aspect-ratio: 9 / 16;
            height: auto;
        }

        .youtube-facade-container {
            position: relative;
            display: block;
            aspect-ratio: 3 / 2;
            max-width: 550px;
            width: 100%;
        }
        .youtube-facade-overlay {
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
            cursor: pointer;
            background: transparent;
            z-index: 1;
        }
        .youtube-facade-container .iframe-youtube {
            position: absolute;
            top: 0; left: 0;
            width: 100%; height: 100%;
        }

        .iframe-tiktok {
            aspect-ratio: 9 / 16;
            height: 600px;
            max-height: 80vh;
        }
        .iframe-twitch {
                aspect-ratio: 16 / 9;
                height: auto;
        }
        .iframe-vocaroo {
            max-width: 300px;
            width: 100%;
            height: 60px;
        }
        .iframe-reddit {
            height: 500px;
            background-color: transparent;
            transition: height 0.3s ease-in-out;
        }
        .video-embed, .image-embed, .thumbnail-embed {
             height: auto;
             max-height: 80vh;
             object-fit: contain;
        }
        .placeholder-embed {
            padding: 20px;
            text-align: center;
            border-radius: 12px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-size: 14px;
        }

        .placeholder-embed a {
            text-decoration: none;
            font-weight: 500;
        }

        html:not(.theme-light) .placeholder-embed {
            background-color: #2a2a2e;
            border: 1px solid #444;
        }
        html:not(.theme-light) .placeholder-embed a {
            color: #b9bbbe;
        }

        html.theme-light .placeholder-embed {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        html.theme-light .placeholder-embed a {
            color: #555;
        }
        .twitter-loading-placeholder {
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-size: 14px;
            min-height: 120px;
        }

        html:not(.theme-light) .twitter-loading-placeholder {
            background-color: #1c1c1c;
            color: #b9bbbe;
        }

        html.theme-light .twitter-loading-placeholder {
            background-color: #f0f2f5;
            color: #555;
        }
        .snapchat-embed-placeholder {
            max-width: 416px;
            min-height: 650px;
            background-color: #333;
            border-radius: 12px;
        }
        html.theme-light .snapchat-embed-placeholder {
            background-color: #e0e0e0;
        }
        .iframe-google-drive {
            height: 600px;
            max-height: 80vh;
            aspect-ratio: 4 / 3;
        }

        .iframe-google-slides {
            height: auto;
            aspect-ratio: 16 / 9;
        }
        .iframe-google-maps {
            aspect-ratio: 16 / 9;
            height: 450px;
            max-height: 75vh;
        }
        .dead-link-sticker {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            font-size: 13px;
            font-family: Arial, sans-serif;
            color: #8c8c8c;
            background-color: #f0f2f5;
            border: 1px solid transparent;
            padding: 5px 10px;
            border-radius: 8px;
        }
        html:not(.theme-light) .dead-link-sticker {
            color: #b9bbbe;
            background-color: transparent;
            border: transparent;
        }
        .dead-link-sticker img {
            width: 50px;
            height: auto;
        }
        .iframe-flourish {
            height: 450px;
            max-height: 85vh;
            background-color: #ffffff;
        }
        .thumbnail-embed img { width: 100%; height: 100%; object-fit: cover; }
        .thumbnail-embed { position: relative; cursor: pointer; overflow: hidden; }
        .jvchat-content .bloc-embed { justify-content: flex-start; }
        .instagram-placeholder { min-height: 450px; max-width: 500px; width: calc(100% - 20px); margin: 1em auto; border-radius: 8px; }
        .article-preview-card {
            max-width: 550px;
            width: 100%;
            border-radius: 12px;
            border: 1px solid #444;
            display: flex;
            flex-direction: column;
            text-decoration: none;
            overflow: hidden;
            transition: background-color 0.2s ease;
        }
        html:not(.theme-light) .article-preview-card {
            background-color: #2a2a2e;
        }
        html:not(.theme-light) .article-preview-card:hover {
            background-color: #333338;
        }
        html.theme-light .article-preview-card {
            border: 1px solid #ddd;
            background-color: #f0f2f5;
        }
        html.theme-light .article-preview-card:hover {
            background-color: #e8eaf0;
        }
        .article-preview-image {
            width: 100%;
            aspect-ratio: 1.91 / 1;
            background-size: cover;
            background-position: center;
            border-bottom: 1px solid #444;
        }
        html.theme-light .article-preview-image {
            border-bottom: 1px solid #ddd;
        }
        .article-preview-content {
            padding: 12px 15px;
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        .article-preview-sitename {
            font-size: 12px;
            font-family: Arial, sans-serif;
            text-transform: uppercase;
        }
        .article-preview-title {
            font-size: 16px;
            font-weight: bold;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        .article-preview-description {
            font-size: 14px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            max-height: 5.4em; /* Limite à environ 3 lignes */
            overflow: hidden;
            text-overflow: ellipsis;
        }
        html:not(.theme-light) .article-preview-sitename { color: #8c8c8c; }
        html:not(.theme-light) .article-preview-title { color: #e4e6eb; }
        html:not(.theme-light) .article-preview-description { color: #b9bbbe; }
        html.theme-light .article-preview-sitename { color: #65676b; }
        html.theme-light .article-preview-title { color: #050505; }
        html.theme-light .article-preview-description { color: #65676b; }

        .distrokid-embed-card {
            display: flex;
            align-items: center;
            max-width: 550px;
            width: 100%;
            border-radius: 12px;
            overflow: hidden;
            text-decoration: none;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            transition: background-color 0.2s ease;
        }
        html:not(.theme-light) .distrokid-embed-card {
            background-color: #2a2a2e;
            border: 1px solid #444;
        }
        html.theme-light .distrokid-embed-card {
            background-color: #f0f2f5;
            border: 1px solid #ddd;
        }
        html:not(.theme-light) .distrokid-embed-card:hover {
             background-color: #333338;
        }
        html.theme-light .distrokid-embed-card:hover {
            background-color: #e8eaf0;
        }

        .distrokid-album-art {
            width: 90px;
            height: 90px;
            flex-shrink: 0;
            background-size: cover;
            background-position: center;
        }

        .distrokid-content {
            flex-grow: 1;
            padding: 10px 15px;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            height: 90px;
            box-sizing: border-box;
        }

        .distrokid-title {
            font-size: 16px;
            font-weight: 600;
            display: -webkit-box;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        html:not(.theme-light) .distrokid-title { color: #e4e6eb; }
        html.theme-light .distrokid-title { color: #050505; }

        .distrokid-artist {
            font-size: 14px;
        }
        html:not(.theme-light) .distrokid-artist { color: #b9bbbe; }
        html.theme-light .distrokid-artist { color: #65676b; }

        .distrokid-content audio {
            width: 100%;
            height: 30px;
        }

      .tweet-loading-overlay {
          position: absolute;
          top: 0; left: 0;
          width: 100%;
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
          background: rgba(0, 0, 0, 0.7);
          color: white;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 15px;
          font-weight: 500;
          z-index: 10;
          border-radius: 9px;
          backdrop-filter: blur(2px);
          -webkit-backdrop-filter: blur(2px);
      }
      html.theme-light .tweet-loading-overlay {
          background: rgba(255, 255, 255, 0.7);
          color: #0f1419;
      }
      .tweet-embed-wrapper {
          position: relative;
          display: block;
          line-height: 0;
      }

      .overlay-replies-button {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
          background: rgba(20, 23, 26, 0.85);
          backdrop-filter: blur(4px);
          -webkit-backdrop-filter: blur(4px);
          text-align: center;
          padding: 12px 0;
          cursor: pointer;
          border-bottom-left-radius: 9px;
          border-bottom-right-radius: 9px;
          transition: background-color 0.2s ease;
          display: block; /* Visible par défaut */
      }

      .tweet-embed-wrapper.showing-replies .overlay-replies-button {
          display: none;
      }
      .overlay-replies-button a {
          color: white;
          text-decoration: none;
          font-weight: 600;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
          font-size: 14px;
      }

      html.theme-light .overlay-replies-button {
          background: rgba(255, 255, 255, 0.8);
      }
      html.theme-light .overlay-replies-button a {
          color: #0f1419;
      }
    `);


    class EmbedManager {
        constructor(providers) {
            this.providers = providers;
            this.isDarkTheme = !document.documentElement.classList.contains('theme-light');
            this.processedMessages = new WeakSet();
        }

        init() {
            setTimeout(() => {
                const hostname = window.location.hostname;
                if (hostname === 'www.jeuxvideo.com') {
                    this.observeContainer('.conteneur-messages-pagi', '[MiniaTweetPRO+] Observateur du forum JVC activé.');
                    this.observeContainer('#jvchat-main', '[MiniaTweetPRO+] Observateur de JVChat activé.');
                } else if (hostname === 'jvarchive.com' || hostname === 'jvarchive.st') {
                    this.observeContainer('body', '[MiniaTweetPRO+] Observateur de JVArchive activé.');
                }
            }, 85);
        }

        observeContainer(selector, startMessage) {
            const targetNode = document.querySelector(selector);
            if (!targetNode) return;
            const intersectionObserver = new IntersectionObserver((entries, observer) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.processMessage(entry.target);
                        observer.unobserve(entry.target);
                    }
                });
            }, { rootMargin: '400px 0px' });
            const observeNewMessages = (scope) => {
                const messages = scope.querySelectorAll('.txt-msg, .jvchat-bloc-message, .message-content');
                messages.forEach(msg => {
                    if (!this.processedMessages.has(msg)) {
                        this.processedMessages.add(msg);
                        intersectionObserver.observe(msg);
                    }
                });
            };
            observeNewMessages(targetNode);
            const mutationObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            observeNewMessages(node);
                        }
                    });
                });
            });
            mutationObserver.observe(targetNode, { childList: true, subtree: true });
        }

        processMessage(messageDiv) {
            let needsPostProcessing = new Set();
            this.providers.forEach(provider => {
                messageDiv.querySelectorAll(provider.selector).forEach(async (link) => {
                    if (link.dataset.miniatweetProcessed) return;
                    link.dataset.miniatweetProcessed = 'true';

                    try {
                        const embedElement = await provider.createEmbedElement(link, this.isDarkTheme);

                        if (embedElement) {
                            link.after(embedElement);
                            if (provider.postProcess) {
                                needsPostProcessing.add({ name: provider.name, element: embedElement });
                            }
                        }
                    } catch (e) {
                        console.error(`[${provider.name}] Erreur:`, e);
                    }
                });
            });


            setTimeout(() => {
                 needsPostProcessing.forEach(item => {
                    const provider = this.providers.find(p => p.name === item.name);
                    if (provider) provider.postProcess(item.element);
                });
            }, 90);
        }
    }


    // =========================================================================
    // == FOURNISSEURS DE SERVICES
    // =========================================================================
    const providers = [
        {
            name: 'Instagram',
            selector: 'a[href*="instagram.com/"]:not([data-miniatweet-processed])',
            scriptPromise: null,
            loadScript() {
                if (!this.scriptPromise) {
                    this.scriptPromise = new Promise((resolve) => {
                        if (unsafeWindow.instgrm) return resolve();
                        const script = document.createElement('script');
                        script.async = true;
                        script.charset = 'utf-8';
                        script.src = 'https://www.instagram.com/embed.js';
                        script.onload = resolve;
                        document.head.appendChild(script);
                    });
                }
                return this.scriptPromise;
            },
            createEmbedElement(link, isDarkTheme) {
                this.loadScript();
                const cleanedUrl = link.href.replace(/(\.com\/)[^/]+\/((p|reel)\/)/, '$1$2');
                const blockquote = document.createElement('blockquote');
                blockquote.className = 'instagram-media instagram-placeholder';
                blockquote.setAttribute('data-instgrm-permalink', cleanedUrl);
                blockquote.setAttribute('data-instgrm-version', '14');
                blockquote.style.background = isDarkTheme ? '#2f3136' : '#f9f9f9';
                blockquote.style.border = isDarkTheme ? '1px solid #444' : '1px solid #dbdbdb';
                return blockquote;
            },
            postProcess() {
                this.loadScript().then(() => {
                    if (unsafeWindow.instgrm) unsafeWindow.instgrm.Embeds.process();
                });
            }
        },
        {
            name: 'Twitter',
            selector: 'a[href*="twitter.com/"], a[href*="x.com/"]',
            createEmbedElement(link, isDarkTheme) {
                IframeResizeManager.init();

                const parts = link.href.split('/');
                const statusIndex = parts.findIndex(p => p === 'status' || p === 'statuses');
                if (statusIndex === -1 || !parts[statusIndex + 1]) return null;

                const tweetId = parts[statusIndex + 1].split(/[?#]/)[0];
                const uniqueId = `${tweetId}-${Math.random().toString(36).substring(2, 9)}`;
                const containerId = `tweet-container-${uniqueId}`;
                const placeholderId = `tweet-placeholder-${uniqueId}`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<div id="${containerId}" class="tweet-embed-wrapper iframe-embed" style="padding: 0; background-color: transparent;">
                                           <div id="${placeholderId}" class="twitter-loading-placeholder">Chargement du Tweet...</div>
                                       </div>`;

                let isHandled = false;

                const handleResponse = (status, finalUrl) => {
                    if (isHandled) return;
                    isHandled = true;

                    const mainContainer = document.getElementById(containerId);
                    const placeholderElement = document.getElementById(placeholderId);
                    if (!mainContainer || !placeholderElement) return;

                    if (status === 200) {
                        const theme = isDarkTheme ? 'dark' : 'light';
                        const iframeUrl = `https://platform.twitter.com/embed/Tweet.html?id=${tweetId}&theme=${theme}&dnt=true&lang=fr`;
                        const iframeId = `twitter-iframe-${uniqueId}`;

                        placeholderElement.outerHTML = `
                            <iframe id="${iframeId}" src="${iframeUrl}" class="iframe-embed iframe-twitter" title="Twitter Tweet" sandbox="allow-scripts allow-same-origin" allowtransparency="true" allowfullscreen allow="fullscreen" scrolling="no"></iframe>
                            <div class="overlay-replies-button">
                                <a href="#" class="show-replies-btn">Afficher les réponses</a>
                            </div>
                        `;

                        const targetIframe = document.getElementById(iframeId);
                        const repliesContainerButton = mainContainer.querySelector('.overlay-replies-button');

                        targetIframe.addEventListener('load', () => {
                            const loadingOverlay = mainContainer.querySelector('.tweet-loading-overlay');
                            if (loadingOverlay) loadingOverlay.remove();

                            try {
                                if (targetIframe.contentWindow.location.href.includes('platform.twitter.com')) {
                                    mainContainer.classList.remove('showing-replies');
                                } else {
                                    mainContainer.classList.add('showing-replies');
                                }
                            } catch (e) {
                            }
                        });

                        repliesContainerButton.addEventListener('click', (e) => {
                            e.preventDefault();

                            const loadingOverlay = document.createElement('div');
                            loadingOverlay.className = 'tweet-loading-overlay';
                            loadingOverlay.textContent = 'Chargement des réponses...';
                            mainContainer.appendChild(loadingOverlay);

                            const nitterUrl = link.href.replace(/x\.com|twitter\.com/, 'nitter.net');
                            const finalNitterUrl = `${nitterUrl.split('?')[0]}`;

                            targetIframe.src = finalNitterUrl;
                            targetIframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
                            targetIframe.style.height = '80vh';
                            targetIframe.setAttribute('scrolling', 'yes');
                        });

                    } else {
                        mainContainer.className = 'placeholder-embed';
                        mainContainer.innerHTML = `<a href="${finalUrl || link.href}" target="_blank" rel="noopener noreferrer">Ce Tweet n'est plus disponible ou n'a jamais existé.</a>`;
                    }
                };

                GM.xmlHttpRequest({
                    method: "GET",
                    url: `https://publish.twitter.com/oembed?url=${encodeURIComponent(link.href)}`,
                    onload: function(response) { handleResponse(response.status, response.finalUrl); },
                    onerror: function(response) { handleResponse(response.status || 0, response.finalUrl); }
                });

                return container;
            }
        },
        {
            name: 'TikTok',
            selector: 'a[href*="tiktok.com/"], a[href*="vm.tiktok.com/"]',
            async createEmbedElement(link) {
                let finalUrl = link.href;

                if (finalUrl.includes('vm.tiktok.com')) {
                    try {
                        finalUrl = await new Promise((resolve, reject) => {
                            GM.xmlHttpRequest({
                                method: 'HEAD',
                                url: finalUrl,
                                onload: response => resolve(response.finalUrl),
                                onerror: error => reject(error),
                                ontimeout: error => reject(error)
                            });
                        });
                    } catch (error) {
                        console.error('[TikTok] Impossible de résoudre le lien court :', error);
                        return null;
                    }
                }

                const match = finalUrl.match(/\/video\/(\d+)/);

                if (!match || !match[1]) {
                    console.log('[TikTok] Le lien ne semble pas être une vidéo valide :', finalUrl);
                    return null;
                }

                const videoId = match[1];
                const iframeUrl = `https://www.tiktok.com/embed/v2/${videoId}?lang=fr-FR`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<iframe src="${iframeUrl}" class="iframe-embed iframe-tiktok" title="TikTok Video" sandbox="allow-scripts allow-same-origin allow-popups" allow="autoplay; encrypted-media"></iframe>`;
                return container;
            }
        },
        {
            name: 'Streamable',
            selector: 'a[href*="streamable.com/"]',
            createEmbedElement(link) {
                const cleanUrl = link.href.replace('/e/', '/');
                const httpsUrl = cleanUrl.replace('http://', 'https://');
                const iframeUrl = httpsUrl.replace('.com/', '.com/e/');

                const container = document.createElement('div');
                container.className = 'bloc-embed';

                const iframe = document.createElement('iframe');
                iframe.src = iframeUrl;
                iframe.className = 'iframe-embed iframe-streamable';
                iframe.title = "Streamable Video";
                iframe.setAttribute('allowfullscreen', '');
                iframe.style.aspectRatio = '16 / 9';
                container.appendChild(iframe);

                GM.xmlHttpRequest({
                    method: 'GET',
                    url: `https://api.streamable.com/oembed.json?url=${encodeURIComponent(cleanUrl)}`,
                    onload: function(response) {
                        if (response.status >= 200 && response.status < 300) {
                            try {
                                const data = JSON.parse(response.responseText);
                                if (data.width && data.height) {
                                     if (data.height > data.width) {
                                        iframe.classList.add('iframe-vertical-content');
                                    }
                                    iframe.style.aspectRatio = `${data.width} / ${data.height}`;
                                }
                            } catch (e) {
                                console.error('[Streamable API] Erreur de parsing JSON.', e);
                            }
                        }
                    },
                    onerror: function(error) {
                        console.error('[Streamable API] Erreur GM.xmlHttpRequest:', error);
                    }
                });

                return container;
            }
        },
        {
            name: 'Webmshare',
            selector: 'a[href*="webmshare.com/"]',
            createEmbedElement(link) {
                if (!/webmshare\.com\/(play\/)?[\w]+$/.test(link.href)) return null;
                const videoId = link.pathname.split('/').pop();
                if (!videoId) return null;
                const videoUrl = `https://s1.webmshare.com/${videoId}.webm`;
                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<video src="${videoUrl}" class="video-embed" controls autoplay muted loop playsinline></video>`;
                return container;
            }
        },
        {
            name: 'YouTube',
            selector: 'a[href*="youtube.com/watch"], a[href*="youtu.be/"], a[href*="youtube.com/shorts/"]',
            createEmbedElement(link) {
                const youtubeRegex = /(?:[?&]v=|\/shorts\/|youtu\.be\/)([^?&/\s]{11})/;
                const match = link.href.match(youtubeRegex);

                if (!match || !match[1]) return null;
                const videoId = match[1];

                const isShort = link.href.includes('/shorts/');
                if (isShort) {
                    const iframeUrl = `https://www.youtube.com/embed/${videoId}`;
                    const iframeClasses = 'iframe-embed iframe-youtube iframe-youtube-short iframe-vertical-content';
                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe src="${iframeUrl}" class="${iframeClasses}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>`;
                    return container;
                }

                let startTime = null;
                try {
                    const url = new URL(link.href);
                    const timeParam = url.searchParams.get('t');
                    if (timeParam) {
                        let totalSeconds = 0;
                        const hoursMatch = timeParam.match(/(\d+)h/);
                        const minutesMatch = timeParam.match(/(\d+)m/);
                        const secondsMatch = timeParam.match(/(\d+)s/);
                        if (hoursMatch) totalSeconds += parseInt(hoursMatch[1], 10) * 3600;
                        if (minutesMatch) totalSeconds += parseInt(minutesMatch[1], 10) * 60;
                        if (secondsMatch) totalSeconds += parseInt(secondsMatch[1], 10);
                        if (totalSeconds === 0 && /^\d+$/.test(timeParam)) totalSeconds = parseInt(timeParam, 10);
                        if (totalSeconds > 0) startTime = totalSeconds;
                    }
                } catch (e) {}

                const params = new URLSearchParams();
                if (startTime) params.append('start', startTime);
                params.append('enablejsapi', '1');
                params.append('rel', '0');

                const container = document.createElement('div');
                container.className = 'youtube-facade-container';

                const iframe = document.createElement('iframe');
                iframe.className = 'iframe-embed iframe-youtube';
                iframe.title = "YouTube video player";
                iframe.src = `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
                console.log("iframe src: ", iframe.src);
                iframe.setAttribute('frameborder', '0');
                iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share');
                iframe.setAttribute('allowfullscreen', '');

                const facade = document.createElement('div');
                facade.className = 'youtube-facade-overlay';

                container.appendChild(iframe);
                container.appendChild(facade);

                facade.addEventListener('click', () => {
                    try {
                        iframe.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', 'https://www.youtube.com');
                    } catch (e) {
                        console.error("Erreur lors de l'envoi du message à l'iframe YouTube:", e);
                    }
                    facade.remove();
                }, { once: true });

                const finalContainer = document.createElement('div');
                finalContainer.className = 'bloc-embed';
                finalContainer.appendChild(container);

                return finalContainer;
            }
        },
        {
            name: 'Xcancel',
            selector: 'a[href*="xcancel.com/"]',
            createEmbedElement(link, isDarkTheme) {
                IframeResizeManager.init();

                const parts = link.href.split('/');
                const statusIndex = parts.findIndex(p => p === 'status' || p === 'statuses');
                if (statusIndex === -1 || !parts[statusIndex + 1]) return null;

                const tweetId = parts[statusIndex + 1].split(/[?#]/)[0];
                const uniqueId = `${tweetId}-${Math.random().toString(36).substring(2, 9)}`;
                const containerId = `tweet-container-${uniqueId}`;
                const placeholderId = `tweet-placeholder-${uniqueId}`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<div id="${containerId}" class="tweet-embed-wrapper iframe-embed" style="padding: 0; background-color: transparent;">
                                           <div id="${placeholderId}" class="twitter-loading-placeholder">Chargement du Tweet...</div>
                                       </div>`;

                let isHandled = false;

                const handleResponse = (status, finalUrl) => {
                    if (isHandled) return;
                    isHandled = true;

                    const mainContainer = document.getElementById(containerId);
                    const placeholderElement = document.getElementById(placeholderId);
                    if (!mainContainer || !placeholderElement) return;

                    if (status === 200) {
                        const theme = isDarkTheme ? 'dark' : 'light';
                        const iframeUrl = `https://platform.twitter.com/embed/Tweet.html?id=${tweetId}&theme=${theme}&dnt=true&lang=fr`;
                        const iframeId = `twitter-iframe-${uniqueId}`;

                        placeholderElement.outerHTML = `
                            <iframe id="${iframeId}" src="${iframeUrl}" class="iframe-embed iframe-twitter" title="Twitter Tweet" sandbox="allow-scripts allow-same-origin" allowtransparency="true" allowfullscreen allow="fullscreen" scrolling="no"></iframe>
                            <div class="overlay-replies-button">
                                <a href="#" class="show-replies-btn">Afficher les réponses</a>
                            </div>
                        `;

                        const targetIframe = document.getElementById(iframeId);
                        const repliesContainerButton = mainContainer.querySelector('.overlay-replies-button');

                        targetIframe.addEventListener('load', () => {
                            const loadingOverlay = mainContainer.querySelector('.tweet-loading-overlay');
                            if (loadingOverlay) loadingOverlay.remove();

                            try {
                                if (targetIframe.contentWindow.location.href.includes('platform.twitter.com')) {
                                    mainContainer.classList.remove('showing-replies');
                                } else {
                                    mainContainer.classList.add('showing-replies');
                                }
                            } catch (e) {
                            }
                        });

                        repliesContainerButton.addEventListener('click', (e) => {
                            e.preventDefault();

                            const loadingOverlay = document.createElement('div');
                            loadingOverlay.className = 'tweet-loading-overlay';
                            loadingOverlay.textContent = 'Chargement des réponses...';
                            mainContainer.appendChild(loadingOverlay);

                            const nitterUrl = link.href.replace('xcancel.com', 'nitter.net');
                            const finalNitterUrl = `${nitterUrl.split('?')[0]}?theme=${isDarkTheme ? 'dark' : 'light'}`;

                            targetIframe.src = finalNitterUrl;
                            targetIframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
                            targetIframe.style.height = '80vh';
                            targetIframe.setAttribute('scrolling', 'yes');
                        });

                    } else {
                        mainContainer.className = 'placeholder-embed';
                        mainContainer.innerHTML = `<a href="${finalUrl || link.href}" target="_blank" rel="noopener noreferrer">Ce Tweet n'est plus disponible ou n'a jamais existé.</a>`;
                    }
                };

                const twitterApiUrl = link.href.replace('xcancel.com', 'twitter.com');

                GM.xmlHttpRequest({
                    method: "GET",
                    url: `https://publish.twitter.com/oembed?url=${encodeURIComponent(twitterApiUrl)}`,
                    onload: function(response) { handleResponse(response.status, response.finalUrl); },
                    onerror: function(response) { handleResponse(response.status || 0, response.finalUrl); }
                });

                return container;
            }
        },
      {
          name: 'Facebook',
          selector: `
              a[href*="facebook.com/posts/"],
              a[href*="facebook.com/videos/"],
              a[href*="facebook.com/photos/"],
              a[href*="facebook.com/photo/"],
              a[href*="facebook.com/reel/"],
              a[href*="facebook.com/share/"],
              a[href*="facebook.com/photo.php"]
          `,
          scriptPromise: null,
          loadScript() {
              if (!this.scriptPromise) {
                  this.scriptPromise = new Promise((resolve) => {
                      if (unsafeWindow.FB) return resolve();
                      unsafeWindow.fbAsyncInit = function() {
                          unsafeWindow.FB.init({ xfbml: true, version: 'v18.0' });
                          resolve();
                      };
                      const script = document.createElement('script');
                      script.async = true;
                      script.defer = true;
                      script.crossOrigin = 'anonymous';
                      script.src = 'https://connect.facebook.net/fr_FR/sdk.js';
                      document.head.appendChild(script);
                  });
              }
              return this.scriptPromise;
          },
          async createEmbedElement(link) {
              let embedUrl = link.href;
              if (link.href.includes('/share/')) {
                  try {
                      embedUrl = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'HEAD',
                              url: link.href,
                              onload: response => {
                                  resolve(response.finalUrl || link.href);
                              },
                              onerror: error => {
                                  console.error('[Facebook] Erreur de résolution du lien de partage:', error);
                                  resolve(link.href);
                              }
                          });
                      });
                  } catch (e) {
                      console.error('[Facebook] Exception lors de la résolution du lien:', e);
                  }
              }

              this.loadScript();
              const container = document.createElement('div');
              container.className = 'bloc-embed facebook-embed-placeholder';
              container.style.minHeight = '350px';
              container.innerHTML = `<div class="fb-post" data-href="${embedUrl}" data-width="550" data-show-text="true"></div>`;
              return container;
          },
          postProcess(element) {
              this.loadScript().then(() => {
                  if (unsafeWindow.FB) {
                      unsafeWindow.FB.XFBML.parse(element);
                  }
              });
          }
      },
        {
            name: 'Twitch',
            selector: 'a[href*="twitch.tv/"]',
            createEmbedElement(link) {
                const parentHostname = window.location.hostname;

                try {
                    const url = new URL(link.href);
                    const pathParts = url.pathname.split('/').filter(p => p);
                    let iframeSrc = null;

                    if (url.hostname === 'clips.twitch.tv' || pathParts.includes('clip')) {
                        const clipId = pathParts[pathParts.length - 1];
                        if (clipId) {
                            iframeSrc = `https://clips.twitch.tv/embed?clip=${clipId}&parent=${parentHostname}`;
                        }
                    }
                    else if (pathParts[0] === 'videos' && pathParts[1]) {
                        const videoId = pathParts[1];
                        iframeSrc = `https://player.twitch.tv/?video=${videoId}&parent=${parentHostname}`;
                    }
                    // Gère les directs : twitch.tv/CHANNEL_NAME
                    else if (pathParts.length === 1 && pathParts[0]) {
                        const channelName = pathParts[0];
                        iframeSrc = `https://player.twitch.tv/?channel=${channelName}&parent=${parentHostname}`;
                    }

                    if (!iframeSrc) return null;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeSrc}"
                        class="iframe-embed iframe-twitch"
                        title="Lecteur vidéo Twitch"
                        allowfullscreen="true"
                        frameborder="0">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Twitch] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
        },
        {
            name: 'Vocaroo',
            selector: 'a[href*="vocaroo.com/"], a[href*="voca.ro/"]',
            createEmbedElement(link) {
                try {
                    const audioId = link.pathname.split('/').pop();
                    if (!audioId) return null;
                    if (link.pathname === '/') return null;

                    const iframeSrc = `https://vocaroo.com/embed/${audioId}?autoplay=0`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeSrc}"
                        class="iframe-embed iframe-vocaroo"
                        title="Lecteur audio Vocaroo"
                        frameborder="0"
                        allow="autoplay">
                    </iframe>`;

                    return container;

                } catch (e) {
                    console.error('[Vocaroo] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
          }
      },
      {
          name: 'Reddit',
          selector: 'a[href*="reddit.com/"]',
          async createEmbedElement(link, isDarkTheme) {
              IframeResizeManager.init();

              try {
                  let finalUrl;
                  if (link.pathname.includes('/s/')) {
                      finalUrl = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'HEAD',
                              url: link.href,
                              onload: (response) => {
                                  resolve(response.finalUrl);
                              },
                              onerror: (error) => {
                                  console.error('[Reddit] Erreur GM.xmlHttpRequest :', error);
                                  reject(error);
                              }
                          });
                      });
                  } else {
                      finalUrl = link.href;
                  }

                  const urlObject = new URL(finalUrl);

                  if (!urlObject.pathname.includes('/comments/')) {
                      return null;
                  }

                  urlObject.hostname = 'embed.reddit.com';
                  urlObject.searchParams.set('embed', 'true');
                  urlObject.searchParams.set('theme', isDarkTheme ? 'dark' : 'light');
                  urlObject.searchParams.set('showmedia', 'true');
                  urlObject.searchParams.set('showmore', 'false');

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${urlObject.toString()}"
                      class="iframe-embed iframe-reddit"
                      title="Contenu Reddit intégré"
                      sandbox="allow-scripts allow-same-origin allow-popups"
                      height="600"
                      allowfullscreen>
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Reddit] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Vimeo',
          selector: 'a[href*="vimeo.com/"]',
          createEmbedElement(link) {
              const match = link.href.match(/vimeo\.com\/(?:video\/)?(\d+)/);
              if (!match || !match[1]) return null;

              const videoId = match[1];
              const iframeUrl = `https://player.vimeo.com/video/${videoId}?autoplay=1&muted=1&loop=1`;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${iframeUrl}"
                  class="iframe-embed iframe-youtube"
                  title="Vimeo video player"
                  allow="autoplay; fullscreen; picture-in-picture"
                  frameborder="0"
                  allowfullscreen>
              </iframe>`;
              return container;
          }
      },
      {
          name: 'Dailymotion',
          selector: 'a[href*="dailymotion.com/video/"], a[href*="dai.ly/"]',
          createEmbedElement(link) {
              const pathParts = new URL(link.href).pathname.split('/');
              let videoId = null;

              if (link.hostname.includes('dai.ly')) {
                  videoId = pathParts[1];
              } else {
                  const videoIndex = pathParts.findIndex(p => p === 'video');
                  if (videoIndex !== -1 && pathParts[videoIndex + 1]) {
                      videoId = pathParts[videoIndex + 1];
                  }
              }

              if (!videoId) return null;

              const iframeUrl = `https://www.dailymotion.com/embed/video/${videoId}`;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${iframeUrl}"
                  class="iframe-embed iframe-youtube"
                  title="Dailymotion video player"
                  allow="autoplay; fullscreen; picture-in-picture"
                  frameborder="0"
                  allowfullscreen>
              </iframe>`;
              return container;
          }
      },
      {
          name: 'SoundCloud',
          selector: 'a[href*="soundcloud.com/"]',
          async createEmbedElement(link) {
              const pathParts = new URL(link.href).pathname.split('/').filter(p => p);
              if (pathParts.length < 2) return null;

              try {
                  const data = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: `https://soundcloud.com/oembed?format=json&url=${encodeURIComponent(link.href)}&maxheight=166&color=%23ff5500&auto_play=false&show_comments=false`,
                          onload: (response) => {
                              if (response.status >= 200 && response.status < 300) {
                                  resolve(JSON.parse(response.responseText));
                              } else {
                                  reject(new Error(`L'API SoundCloud a retourné le statut ${response.status}`));
                              }
                          },
                          onerror: (error) => reject(new Error('Erreur réseau lors de la requête vers SoundCloud')),
                          ontimeout: () => reject(new Error('La requête vers l\'API SoundCloud a expiré'))
                      });
                  });
                  if (!data || !data.html) {
                      console.warn('[SoundCloud] Pas de HTML d\'intégration dans la réponse API pour', link.href);
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = data.html;
                  return container;

              } catch (error) {
                  console.error('[SoundCloud] Erreur lors de la récupération de l\'embed :', error);
                  return null;
              }
          }
      },
      {
          name: 'StrawPoll',
          selector: 'a[href*="strawpoll.com/"]',
          createEmbedElement(link) {
              const url = new URL(link.href);
              const pathParts = url.pathname.split('/').filter(p => p);

              let pollId = null;
              if (pathParts[0] === 'polls' && pathParts[1]) {
                  pollId = pathParts[1];
              } else if (pathParts.length === 1 && pathParts[0]) {
                  pollId = pathParts[0];
              }

              if (!pollId) return null;

              const iframeUrl = `https://strawpoll.com/embed/${pollId}`;

              const container = document.createElement('div');
              container.className = 'bloc-embed';
              container.innerHTML = `<iframe
                  src="${iframeUrl}"
                  class="iframe-embed"
                  style="width: 100%; height: 450px; border: 0;"
                  title="Sondage StrawPoll">
              </iframe>`;
              return container;
          }
      },
      {
          name: 'Imgur',
          selector: 'a[href*="imgur.com/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                   const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const isDirectMedia = url.hostname === 'i.imgur.com' || /\.(mp4|gifv|webm|jpg|jpeg|png|gif)$/i.test(url.pathname);

                  if (isDirectMedia) {
                      if (/\.(mp4|gifv|webm)$/i.test(url.pathname)) {
                          let videoUrl = url.href.replace('.gifv', '.mp4');
                          container.innerHTML = `<video src="${videoUrl}" class="video-embed" controls autoplay muted loop playsinline></video>`;
                      }
                      else {
                          container.innerHTML = `<img src="${url.href}" class="image-embed" alt="Image depuis Imgur" loading="lazy">`;
                      }
                      return container;
                  }

                 const pathParts = url.pathname.split('/').filter(p => p);
                  if (pathParts.length === 0 || ['upload', 'search'].includes(pathParts[0])) {
                      return null;
                  }

                  const embedId = pathParts.join('/').replace(/\.[^/.]+$/, "");
                  container.classList.add('imgur-embed');

                  const blockquote = document.createElement('blockquote');
                  blockquote.className = 'imgur-embed-pub';
                  blockquote.lang = 'en';
                  blockquote.setAttribute('data-id', embedId);
                  blockquote.innerHTML = `<a href="//imgur.com/${embedId}">${link.textContent || 'Voir sur Imgur'}</a>`;

                  const script = document.createElement('script');
                  script.async = true;
                  script.src = 'https://s.imgur.com/min/embed.js';
                  script.charset = 'utf-8';

                  container.appendChild(blockquote);
                  container.appendChild(script);

                  return container;


              } catch (e) {
                  console.error(`[Imgur] Échec final pour trouver une image valide pour ${link.href}:`, e);
                  const stickerUrl = 'https://risibank.fr/cache/medias/0/5/512/51206/thumb.png';
                  const deadLinkContainer = document.createElement('div');
                  deadLinkContainer.className = 'bloc-embed';
                  deadLinkContainer.innerHTML = `<div class="dead-link-sticker"><img src="${stickerUrl}" alt="[Média supprimé]"><span>[Média supprimé]</span></div>`;
                  return deadLinkContainer;
              }
              return null;
          }
      },
      {
          name: 'Flickr',
          selector: 'a[href*="flickr.com/photos/"], a[href*="flic.kr/p/"]',
          async createEmbedElement(link) {
              try {
                  const apiUrl = `https://www.flickr.com/services/oembed/?url=${encodeURIComponent(link.href)}&format=json&maxwidth=550`;

                  const data = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: apiUrl,
                          onload: (response) => {
                              if (response.status >= 200 && response.status < 300) {
                                  resolve(JSON.parse(response.responseText));
                              } else {
                                  reject(new Error(`Erreur API Flickr: ${response.status}`));
                              }
                          },
                          onerror: (error) => reject(error)
                      });
                  });

                  if (!data || !data.html) {
                      console.warn('[Flickr] Pas de HTML d\'intégration dans la réponse API pour', link.href);
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = data.html;

                  return container;

              } catch (error) {
                  console.error('[Flickr] Erreur lors de la récupération de l\'embed :', error);
                  return null;
              }
          }
      },
      {
          name: 'Spotify',
          selector: 'a[href*="open.spotify.com/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (!/\/(track|album|playlist|artist|episode|show)\//.test(url.pathname)) {
                      return null;
                  }

                  const embedUrl = url.href.replace(
                      'open.spotify.com/',
                      'open.spotify.com/embed/'
                  );

                  const height = url.pathname.includes('/track/') ? '152' : '352';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed"
                      src="${embedUrl}"
                      style="height: ${height}px;"
                      frameborder="0"
                      allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
                      loading="lazy"
                      title="Lecteur Spotify intégré">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Spotify] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Zupimages',
          selector: 'a[href*="zupimages.net/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  let imageUrl = null;

                  if (url.pathname.includes('viewer.php')) {
                      const imageId = url.searchParams.get('id');
                      if (imageId) {
                          imageUrl = `https://zupimages.net/up/${imageId}`;
                      }
                  }
                  else if (url.pathname.startsWith('/up/') && /\.(jpe?g|png|gif|webp)$/i.test(url.pathname)) {
                      imageUrl = link.href;
                  }

                  if (!imageUrl) {
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis Zupimages" loading="lazy">`;
                  return container;

              } catch (e) {
                  console.error('[Zupimages] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
            {
          name: 'Giphy',
          selector: 'a[href*="giphy.com/"], a[href*="gph.is/"]',
          async createEmbedElement(link) {
              try {
                  let finalUrl = link.href;

                  if (link.hostname === 'gph.is') {
                      finalUrl = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'HEAD',
                              url: link.href,
                              onload: response => resolve(response.finalUrl || link.href),
                              onerror: error => reject(error),
                              ontimeout: error => reject(error)
                          });
                      });
                  }

                  const url = new URL(finalUrl);
                  let gifId = null;

                  if (url.hostname === 'media.giphy.com' && url.pathname.includes('/media/')) {
                      const pathParts = url.pathname.split('/');
                      if (pathParts.length > 2 && pathParts[1] === 'media') {
                          gifId = pathParts[2];
                      }
                  } else if (url.hostname === 'giphy.com' && url.pathname.includes('/gifs/')) {
                      const lastPart = url.pathname.split('/').filter(p => p).pop();
                      if (lastPart) {
                          gifId = lastPart.split('-').pop();
                      }
                  }

                  if (!gifId) {
                      return null;
                  }

                  const iframeUrl = `https://giphy.com/embed/${gifId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Giphy embed"
                      frameborder="0"
                      allowfullscreen>
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Giphy] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Telegram',
          selector: 'a[href*="t.me/"]',
          createEmbedElement(link, isDarkTheme) {
              try {
                  const url = new URL(link.href);
                  const pathParts = url.pathname.split('/').filter(p => p);

                  if (pathParts.length < 2 || !/^\d+$/.test(pathParts[1])) {
                      return null;
                  }

                  const postData = `${pathParts[0]}/${pathParts[1]}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const script = document.createElement('script');
                  script.async = true;
                  script.src = 'https://telegram.org/js/telegram-widget.js?22';
                  script.setAttribute('data-telegram-post', postData);
                  script.setAttribute('data-width', '100%');
                  script.setAttribute('data-userpic', 'true');

                  if (isDarkTheme) {
                      script.setAttribute('data-dark', '1');
                  }

                  container.appendChild(script);

                  return container;

              } catch (e) {
                  console.error('[Telegram] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },

      {
          name: 'GoogleDrive',
          selector: 'a[href*="drive.google.com/"], a[href*="docs.google.com/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  let embedUrl = null;
                  let iframeClass = 'iframe-embed iframe-google-drive';

                  const docMatch = url.pathname.match(/\/(document|spreadsheets|presentation|drawings)\/d\/([^/]+)/);
                  if (docMatch) {
                      const docType = docMatch[1];
                      const docId = docMatch[2];
                      const embedType = (docType === 'presentation' || docType === 'drawings') ? 'embed' : 'preview';
                      embedUrl = `https://docs.google.com/${docType}/d/${docId}/${embedType}`;

                      if (docType === 'presentation') {
                          iframeClass = 'iframe-embed iframe-google-slides';
                      }
                  }

                  const formMatch = url.pathname.match(/\/forms\/d\/e\/([^/]+)/);
                  if (formMatch) {
                      const formId = formMatch[1];
                      embedUrl = `https://docs.google.com/forms/d/e/${formId}/viewform?embedded=true`;
                  }

                  const fileMatch = url.pathname.match(/\/file\/d\/([^/]+)/);
                  if (fileMatch) {
                      const fileId = fileMatch[1];
                      embedUrl = `https://drive.google.com/file/d/${fileId}/preview`;
                  }

                  const folderMatch = url.pathname.match(/\/drive\/folders\/([^/]+)/);
                  if (folderMatch) {
                      const folderId = folderMatch[1];
                      embedUrl = `https://drive.google.com/embeddedfolderview?id=${folderId}#list`;
                  }

                  if (!embedUrl) {
                      return null;
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe src="${embedUrl}" class="${iframeClass}" frameborder="0" allowfullscreen></iframe>`;

                  return container;

              } catch (e) {
                  console.error('[GoogleDrive] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'IssouTV',
          selector: 'a[href*="issoutv.com/videos/"]',
          createEmbedElement(link) {
              try {
                  const videoId = new URL(link.href).pathname.split('/').pop();

                  if (!videoId) return null;
                  const videoUrl = `https://issoutv.com/storage/videos/${videoId}.webm`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<video
                      src="${videoUrl}"
                      class="video-embed"
                      controls
                      autoplay
                      muted
                      loop
                      playsinline>
                  </video>`;

                  return container;

              } catch (e) {
                  console.error('[IssouTV] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Bilibili',
          selector: 'a[href*="bilibili.com/video/"]',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/video\/(BV[a-zA-Z0-9]+)/);
                  if (!match || !match[1]) {
                      return null;
                  }

                  const videoId = match[1];
                  const iframeUrl = `https://player.bilibili.com/player.html?bvid=${videoId}&page=1&high_quality=1`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Lecteur vidéo Bilibili"
                      scrolling="no"
                      border="0"
                      frameborder="0"
                      framespacing="0"
                      allowfullscreen="true">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Bilibili] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Koreus',
          selector: 'a[href*="koreus.com/video/"]',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/video\/(.+?)\.html/);
                  if (!match || !match[1]) {
                      return null;
                  }

                  const videoId = match[1];
                  const iframeUrl = `https://www.koreus.com/embed/${videoId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Lecteur vidéo Koreus"
                      frameborder="0"
                      allowfullscreen>
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[Koreus] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'GoogleMaps',
          selector: 'a[href*="google.com/maps/place/"], a[href*="google.com/maps/@"], a[href*="maps.app.goo.gl/"]',
          async createEmbedElement(link) {
              try {
                  let finalUrl = link.href;
                  if (link.hostname === 'maps.app.goo.gl') {
                      finalUrl = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'HEAD',
                              url: link.href,
                              onload: response => resolve(response.finalUrl || link.href),
                              onerror: error => reject(error)
                          });
                      });
                  }

                  const url = new URL(finalUrl);
                  let query = null;

                  const placeMatch = url.pathname.match(/\/place\/([^/]+)/);
                  if (placeMatch && placeMatch[1]) {
                      query = placeMatch[1];
                  }
                  else {
                      const coordsMatch = url.pathname.match(/@(-?\d+\.\d+,-?\d+\.\d+)/);
                      if (coordsMatch && coordsMatch[1]) {
                          query = coordsMatch[1];
                      }
                  }

                  if (!query) {
                      console.warn('[GoogleMaps] Impossible d\'extraire la localisation depuis:', finalUrl);
                      return null;
                  }

                  const embedUrl = `https://maps.google.com/maps?q=${encodeURIComponent(query)}&output=embed&z=15`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed iframe-google-maps"
                      src="${embedUrl}"
                      frameborder="0"
                      allowfullscreen
                      loading="lazy"
                      title="Carte Google Maps intégrée">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[GoogleMaps] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'AppleMusic',
          selector: 'a[href*="music.apple.com/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (!/\/(album|playlist|station|artist)\//.test(url.pathname)) {
                      return null;
                  }

                  const embedUrl = url.href.replace(
                      'music.apple.com',
                      'embed.music.apple.com'
                  );

                  const isSong = url.searchParams.has('i');
                  const height = isSong ? '175' : '450';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      class="iframe-embed"
                      src="${embedUrl}"
                      style="height: ${height}px;"
                      frameborder="0"
                      allowfullscreen
                      sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation"
                      allow="autoplay *; encrypted-media *; fullscreen *"
                      loading="lazy"
                      title="Lecteur Apple Music intégré">
                  </iframe>`;

                  return container;

              } catch (e) {
                  console.error('[AppleMusic] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'DeviantArt',
          selector: 'a[href*="deviantart.com/"][href*="/art/"]',
          async createEmbedElement(link) {
              try {
                  let normalizedUrl = link.href;
                  const originalUrl = new URL(link.href);
                  if (originalUrl.hostname !== 'www.deviantart.com') {
                      try {
                          normalizedUrl = await new Promise((resolve, reject) => {
                              GM.xmlHttpRequest({
                                  method: 'HEAD',
                                  url: link.href,
                                  onload: response => {
                                      resolve(response.finalUrl || link.href);
                                  },
                                  onerror: error => reject(error),
                                  ontimeout: error => reject(error)
                              });
                          });
                      } catch (e) {
                          console.warn('[DeviantArt] La résolution du lien a échoué. On continue avec l\'URL originale.', e);
                          normalizedUrl = link.href;
                      }
                  }

                  const apiUrl = `https://backend.deviantart.com/oembed?url=${encodeURIComponent(normalizedUrl)}`;
                  const data = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: apiUrl,
                          onload: (response) => {
                              if (response.status >= 200 && response.status < 300) {
                                  resolve(JSON.parse(response.responseText));
                              } else {
                                  reject(new Error(`L'API DeviantArt a retourné le statut ${response.status}`));
                              }
                          },
                          onerror: (error) => reject(error),
                          ontimeout: (error) => reject(new Error('La requête vers l\'API DeviantArt a expiré.'))
                      });
                  });

                  const imageUrl = data.url || data.thumbnail_url;

                  if (imageUrl) {
                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="${data.title || 'Art depuis DeviantArt'}" loading="lazy">`;
                      return container;
                  } else {
                      console.warn('[DeviantArt] Aucune URL d\'image trouvée dans la réponse de l\'API pour :', normalizedUrl);
                      return null;
                  }

              } catch (error) {
                  console.error('[DeviantArt] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
      },
      {
            name: 'Pinterest',
            selector: 'a[href*="pinterest."][href*="/pin/"]',
            scriptPromise: null,
            loadScript() {
                if (!this.scriptPromise) {
                    this.scriptPromise = new Promise((resolve, reject) => {
                        if (window.PinUtils) return resolve();
                        const script = document.createElement('script');
                        script.async = true;
                        script.defer = true;
                        script.src = 'https://assets.pinterest.com/js/pinit.js';
                        script.onload = resolve;
                        script.onerror = () => reject(new Error('Failed to load Pinterest script'));
                        document.head.appendChild(script);
                    });
                }
                return this.scriptPromise;
            },
            createEmbedElement(link) {
                const match = link.href.match(/\/pin\/(\d+)\/?/);
                if (!match || !match[1]) {
                    console.warn('[Pinterest] Impossible d\'extraire l\'ID du Pin depuis :', link.href);
                    return null;
                }
                const pinId = match[1];
                const canonicalUrl = `https://www.pinterest.com/pin/${pinId}/`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';

                const pinEmbed = document.createElement('a');
                pinEmbed.href = canonicalUrl;
                pinEmbed.setAttribute('data-pin-do', 'embedPin');
                pinEmbed.setAttribute('data-pin-width', 'large');
                pinEmbed.setAttribute('data-pin-terse', 'true');

                container.appendChild(pinEmbed);
                return container;
            },
            postProcess() {
                this.loadScript().then(() => {
                    if (window.PinUtils && typeof window.PinUtils.build === 'function') {
                        window.PinUtils.build();
                    }
                }).catch(error => {
                    console.error('[Pinterest] Erreur lors du chargement ou de l\'exécution du script :', error);
                });
            }
      },

      {
          name: 'ImageShack',
          selector: 'a[href*="imageshack.com/i/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  const pathParts = url.pathname.split('/').filter(p => p);

                  if (pathParts.length !== 2 || pathParts[0] !== 'i' || !pathParts[1]) {
                      return null;
                  }
                  const imageId = pathParts[1];

                  if (imageId.length < 3) {
                      throw new Error("ID d'image ImageShack invalide.");
                  }

                  const base36Part = imageId.substring(0, 2);
                  let filePart = imageId.substring(2);

                  //  Convertir la première partie de base 36 en base 10
                  const serverFolder = parseInt(base36Part, 36);

                  if (isNaN(serverFolder)) {
                      throw new Error(`Échec de la conversion de '${base36Part}' depuis la base 36.`);
                  }

                  if (/[a-zA-Z]$/.test(filePart)) {
                       filePart = filePart.slice(0, -1);
                  }

                  // Le format est : https://imagizer.imageshack.com/v2/{transfo}/{dossier}/{fichier}.jpg
                  const transformationParam = 'xq70';
                  const imageUrl = `https://imagizer.imageshack.com/v2/${transformationParam}/${serverFolder}/${filePart}.jpg`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis ImageShack" loading="lazy">`;
                  return container;

              } catch (error) {
                  console.error(`[ImageShack] Erreur lors de la transformation de l'URL ${link.href}:`, error.message);
                  return null;
              }
          }
      },
        {
              name: 'Gofundme',
              selector: 'a[href*="gofundme.com/f/"]',
              createEmbedElement(link) {
                  try {
                      const url = new URL(link.href);
                      if (!url.pathname.startsWith('/f/')) {
                          return null;
                      }
                      const cleanUrlPath = url.pathname;
                      const embedUrl = `https://www.gofundme.com${cleanUrlPath}/widget/large`;

                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<iframe
                          src="${embedUrl}"
                          class="iframe-embed"
                          style="height: 620px; border-radius: 8px;"
                          title="Campagne Gofundme intégrée"
                          frameborder="0"
                          scrolling="no">
                      </iframe>`;

                      return container;

                  } catch (e) {
                      console.error('[Gofundme] Erreur lors de la création de l\'embed :', e);
                      return null;
                  }
            }
      },
      {
            name: 'Coub',
            selector: 'a[href*="coub.com/view/"]',
            createEmbedElement(link) {
                try {
                    const match = link.href.match(/view\/([a-zA-Z0-9]+)/);
                    if (!match || !match[1]) return null;

                    const videoId = match[1];
                    const iframeUrl = `https://coub.com/embed/${videoId}?muted=true&autostart=true&originalSize=false&startWithHD=true`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeUrl}"
                        class="iframe-embed iframe-youtube"
                        title="Coub Video"
                        allow="autoplay"
                        frameborder="0"
                        width="550"
                        height="310">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Coub] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
      },
      {
          name: 'Gyazo',
          selector: 'a[href^="https://gyazo.com/"]',
          async createEmbedElement(link) {
              if (link.hostname === 'i.gyazo.com' && /\.(jpe?g|png|gif|webp)$/i.test(link.pathname)) {
                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<img src="${link.href}" class="image-embed" alt="Image depuis Gyazo" loading="lazy">`;
                  return container;
              }

              if (link.hostname === 'gyazo.com' && link.pathname.length > 1) {
                  try {
                      const data = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'GET',
                              url: `https://api.gyazo.com/api/oembed?url=${encodeURIComponent(link.href)}`,
                              onload: (response) => {
                                  if (response.status === 200) resolve(JSON.parse(response.responseText));
                                  else reject(new Error(`API Gyazo a retourné le statut ${response.status}`));
                              },
                              onerror: (err) => reject(err)
                          });
                      });

                      if (data && data.url) {
                          const container = document.createElement('div');
                          container.className = 'bloc-embed';
                          container.innerHTML = `<img src="${data.url}" class="image-embed" alt="Image depuis Gyazo" loading="lazy">`;
                          return container;
                      }
                  } catch (error) {
                      console.error('[Gyazo] Erreur lors de la récupération de l\'embed :', error);
                      return null;
                  }
              }
              return null;
          }
      },
      {
          name: 'Codepen',
          selector: 'a[href*="codepen.io/"]',
          createEmbedElement(link) {
              if (!link.pathname.includes('/pen/')) return null;

              try {
                  const url = new URL(link.href);
                  url.pathname = url.pathname.replace('/pen/', '/embed/');
                  url.searchParams.set('default-tab', 'result');
                  url.searchParams.set('theme-id', 'dark');

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${url.toString()}"
                      class="iframe-embed"
                      style="height: 450px; border: 1px solid #444;"
                      title="Codepen Embed"
                      scrolling="no"
                      frameborder="0"
                      loading="lazy"
                      allowtransparency="true"
                      allowfullscreen="true">
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Codepen] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
            name: 'Pastebin',
            selector: 'a[href*="pastebin.com/"]',
            createEmbedElement(link) {
                try {
                    const url = new URL(link.href);
                    const pathParts = url.pathname.split('/').filter(p => p);
                    if (pathParts.length !== 1 || !/^[a-zA-Z0-9]{8}$/.test(pathParts[0])) {
                        return null;
                    }

                    const pasteId = pathParts[0];
                    const iframeUrl = `https://pastebin.com/embed_iframe/${pasteId}`;

                    const container = document.createElement('div');
                    container.className = 'bloc-embed';
                    container.innerHTML = `<iframe
                        src="${iframeUrl}"
                        class="iframe-embed"
                        style="height: 400px;"
                        title="Pastebin Embed"
                        sandbox="allow-scripts allow-same-origin"
                        frameborder="0">
                    </iframe>`;
                    return container;

                } catch (e) {
                    console.error('[Pastebin] Erreur lors de la création de l\'embed :', e);
                    return null;
                }
            }
      },
      {
          name: 'Tenor',
          selector: 'a[href*="tenor.com/"][href*="/view/"]',
          createEmbedElement(link) {
              try {
                  const gifId = link.pathname.split('-').pop();
                  if (!gifId || !/^\d+$/.test(gifId)) {
                      console.warn('[Tenor] ID du GIF non trouvé ou invalide pour le lien :', link.href);
                      return null;
                  }

                  const iframeUrl = `https://tenor.com/embed/${gifId}`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed iframe-youtube"
                      title="Tenor GIF"
                      frameborder="0"
                      allowfullscreen
                      style="width: 100%; min-height: 200px;">
                  </iframe>`;

                  GM.xmlHttpRequest({
                      method: 'GET',
                      url: `https://tenor.com/oembed?url=${encodeURIComponent(link.href)}`,
                      onload: (response) => {
                          if (response.status === 200) {
                              try {
                                  const data = JSON.parse(response.responseText);
                                  const iframe = container.querySelector('iframe');

                                  if (iframe && data && data.width && data.height) {
                                      const aspectRatio = data.width / data.height;
                                      iframe.style.aspectRatio = `${aspectRatio}`;
                                      iframe.style.minHeight = 'auto';
                                  }
                              } catch (e) {
                                  console.error('[Tenor] Erreur de parsing JSON depuis l\'API oEmbed.', e);
                              }
                          }
                      },
                      onerror: (error) => {
                          console.error('[Tenor] Erreur réseau lors de l\'appel à l\'API oEmbed.', error);
                      }
                  });

                  return container;

              } catch (e) {
                  console.error('[Tenor] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Postimages',
          selector: 'a[href*="postimg.cc/"]',
          async createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  if (url.hostname === 'postimg.cc' && url.pathname.startsWith('/image/')) {
                      const pageHtml = await new Promise((resolve, reject) => {
                          GM.xmlHttpRequest({
                              method: 'GET',
                              url: link.href,
                              onload: response => resolve(response.responseText),
                              onerror: error => reject(error)
                          });
                      });

                      const doc = new DOMParser().parseFromString(pageHtml, 'text/html');
                      const imageUrl = doc.querySelector('meta[property="og:image"]')?.getAttribute('content');

                      if (imageUrl) {
                          const container = document.createElement('div');
                          container.className = 'bloc-embed';
                          container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis Postimages" loading="lazy">`;
                          return container;
                      }
                  }

                  return null;

              } catch (e) {
                  console.error('[Postimages] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'ImgBB',
          selector: 'a[href*="ibb.co/"]',
          async createEmbedElement(link) {
              if (link.pathname.split('/').filter(p => p).length !== 1) {
                  return null;
              }

              try {
                  const pageHtml = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: link.href,
                          onload: response => resolve(response.responseText),
                          onerror: error => reject(error),
                          ontimeout: () => reject(new Error('Timeout'))
                      });
                  });

                  const match = pageHtml.match(/<meta\s+property="og:image"\s+content="([^"]+)"/);
                  const imageUrl = match ? match[1] : null;

                  if (imageUrl) {
                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<img src="${imageUrl}" class="image-embed" alt="Image depuis ImgBB" loading="lazy">`;
                      return container;
                  } else {
                      console.warn('[ImgBB] Impossible de trouver l\'URL de l\'image pour :', link.href);
                      return null;
                  }

              } catch (e) {
                  console.error('[ImgBB] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Sketchfab',
          selector: 'a[href*="sketchfab.com/3d-models/"], a[href*="sketchfab.com/models/"]',
          createEmbedElement(link) {
              try {
                  const url = new URL(link.href);
                  const modelMatch = url.href.match(/([a-f0-9]{32})/i);

                  if (modelMatch && modelMatch[1]) {
                      const modelId = modelMatch[1];
                      const iframeUrl = `https://sketchfab.com/models/${modelId}/embed`;

                      const container = document.createElement('div');
                      container.className = 'bloc-embed';
                      container.innerHTML = `<iframe
                          title="Modèle 3D Sketchfab"
                          class="iframe-embed iframe-youtube"
                          src="${iframeUrl}"
                          frameborder="0"
                          allow="autoplay; fullscreen; xr-spatial-tracking"
                          xr-spatial-tracking
                          execution-while-out-of-viewport
                          execution-while-not-rendered
                          web-share
                          allowfullscreen>
                      </iframe>`;
                      return container;
                  }

                  return null;

              } catch (e) {
                  console.error('[Sketchfab] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Steam',
          selector: 'a[href*="store.steampowered.com/app/"]',
          createEmbedElement(link) {
              try {
                  const match = link.href.match(/\/app\/(\d+)/);
                  if (!match || !match[1]) return null;

                  const appId = match[1];
                  const iframeUrl = `https://store.steampowered.com/widget/${appId}/`;

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      src="${iframeUrl}"
                      class="iframe-embed"
                      style="height: 190px; border-radius: 8px;"
                      title="Widget Steam"
                      frameborder="0">
                  </iframe>`;
                  return container;

              } catch (e) {
                  console.error('[Steam] Erreur lors de la création de l\'embed :', e);
                  return null;
              }
          }
      },
      {
          name: 'Bandcamp',
          selector: 'a[href*=".bandcamp.com/"]',
          async createEmbedElement(link) {
              if (!link.pathname.includes('/track/') && !link.pathname.includes('/album/')) {
                  return null;
              }

              try {
                  const pageHtml = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: link.href,
                          onload: response => resolve(response.responseText),
                          onerror: error => reject(error),
                          ontimeout: () => reject(new Error('Timeout'))
                      });
                  });

                  const doc = new DOMParser().parseFromString(pageHtml, "text/html");
                  const embedUrlMeta = doc.querySelector('meta[property="og:video"]');

                  if (!embedUrlMeta) {
                      console.warn('[Bandcamp] Meta tag "og:video" introuvable pour :', link.href);
                      return null;
                  }

                  let iframeUrl = embedUrlMeta.getAttribute('content');
                  if (!iframeUrl) return null;
                  if (!iframeUrl.endsWith('/')) iframeUrl += '/';
                  iframeUrl += 'transparent=true/';

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `<iframe
                      style="border: 0; width: 100%; max-width: 550px; height: 120px;"
                      src="${iframeUrl}"
                      title="Lecteur Bandcamp"
                      seamless>
                  </iframe>`;

                  return container;

              } catch (error) {
                  console.error('[Bandcamp] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
      },
      {
            name: 'Flourish',
            selector: 'a[href*="public.flourish.studio/visualisation/"], a[href*="public.flourish.studio/story/"]',
            async createEmbedElement(link) {
                const match = link.href.match(/(visualisation|story)\/\d+/);

                if (!match || !match[0]) {
                    console.warn('[Flourish] Impossible d\'extraire l\'ID de la visualisation:', link.href);
                    return null;
                }

                const embedPath = match[0];
                const iframeUrl = `https://flo.uri.sh/${embedPath}/embed`;

                const container = document.createElement('div');
                container.className = 'bloc-embed';
                container.innerHTML = `<iframe
                    src="${iframeUrl}"
                    class="iframe-embed iframe-flourish"
                    title="Flourish Visualisation"
                    sandbox="allow-scripts allow-same-origin"
                    scrolling="no"
                ></iframe>`;

                return container;
            }
      },
      {
          name: 'DistroKid',
          selector: 'a[href*="distrokid.com/hyperfollow/"]',
          async createEmbedElement(link) {
              const defaultAlbumArt = 'https://risibank.fr/cache/medias/0/5/532/53280/full.png';
              const hyperfollowUrl = link.href.split('?')[0];

              try {
                  const responseText = await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'GET',
                          url: hyperfollowUrl,
                          headers: {
                              'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
                          },
                          onload: (response) => resolve(response.responseText),
                          onerror: (error) => reject(error),
                          ontimeout: () => reject(new Error('Timeout'))
                      });
                  });

                  let audioUrl = null;
                  const audioTagMatch = responseText.match(/<audio[^>]+src="([^"]+)"/);
                  if (audioTagMatch && audioTagMatch[1]) {
                      audioUrl = audioTagMatch[1];
                  } else {
                      const jsonDataMatch = responseText.match(/previewData\.tracks\s*=\s*JSON\.parse\(\s*"(.+?)"\s*\);/);
                      if (jsonDataMatch && jsonDataMatch[1]) {
                          const jsonString = new Function(`return "${jsonDataMatch[1]}"`)();
                          const tracks = JSON.parse(jsonString);
                          if (tracks && tracks.length > 0) audioUrl = tracks[0].preview;
                      }
                  }

                  if (!audioUrl) {
                      console.warn('[DistroKid] Impossible de trouver un lien audio pour :', hyperfollowUrl);
                      return null;
                  }

                  const titleMatch = responseText.match(/<title[^>]*>([^<]+) by ([^<]+) - DistroKid<\/title>/);
                  const trackTitle = titleMatch ? titleMatch[1] : 'Titre inconnu';
                  const artistName = titleMatch ? titleMatch[2] : 'Artiste inconnu';

                  let albumArtUrl = defaultAlbumArt;
                  const bodyArtMatch = responseText.match(/<img[^>]+class="artCover[^"]+"[^>]+src="([^"]+)"/);
                  if (bodyArtMatch && bodyArtMatch[1]) {
                      albumArtUrl = bodyArtMatch[1];
                  }

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  container.innerHTML = `
                    <a href="${hyperfollowUrl}" target="_blank" rel="noopener noreferrer" class="distrokid-embed-card">
                        <div class="distrokid-album-art" style="background-image: url('${albumArtUrl}');"></div>
                        <div class="distrokid-content">
                            <div>
                                <div class="distrokid-title">${trackTitle}</div>
                                <div class="distrokid-artist">${artistName}</div>
                            </div>
                            <audio src="${audioUrl}" controls preload="metadata"></audio>
                        </div>
                    </a>
                  `;

                  return container;

              } catch (error) {
                  console.error('[DistroKid] Erreur lors de la création de l\'embed :', error);
                  return null;
              }
          }
      },
      {
          name: 'GenericMedia',
          selector: `
              a[href*=".jpg" i]:not([href*="noelshack.com"]), a[href*=".jpeg" i]:not([href*="noelshack.com"]),
              a[href*=".png" i]:not([href*="noelshack.com"]), a[href*=".gif" i]:not([href*="noelshack.com"]),
              a[href*=".webp" i]:not([href*="noelshack.com"]), a[href*=".bmp" i]:not([href*="noelshack.com"]),
              a[href*=".mp4" i]:not([href*="noelshack.com"]), a[href*=".webm" i]:not([href*="noelshack.com"]),
              a[href*=".mov" i]:not([href*="noelshack.com"]), a[href*=".ogg" i]:not([href*="noelshack.com"])
          `,
          async createEmbedElement(link) {
              const stickerUrl = 'https://risibank.fr/cache/medias/0/5/512/51206/thumb.png';

              const createDeadLinkSticker = () => {
                  const container = document.createElement('div');
                  container.className = 'bloc-embed';
                  container.innerHTML = `
                      <div class="dead-link-sticker">
                          <img src="${stickerUrl}" alt="[Média supprimé]">
                          <span>[Média supprimé]</span>
                      </div>
                  `;
                  return container;
              };

              try {
                  await new Promise((resolve, reject) => {
                      GM.xmlHttpRequest({
                          method: 'HEAD',
                          url: link.href,
                          timeout: 4000,
                          onload: (response) => {
                              if (response.status >= 200 && response.status < 300) {
                                  resolve();
                              } else {
                                  reject(new Error(`Statut HTTP: ${response.status}`));
                              }
                          },
                          onerror: (error) => reject(new Error('Erreur réseau')),
                          ontimeout: () => reject(new Error('Timeout dépassé'))
                      });
                  });

                  const container = document.createElement('div');
                  container.className = 'bloc-embed';

                  const handleError = (element) => {
                      const parentBloc = element.closest('.bloc-embed');
                      if (parentBloc) {
                          parentBloc.replaceWith(createDeadLinkSticker());
                      }
                  };

                  const pathname = new URL(link.href).pathname;

                  if (/\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(pathname)) {
                      const img = document.createElement('img');
                      img.src = link.href;
                      img.className = 'image-embed';
                      img.alt = 'Image intégrée';
                      img.loading = 'lazy';
                      img.onerror = () => handleError(img);
                      container.appendChild(img);
                  } else if (/\.(mp4|webm|mov|ogg)$/i.test(pathname)) {
                      const video = document.createElement('video');
                      video.src = link.href;
                      video.className = 'video-embed';
                      video.controls = true;
                      video.autoplay = true;
                      video.muted = true;
                      video.loop = true;
                      video.playsinline = true;
                      video.onerror = () => handleError(video);
                      container.appendChild(video);
                  }

                  if (container.hasChildNodes()) {
                      return container;
                  }
                  return null;

              } catch (error) {
                  console.warn(`[GenericMedia] Lien mort détecté pour ${link.href}. Raison: ${error.message}`);
                  return createDeadLinkSticker();
              }
          }
      },
      {
            name: 'ArticlePreview',
            selector: 'a[href^="http"]:not([data-miniatweet-processed])',
            async createEmbedElement(link) {
                const href = link.href;
                const excludedDomains = [
                    'youtube.com', 'youtu.be', 'twitter.com', 'x.com', 'instagram.com',
                    'tiktok.com', 'vm.tiktok.com', 'streamable.com', 'webmshare.com',
                    'facebook.com', 'twitch.tv', 'vocaroo.com', 'voca.ro', 'reddit.com',
                    'flourish.studio', 'jeuxvideo.com', 'jvarchive.com', 'jvarchive.st',
                    'noelshack.com', 'spotify.com'
                ];
                if (excludedDomains.some(domain => new URL(href).hostname.includes(domain))) {
                    return null;
                }
                if (/\.(jpg|jpeg|png|gif|webp|bmp|mp4|webm|mov|ogg)$/i.test(href)) {
                    return null;
                }

                let htmlContent;
                try {
                    htmlContent = await new Promise((resolve, reject) => {
                        GM.xmlHttpRequest({
                            method: 'GET',
                            url: href,
                            headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' },
                            onload: response => {
                                if (response.status >= 200 && response.status < 300) {
                                    resolve(response.responseText);
                                } else {
                                    reject(new Error(`Statut HTTP: ${response.status}`));
                                }
                            },
                            onerror: err => reject(err),
                            ontimeout: () => reject(new Error('Timeout'))
                        });
                    });
                } catch (error) {
                    console.warn(`[ArticlePreview] Impossible de récupérer ${href}: ${error.message}`);
                    return null;
                }

                const parser = new DOMParser();
                const doc = parser.parseFromString(htmlContent, 'text/html');

                const getMeta = (prop) => doc.querySelector(`meta[property="${prop}"], meta[name="${prop}"]`)?.getAttribute('content')?.trim();

                const title = getMeta('og:title') || doc.querySelector('title')?.textContent.trim();
                const description = getMeta('og:description');
                const imageUrl = getMeta('og:image');
                const siteName = getMeta('og:site_name');

                if (!title || !imageUrl) {
                    return null;
                }

                const container = document.createElement('div');
                container.className = 'bloc-embed';

                container.innerHTML = `
                    <a href="${href}" class="article-preview-card" target="_blank" rel="noopener noreferrer">
                        <div class="article-preview-image" style="background-image: url('${imageUrl}');"></div>
                        <div class="article-preview-content">
                            ${siteName ? `<div class="article-preview-sitename">${siteName}</div>` : ''}
                            <div class="article-preview-title">${title}</div>
                            ${description ? `<div class="article-preview-description">${description}</div>` : ''}
                        </div>
                    </a>
                `;

                return container;
            }
        },
    ];

    const embedManager = new EmbedManager(providers);
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => embedManager.init());
    } else {
        embedManager.init();
    }

})();