MarkdownImageRenderer for Gemini
通称「MIR」
目的:Webから、GeminiでマークダウンURLの画像を閲覧する
使い方
1.類似のスクリプトや拡張機能がインストールされていないことを確認してください(Chrome拡張機能など)
2.ブラウザの開発者モードが有効になっていることを確認してください(※)
3.ViolentMonkeyブラウザ拡張機能をインストールしてください。モバイルユーザーはFirefox、Edge、または拡張機能をサポートするその他のブラウザを使用できます。ChromeユーザーはTamperMonkeyを使用する必要がある場合がありますが、これは独自のものなので理想的ではありません。iOSユーザーはApp StoreからUserscriptsをダウンロードし、
4.以下のようにホワイトリストに画像があるサイトを入れる「netlify.app」 ※これはいれなくても許可する?みたいに表示されるのでそこで許可するでもよい
※開発者モード 有効にする方法
https://laboradian.com/tampermonkey-on-dev-mode-chrome/
エディター右にある、設定にある有効がチェックなければチェックしてね(デフォルトONだったような)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
// ==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