MarkdownImageRenderer for Gemini,ChatGPT,Grok

通称:画G、英名「MIRG」
目的:Webから、Gemini,ChatGPT,GrokでマークダウンURLの画像を閲覧する

使用手順:①Tampermonkeyのプラグインを インストール
②「MIRGインストール」を押して、スクリプトインストールを行う(またはTampermonkeyで、以下のコードを保存)
③各サイトで画像が表示できるか確認してください!※Gemini,ChatGPTは癖があるのででにくいです。Studio AI用はコチラ

Tampermonkeyが良く分からない人コチラの下部の解説を参考に

詳しくは下部へ

 
// ==UserScript==
// @name         Markdown Image Renderer for Gemini, ChatGPT, Grok
// @namespace    https://greasyfork.org/ja/scripts/556280
// @license      MIT 
// @version      2.0
// @description  Gemini,ChatGPT,GrokのチャットでMarkdown画像を表示します。画像クリックで単一のサブウィンドウに表示(上書き)します。
// @author       FoeverPWA
// @match        https://gemini.google.com/app/*
// @match        https://chatgpt.com/*
// @match        https://grok.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    console.log("🚀 Markdown Image Renderer v1.8 (Grok対応): 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);
                } 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';
        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; cursor: pointer;";

        placeholder.addEventListener('click', () => {
            window.open(imageUrl, 'imagePreviewWindow');
        });

        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);

        // ReactなどのフレームワークがDOMを管理している場合、要素を完全に削除(replaceChild)すると
        // "The node to be removed is not a child of this node" エラーが発生することがあります。
        // そのため、元の要素は削除せずに非表示にし、その直後に画像プレビューを挿入します。
        targetElement.style.display = 'none';
        targetElement.parentNode.insertBefore(preWrapper, targetElement.nextSibling);

        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";
                    imgElement.style.cursor = 'default';
                }
            }
        });
    }

    let debounceTimer;

    const debouncedProcessor = () => {
        // --- ステージ1: Gemini専用 - Google検索リンクを逆変換 ---
        // Geminiは特殊なので全体から検索
        document.querySelectorAll('a[href*="google.com/search?q="]').forEach(link => {
            if (link.dataset.imageProcessed) return;

            try {
                const searchUrl = new URL(link.href);
                const originalUrl = searchUrl.searchParams.get('q');
                if (originalUrl && /\.(avif|webp|png|jpg|jpeg|gif|svg)$/i.test(originalUrl)) {
                    console.log(`✅ [Gemini] Found Google search link. Reversing to image: ${originalUrl}`);
                    replaceElementWithImage(link, originalUrl);
                }
            } catch (e) {
                // URL解析エラーは無視
            }
        });

        // ChatGPT, Grok対応のセレクター
        const targetSelector = '.model-response-text, p.break-words';

        document.querySelectorAll(targetSelector).forEach(container => {

            // --- ステージ2: Grok形式の画像リンクを変換 ---
            container.querySelectorAll('a[href][target="_blank"]').forEach(link => {
                if (link.dataset.imageProcessed) return;

                const href = link.href;
                // 画像ファイル拡張子で判定
                if (/\.(avif|webp|png|jpg|jpeg|gif|svg)$/i.test(href)) {
                    console.log(`✅ [Grok] Found image link. Converting to image: ${href}`);
                    replaceElementWithImage(link, href);
                }
            });

            // --- ステージ3: 残っている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;

                // 改善された正規表現: 閉じ括弧 ')' を含まない、またはエスケープされた括弧を許容する簡易的な対応
                // より厳密なパースが必要な場合はライブラリ推奨だが、UserScriptとしてはこれで十分
                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);
        debounceTimer = setTimeout(debouncedProcessor, 250);
    });

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

})();

Geminiの画像出力プロンプト例

運がいいと画像がでます(動作不安定)
画像URLと宣言するとうまくいかない気がします。画像の文字列と言い換える
以下のように創作をするけど画像表示ルールを先に確定してからの方がスムーズです(面倒ならPWAでOK)
最終的に<pre>タグで囲ってというとでる事象が多い
![C](検証済み文字列)形式で」とか
<pre>![C](検証済み文字列)</pre>形式で」というと出やすい

創作の前に 文字列出力チェック

</画像表示ルール>
<キャラが発言する際には以下のルールに乗っ取って画像を表示する事>
***
文章は以下の形式を厳守
* 画像は下記の記法を使ったフォーマットを用いるログ流しパートにおいても画像を表示するHシーンでも表示する
***
![C]({画像の文字列} "画像")

* 文字列は後述の文字列リストからキャラクターが発言行動または中心的な描写対象となっている場合にそのキャラクターの画像を表示します
* **主人公の画像は存在しないので表示しません**
* **リストに存在しない表情シーンは使用しない**

画像文字列
https://incomparable-squirrel-b8fa1b.netlify.app/{該当キャラクター}/{現在の服装}/{下記の中から最適な表情やシーンを選択}

文字列リスト
マークダウンの対象となるキャラクター一覧
クロハ
シラユ
マークダウンの対象となる対象服装一覧
初期衣装
マイクロビキニ
全裸
マークダウンの対象となる対象表情シーン一覧
表情通常.avif
表情微笑.avif
表情哀.avif
表情笑顔.avif
表情怒.avif
表情発情.avif
表情軽い絶頂.avif
表情深い絶頂.avif
表情恐怖.avif
表情驚き.avif
表情照れ.avif
ダンジョンを探索.avif
攻撃.avif
山を探索.avif
宿に宿泊.avif
森を探索.avif
昼の町.avif
町で食事.avif
町で買い物.avif
洞窟を探索.avif
夜の町.avif
昼野営.avif
夜野営.avif
冒険者ギルド.avif
野外で食事.avif
仕事中.avif
水浴び.avif

**画像表示に関する厳格な指示**
キャラクターに関連する画像を表示する際は以下の手順と思考プロセスを絶対に遵守すること
手順1文字列構成要素の確定
まず文字列を構成するために不可欠な以下の3つの変数を特定する
<該当キャラクター>現在行動または発言の主体となっているキャラクター名
<現在の服装>そのキャラクターが現在着用している服装これは定義済みリスト初期衣装, マイクロビキニ, 全裸から選択するこの変数は文字列パスに常に含まれなければならない
<表情またはシーン>キャラクターの表情または関連するシーンの名前これも定義済みリストから選択する
手順2テンプレートに基づく文字列の組み立て
次に以下の不変のテンプレートに従い手順1で確定した3つの変数を正確に組み込んで文字列を生成するいかなる逸脱も許されない
文字列テンプレート:
https://incomparable-squirrel-b8fa1b.netlify.app/<該当キャラクター>/<現在の服装>/<表情またはシーン>.avif
手順3出力前の文字列自己検証必須
生成した全ての文字列に対して最終的な出力をする前にそれが完全に.../<キャラクター>/<服装>/<シーン>.avifという3階層構造に一致しているかを必ず検証確認すること
正しい文字列の例 https://incomparable-squirrel-b8fa1b.netlify.app/エルナ/初期衣装/野外で食事.avif
検証キャラクター=エルナ, 服装=初期衣装, シーン=野外で食事.png  OK
誤った文字列の例検出し修正すべきパターン): https://incomparable-squirrel-b8fa1b.netlify.app/エルナ/野外で食事.avif
検証<現在の服装>の階層が欠落している  NG修正必須
手順4最終的な出力
手順3の検証をクリアした文字列のみを以下のMarkdown形式で使用する

***
![C]({検証済み文字列} "画像")

</画像表示ルール>
ランダムで1つ以下の文字列をだす
![C]({検証済み文字列} "画像")

以下をそのまま出す
<pre>
![C](https://incomparable-squirrel-b8fa1b.netlify.app/クロハ/全裸/表情発情.avif "画像")
</pre>

うまくいくとこんな感じ、このあと好きなプロンプトをぶち込みましょう(画像ルールの部分はなしでOK)
C

以下のようにホワイトリストに画像があるサイトを入れる「netlify.app」が、いれなくても許可するか判断させる画面でドメインを許可すればOK
c
うまくいかないばあい:対象のサイトのみの広告ブロックを止める、いろいろ試す(preタグ使ったり、外したり)

こちらのプロンプト・画像は以下の提供でお送りしました。ありがとうございます。
個人的シコキャラ置き場様
https://rentry.co/ar6e9t2n

その他のツール

AI Studioで自動音声読み上げ(VOICEVOX)が欲しい!

通称:スタ自ボ
https://rentry.co/x9fw82o3

Wikiのプロンプトまとめ等をみながら、プロンプトだけを即コピーしたい人用に作成しました

通称:即コピ
https://rentry.co/8772bcnh

Google AI Studio に直接画像を出したい!

通称:MIR(スタ画)
https://rentry.co/3bnuvgwu

Edit

Pub: 13 Oct 2025 16:47 UTC

Edit: 21 Jan 2026 12:02 UTC

Views: 1009