您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.1 // @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: 500, 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: 8px; 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-youtube-short { aspect-ratio: 9 / 16; max-width: 340px; height: auto; max-height: 75vh; } .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; -webkit-line-clamp: 2; -webkit-box-orient: vertical; 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; } `); 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'); if (statusIndex === -1 || !parts[statusIndex + 1]) return null; const tweetId = parts[statusIndex + 1].split('?')[0]; const placeholderId = `tweet-placeholder-${tweetId}-${Math.random().toString(36).substring(2, 9)}`; const container = document.createElement('div'); container.className = 'bloc-embed'; container.innerHTML = `<div id="${placeholderId}" class="iframe-embed twitter-loading-placeholder">Chargement du Tweet...</div>`; let isHandled = false; // Drapeau pour s'assurer qu'on ne traite la réponse qu'une fois const handleResponse = (status, finalUrl) => { if (isHandled) return; isHandled = true; const placeholderElement = document.getElementById(placeholderId); if (!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&frame=false`; const iframeId = `twitter-iframe-${tweetId}-${Math.random().toString(36).substring(2, 9)}`; const iframeHTML = `<iframe id="${iframeId}" src="${iframeUrl}" class="iframe-embed iframe-twitter" title="Twitter Tweet" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" allowtransparency="true"></iframe>`; placeholderElement.outerHTML = iframeHTML; } else { placeholderElement.className = 'placeholder-embed'; placeholderElement.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)}`, onreadystatechange: function(response) { if (response.readyState === XMLHttpRequest.HEADERS_RECEIVED) { handleResponse(response.status, response.finalUrl); response.abort(); } }, onload: function(response) { handleResponse(response.status, response.finalUrl); }, onerror: function(response) { handleResponse(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) { 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) { let correctedHref = link.href; if (correctedHref.includes('&') && !correctedHref.includes('?')) { correctedHref = correctedHref.replace('&', '?'); } const timeToSeconds = (timeString) => { if (!timeString) return null; let totalSeconds = 0; const hoursMatch = timeString.match(/(\d+)h/); const minutesMatch = timeString.match(/(\d+)m/); const secondsMatch = timeString.match(/(\d+)s/); const plainSecondsMatch = timeString.match(/^\d+$/); 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 && plainSecondsMatch) { totalSeconds = parseInt(timeString, 10); } return totalSeconds > 0 ? totalSeconds : null; }; let videoId = null; let startTime = null; let isShort = false; try { const url = new URL(correctedHref); startTime = timeToSeconds(url.searchParams.get('t')); if (url.hostname === 'youtu.be') { videoId = url.pathname.substring(1).split('?')[0]; } else if (url.pathname.startsWith('/shorts/')) { videoId = url.pathname.split('/')[2]; isShort = true; } else if (url.pathname === '/watch') { videoId = url.searchParams.get('v'); } } catch (e) { console.error('[YouTube] Erreur de parsing URL :', e); return null; } if (!videoId) return null; const params = new URLSearchParams(); if (startTime) { params.append('start', startTime); } const iframeUrl = `https://www.youtube.com/embed/${videoId}?${params.toString()}`; const iframeClasses = isShort ? 'iframe-embed iframe-youtube iframe-youtube-short' : 'iframe-embed iframe-youtube'; 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; } }, { name: 'Xcancel', selector: 'a[href*="xcancel.com/"]', createEmbedElement(link, isDarkTheme) { IframeResizeManager.init(); const parts = link.href.split('/'); const statusIndex = parts.findIndex(p => p === 'status'); if (statusIndex === -1 || !parts[statusIndex + 1]) return null; const tweetId = parts[statusIndex + 1].split('?')[0].split('#')[0]; const placeholderId = `tweet-placeholder-${tweetId}-${Math.random().toString(36).substring(2, 9)}`; const container = document.createElement('div'); container.className = 'bloc-embed'; container.innerHTML = `<div id="${placeholderId}" class="iframe-embed iframe-twitter twitter-loading-placeholder">Chargement du Tweet...</div>`; const twitterApiUrl = link.href.replace('xcancel.com', 'twitter.com'); let isHandled = false; const handleResponse = (status, finalUrl) => { if (isHandled) return; isHandled = true; const placeholderElement = document.getElementById(placeholderId); if (!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&frame=false`; const iframeId = `twitter-iframe-${tweetId}-${Math.random().toString(36).substring(2, 9)}`; const iframeHTML = `<iframe id="${iframeId}" src="${iframeUrl}" class="iframe-embed iframe-twitter" title="Twitter Tweet" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" allowtransparency="true"></iframe>`; placeholderElement.outerHTML = iframeHTML; } else { placeholderElement.className = 'placeholder-embed'; placeholderElement.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(twitterApiUrl)}`, onreadystatechange: function(response) { if (response.readyState === XMLHttpRequest.HEADERS_RECEIVED) { handleResponse(response.status, response.finalUrl); response.abort(); } }, onload: function(response) { handleResponse(response.status, response.finalUrl); }, onerror: function(response) { handleResponse(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`; // Étape 5: Créer l'élément 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(); } })();