// ==UserScript== // @name Bing Plus // @version 1.2 // @description Link Bing search results directly to real URL, show Gemini search results on the right side (PC only), and highlight ad links in green. // @author lanpod // @match https://www.bing.com/search* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @license MIT // @namespace http://tampermonkey.net/ // @downloadURL none // ==/UserScript== (function () { 'use strict'; /*** 공통 유틸 함수 ***/ const getUrlParam = (url, key) => new URL(url).searchParams.get(key); const patterns = [ { pattern: /^https?:\/\/(.*\.)?bing\.com\/(ck\/a|aclick)/, key: 'u' }, { pattern: /^https?:\/\/e\.so\.com\/search\/eclk/, key: 'aurl' }, ]; const isRedirectUrl = url => patterns.find(p => p.pattern.test(url)); const decodeRedirectUrl = (url, key) => { let encodedUrl = getUrlParam(url, key)?.replace(/^a1/, ''); if (!encodedUrl) return null; try { let decodedUrl = decodeURIComponent(atob(encodedUrl.replace(/_/g, '/').replace(/-/g, '+'))); return decodedUrl.startsWith('/') ? window.location.origin + decodedUrl : decodedUrl; } catch { return null; } }; const resolveRealUrl = url => { let match; while ((match = isRedirectUrl(url))) { const realUrl = decodeRedirectUrl(url, match.key); if (!realUrl || realUrl === url) break; url = realUrl; } return url; }; /*** 링크 URL 변환 로직 ***/ const convertLinks = root => { root.querySelectorAll('a[href]').forEach(a => { const realUrl = resolveRealUrl(a.href); if (realUrl && realUrl !== a.href) a.href = realUrl; }); }; /*** 광고 링크 스타일 적용 (초록색) ***/ GM_addStyle(`#b_results > li.b_ad a { color: green !important; }`); /*** PC 환경 확인 함수 ***/ const isPCEnvironment = () => { // PC 환경 감지: 화면 크기와 User-Agent를 기반으로 판단 return window.innerWidth > 768 && !/Mobi|Android|iPhone|iPad|iPod/.test(navigator.userAgent); }; /*** Gemini 검색 결과 박스 생성 및 API 호출 로직 ***/ let apiKey; // API 키 초기화 if (isPCEnvironment()) { // PC 환경에서만 API 키를 요청 apiKey = localStorage.getItem('geminiApiKey') || prompt('Gemini API 키를 입력하세요:'); if (apiKey) localStorage.setItem('geminiApiKey', apiKey); } const markedParse = text => text .replace(/^### (.*$)/gm, '

$1

') .replace(/^## (.*$)/gm, '

$1

') .replace(/^# (.*$)/gm, '

$1

') .replace(/^\* (.*$)/gm, '
  • $1
  • ') .replace(/^- (.*$)/gm, '
  • $1
  • ') .replace(/``````/gs, '
    $1
    ') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/(?$1') .replace(/\n/g, '
    '); const getPromptQuery = query => { const lang = navigator.language; if (lang.includes('ko')) return `"${query}"에 대한 정보를 마크다운 형식으로 작성해줘`; if (lang.includes('zh')) return `请以标记格式填写有关"${query}"的信息。`; return `Please write information about "${query}" in markdown format`; }; const createGeminiBox = () => { const box = document.createElement('div'); box.id = 'gemini-box'; box.innerHTML = `

    Gemini Search Results


    Loading...
    `; return box; }; GM_addStyle(` #gemini-box { max-width:400px; background:#fff; border:1px solid #e0e0e0; padding:16px; margin-bottom:20px; font-family:sans-serif; } #gemini-header { display:flex; align-items:center; margin-bottom:8px; } #gemini-logo { width:24px; height:24px; margin-right:8px; } #gemini-box h3 { margin:0; font-size:18px; color:#202124; } #gemini-divider { height:1px; background:#e0e0e0; margin:8px 0; } #gemini-content { font-size:14px; line-height:1.6; color:#333; } `); let currentQuery; let geminiResponseCache; const fetchGeminiResult = query => { GM_xmlhttpRequest({ method: 'POST', url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ contents: [{ parts: [{ text: getPromptQuery(query) }] }] }), onload({ responseText }) { if (currentQuery !== query) return; try { geminiResponseCache = JSON.parse(responseText)?.candidates?.[0]?.content?.parts?.[0]?.text || 'No response'; document.getElementById('gemini-content').innerHTML = markedParse(geminiResponseCache); } catch { document.getElementById('gemini-content').innerText = 'Error parsing response'; } }, onerror() { document.getElementById('gemini-content').innerText = 'API request failed'; } }); }; const ensureGeminiBox = () => { if (!isPCEnvironment()) return; // 모바일 환경에서는 실행하지 않음 const queryParam = new URLSearchParams(location.search).get('q'); if (!queryParam) return; let contextEl = document.getElementById('b_context'); if (!contextEl) return; let geminiBoxEl = document.getElementById('gemini-box'); if (!geminiBoxEl) contextEl.prepend(createGeminiBox()); if (queryParam === currentQuery && geminiResponseCache) { // 캐시된 응답이 있으면 표시 document.getElementById('gemini-content').innerHTML = markedParse(geminiResponseCache); } else { // 새로운 쿼리일 경우 API 호출 currentQuery = queryParam; fetchGeminiResult(queryParam); } }; // URL 변경 감지 및 Gemini 박스 유지 let lastHref=location.href; new MutationObserver(()=>{ if(location.href!==lastHref){ lastHref=location.href; ensureGeminiBox(); convertLinks(document); } }).observe(document.body,{childList:true,subtree:true}); convertLinks(document); // PC 환경일 경우에만 Gemini 박스 초기화 if (isPCEnvironment()) ensureGeminiBox(); })();