MarkdownImageRenderer for Gemini

通称「MIR」
目的:Webから、GeminiでマークダウンURLの画像を閲覧する

使い方
1.類似のスクリプトや拡張機能がインストールされていないことを確認してください(Chrome拡張機能など)
2.ブラウザの開発者モードが有効になっていることを確認してください(※)
3.ViolentMonkeyブラウザ拡張機能をインストールしてください。モバイルユーザーはFirefox、Edge、または拡張機能をサポートするその他のブラウザを使用できます。ChromeユーザーはTamperMonkeyを使用する必要がある場合がありますが、これは独自のものなので理想的ではありません。iOSユーザーはApp StoreからUserscriptsをダウンロードし、
4.以下のようにホワイトリストに画像があるサイトを入れる「netlify.app」 ※これはいれなくても許可する?みたいに表示されるのでそこで許可するでもよい
c

※開発者モード 有効にする方法
https://laboradian.com/tampermonkey-on-dev-mode-chrome/
エディター右にある、設定にある有効がチェックなければチェックしてね(デフォルトONだったような)

 
// ==UserScript==
// @name         Markdown Image Renderer for Gemini (Reverse fix)
// @namespace    MIT Licence
// @version      1.4
// @description  GeminiのチャットでMarkdown画像を表示します。「a link」化やGoogle検索リンク化を逆変換して対応
// @match        https://gemini.google.com/app/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    console.log("🚀 Gemini Markdown Image Renderer v1.4 (Reverse fix): Script loaded.");

    function fetchImageAsDataURL(url, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            responseType: 'blob',
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    callback(response.response); // blobを直接返す
                } else {
                    console.error(`❌ [Fetch] HTTP Error ${response.status} for: ${url}`);
                    callback(null);
                }
            },
            onerror: (error) => {
                console.error(`❌ [Fetch] Network Error for: ${url}`, error);
                callback(null);
            }
        });
    }

    /**
     * 指定された要素を、指定されたURLの画像に置き換えます
     * @param {HTMLElement} targetElement - 置き換え対象のDOM要素 (<a>タグなど)
     * @param {string} imageUrl - 表示する画像のURL
     */
    function replaceElementWithImage(targetElement, imageUrl) {
        if (!targetElement.parentNode || targetElement.dataset.imageProcessed) return;
        targetElement.dataset.imageProcessed = 'true';

        const altText = imageUrl.split('/').pop().split('.')[0] || 'image'; // URLからファイル名をaltに
        const uniqueId = 'img-placeholder-' + Date.now() + Math.random().toString(36).substring(2);

        const placeholder = document.createElement('img');
        placeholder.id = uniqueId;
        placeholder.alt = altText + ' (loading...)';
        placeholder.style.cssText = "max-width: 100%; height: auto; border-radius: 8px; display: block; background-color:#f0f0f0; min-height: 50px;";

        const preWrapper = document.createElement('pre');
        preWrapper.setAttribute('contenteditable', 'false');
        preWrapper.style.cssText = "margin: 0; padding: 0; background: transparent; border: none; font-family: inherit; white-space: pre-wrap; display: block;";
        preWrapper.appendChild(placeholder);

        // まずプレースホルダーに置き換える
        targetElement.parentNode.replaceChild(preWrapper, targetElement);

        // 非同期で画像を取得して表示
        fetchImageAsDataURL(imageUrl, (blob) => {
            const imgElement = document.getElementById(uniqueId);
            if (imgElement) {
                if (blob) {
                    imgElement.src = URL.createObjectURL(blob);
                    imgElement.alt = altText;
                } else {
                    imgElement.alt = `[画像読み込み失敗] ${altText}`;
                    imgElement.style.border = "1px dashed #ccc";
                    imgElement.style.padding = "10px";
                }
            }
        });
    }

    let debounceTimer;

    const debouncedProcessor = () => {
        const targetSelector = '.response-container-content .markdown, .model-response-text';

        document.querySelectorAll(targetSelector).forEach(container => {
            // --- ステージ1: Google検索リンクを逆変換 ---
            container.querySelectorAll('a[href*="google.com/search?q="]').forEach(link => {
                try {
                    const searchUrl = new URL(link.href);
                    const originalUrl = searchUrl.searchParams.get('q');
                    // URLが画像ファイルっぽいか簡易チェック
                    if (originalUrl && /\.(avif|webp|png|jpg|jpeg|gif)$/i.test(originalUrl)) {
                        console.log(`✅ [Reverse] Found Google search link. Reversing to image: ${originalUrl}`);
                        replaceElementWithImage(link, originalUrl);
                    }
                } catch (e) {
                    // URL解析エラーは無視
                }
            });

            // --- ステージ2: 残っているMarkdownテキストを処理 (フォールバック) ---
            const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
            while (walker.nextNode()) {
                const node = walker.currentNode;
                if (!node.parentElement || node.parentElement.closest('[data-image-processed="true"]')) continue;

                const markdownImageRegex = /!\[[^\]]*\]\(([^)]+)\)/;
                const match = node.textContent.match(markdownImageRegex);

                if (match) {
                    const imageUrl = match[1];
                     // テキストノード全体を画像に置き換える(単純化のため)
                    console.log(`✅ [Markdown] Found raw markdown text. Converting to image: ${imageUrl}`);
                    replaceElementWithImage(node.parentElement, imageUrl);
                }
            }
        });
    };

    const observer = new MutationObserver(() => {
        clearTimeout(debounceTimer);
        // Geminiの処理が終わった頃合いを狙う
        debounceTimer = setTimeout(debouncedProcessor, 250);
    });

    console.log("👀 DOM Observer started.");
    observer.observe(document.body, { childList: true, subtree: true });

})();

Strudi AIはもっとエグそうでした(無理そうでした)できたら教えて

次回作2つ絶賛作成中
即ちゃ
https://rentry.co/c9eqnswq

即プロ(即プロンプトコピー)※ただし全てのコードブロックが繋がっちゃっているものとする
comming soon

Edit

Pub: 13 Oct 2025 16:47 UTC

Edit: 14 Oct 2025 01:09 UTC

Views: 36