// ==UserScript==
// @name Google Plus & Bing Plus
// @version 7.1.1
// @description Add Gemini response, improve speed to search results(Bing), add Google/Bing search buttons
// @author monit8280
// @match https://www.bing.com/search*
// @match https://www.google.com/search*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect generativelanguage.googleapis.com
// @require https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.12/marked.min.js
// @license MIT
// @namespace http://tampermonkey.net/
// ==/UserScript==
(function () {
'use strict';
// 설정 모듈: 전역 설정값 관리 (API, 스타일, 메시지 등)
const Config = {
API: {
GEMINI_MODEL: 'gemini-2.5-flash',
GEMINI_URL: 'https://generativelanguage.googleapis.com/v1beta/models/',
MARKED_CDN_URL: 'https://api.cdnjs.com/libraries/marked'
},
VERSIONS: {
MARKED_VERSION: '15.0.12'
},
CACHE: {
PREFIX: 'gemini_cache_'
},
STORAGE_KEYS: {
CURRENT_VERSION: 'markedCurrentVersion',
LATEST_VERSION: 'markedLatestVersion',
LAST_NOTIFIED: 'markedLastNotifiedVersion',
THEME_MODE: 'themeMode' // 테마 모드 저장을 위한 새 키 추가
},
UI: {
DEFAULT_MARGIN: 8,
DEFAULT_PADDING: 16,
Z_INDEX: 9999
},
STYLES: {
COLORS: {
BACKGROUND_LIGHT: '#fff', // 라이트 모드 배경색
BACKGROUND_DARK: '#282c34', // 다크 모드 배경색
BORDER_LIGHT: '#e0e0e0', // 라이트 모드 테두리색
BORDER_DARK: '#444', // 다크 모드 테두리색
TEXT_LIGHT: '#000', // 라이트 모드 텍스트색
TEXT_DARK: '#e0e0e0', // 다크 모드 텍스트색
TITLE_LIGHT: '#000', // 라이트 모드 제목색
TITLE_DARK: '#ffffff', // 다크 모드 제목색
BUTTON_BG_LIGHT: '#f0f3ff', // 라이트 모드 버튼 배경
BUTTON_BG_DARK: '#3a3f4b', // 다크 모드 버튼 배경
BUTTON_BORDER_LIGHT: '#ccc', // 라이트 모드 버튼 테두리
BUTTON_BORDER_DARK: '#555', // 다크 모드 버튼 테두리
CODE_BLOCK_BG_LIGHT: '#f0f0f0', // 라이트 모드 코드 블록 배경
CODE_BLOCK_BG_DARK: '#3b3b3b', // 다크 모드 코드 블록 배경
},
BORDER_RADIUS: '4px',
FONT_SIZE: {
TEXT: '14px',
TITLE: '18px'
},
ICON_SIZE: '20px',
LOGO_SIZE: '24px',
SMALL_ICON_SIZE: '16px'
},
ASSETS: {
GOOGLE_LOGO: 'https://www.google.com/favicon.ico',
BING_LOGO: 'https://account.microsoft.com/favicon.ico',
GEMINI_LOGO: 'data:image/png;base64,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',
REFRESH_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik00LjA2MTg5IDEzQzQuMDIxMDQgMTIuNjcyNCA0IDEyLjMzODcgNCAxMkM0IDcuNTgxNzIgNy41ODE3MiA0IDEyIDRDMTQuNTAwNiA0IDE2LjczMzIgNS4xNDcyNyAxOC4yMDAyIDYuOTQ0MTZNMTkuOTM4MSAxMUMxOS45NzkgMTEuMzI3NiAyMCAxMS42NjEzIDIwIDEyQzIwIDE2LjQxODMgMTYuNDE4MyAyMCAxMiAyMEM5LjYxMDYxIDIwIDcuNDY1ODkgMTguOTUyNSA2IDE3LjI5MTZNOSAxN0g2VjE3LjI5MTZNMTguMjAwMiA0VjYuOTQ0MTZNMTguMjAwMiA2Ljk0NDE2VjYuOTk5OTNMMTUuMjAwMiA3TTYgMjBWMTcuMjkxNiIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPC9zdmc+',
LIGHT_MODE_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIEdlbmVyYXRvcjogU1ZHIFJlcG8gTWl4ZXIgVG9vbHMgLS0+DQo8c3ZnIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgY2xhc3M9Imljb24iICB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg2MSA2NTYuN2wxNDQuNi0xNDQuNkw4NjEgMzY3LjZWMTYzLjFINjU2LjZMNTEyIDE4LjYgMzY3LjQgMTYzLjFIMTYzdjIwNC41TDE4LjQgNTEyLjEgMTYzIDY1Ni43djIwNC40aDIwNC40TDUxMiAxMDA1LjdsMTQ0LjYtMTQ0LjZIODYxeiIgZmlsbD0iI0ZDRDE3MCIgLz48cGF0aCBkPSJNNTEyIDEwMTUuN2MtMi42IDAtNS4xLTEtNy4xLTIuOUwzNjMuMyA4NzEuMUgxNjNjLTUuNSAwLTEwLTQuNS0xMC0xMFY2NjAuOEwxMS40IDUxOS4yYy0xLjktMS45LTIuOS00LjQtMi45LTcuMSAwLTIuNyAxLjEtNS4yIDIuOS03LjFMMTUzIDM2My40VjE2My4xYzAtNS41IDQuNS0xMCAxMC0xMGgyMDAuM0w1MDQuOSAxMS41YzEuOS0xLjkgNC40LTIuOSA3LjEtMi45czUuMiAxLjEgNy4xIDIuOWwxNDEuNiAxNDEuNkg4NjFjNS41IDAgMTAgNC41IDEwIDEwdjIwMC4zTDEwMTIuNiA1MDVjMS45IDEuOSAyLjkgNC40IDIuOSA3LjEgMCAyLjctMS4xIDUuMi0yLjkgNy4xTDg3MSA2NjAuOHYyMDAuM2MwIDUuNS00LjUgMTAtMTAgMTBINjYwLjdsLTE0MS42IDE0MS42Yy0yIDItNC41IDMtNy4xIDN6TTE3MyA4NTEuMWgxOTQuNGMyLjcgMCA1LjIgMS4xIDcuMSAyLjlMNTEyIDk5MS42bDEzNy41LTEzNy41YzEuOS0xLjkgNC40LTIuOSA3LjEtMi45SDg1MVY2NTYuN2MwLTIuNyAxLjEtNS4yIDIuOS03LjFsMTM3LjUtMTM3LjUtMTM3LjUtMTM3LjVjLTEuOS0xLjktMi45LTQuNC0yLjktNy4xVjE3My4xSDY1Ni42Yy0yLjcgMC01LjItMS4xLTcuMS0yLjlMNTEyIDMyLjcgMzc0LjUgMTcwLjJjLTEuOSAxLjktNC40IDIuOS03LjEgMi45SDE3M3YxOTQuNGMwIDIuNy0xLjEgNS4yLTIuOSA3LjFMMzIuNiA1MTIuMWwxMzcuNSAxMzcuNWMxLjkgMS45IDIuOSA0LjQgMi45IDcuMXYxOTQuNHoiIGZpbGw9IiIgLz48cGF0aCBkPSJNNTEyIDUxMi4xbS0yNTcuOCAwYTI1Ny44IDI1Ny44IDAgMSAwIDUxNS42IDAgMjU3LjggMjU3LjggMCAxIDAtNTE1LjYgMFoiIGZpbGw9IiNGN0REQUQiIC8+PHBhdGggZD0iTTUxMiA3NzkuOWMtNzEuNSAwLTEzOC44LTI3LjktMTg5LjQtNzguNC01MC42LTUwLjYtNzguNC0xMTcuOC03OC40LTE4OS40czI3LjktMTM4LjggNzguNC0xODkuNGM1MC42LTUwLjYgMTE3LjgtNzguNCAxODkuNC03OC40IDcxLjUgMCAxMzguOCAyNy45IDE4OS40IDc4LjQgNTAuNiA1MC42IDc4LjQgMTE3LjggNzguNCAxODkuNFM3NTIgNjUwLjkgNzAxLjQgNzAxLjUgNTgzLjUgNzc5LjkgNTEyIDc3OS45eiBtMC01MTUuNmMtNjYuMiAwLTEyOC40IDI1LjgtMTc1LjIgNzIuNi00Ni44IDQ2LjgtNzIuNiAxMDktNzIuNiAxNzUuMnMyNS44IDEyOC40IDcyLjYgMTc1LjJjNDYuOCA0Ni44IDEwOSA3Mi42IDE3NS4yIDcyLjYgNjYuMiAwIDEyOC40LTI1LjggMTc1LjItNzIuNiA0Ni44LTQ2LjggNzIuNi0xMDkgNzIuNi0xNzUuMlM3MzQgMzgzLjcgNjg3LjIgMzM2LjljLTQ2LjgtNDYuOC0xMDktNzIuNi0xNzUuMi03Mi42eiIgZmlsbD0iIiAvPjwvc3ZnPg==', // 라이트 모드 아이콘
DARK_MODE_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xOS45MDAxIDIuMzA3MTlDMTkuNzM5MiAxLjg5NzYgMTkuMTYxNiAxLjg5NzYgMTkuMDAwNyAyLjMwNzE5TDE4LjU3MDMgMy40MDI0N0MxOC41MjEyIDMuNTI3NTIgMTguNDIyNiAzLjYyNjUxIDE4LjI5OCAzLjY3NTgzTDE3LjIwNjcgNC4xMDc4QzE2Ljc5ODYgNC4yNjkzNCAxNi43OTg2IDQuODQ5IDE3LjIwNjcgNS4wMTA1NEwxOC4yOTggNS40NDI1MkMxOC40MjI2IDUuNDkxODQgMTguNTIxMiA1LjU5MDgyIDE4LjU3MDMgNS43MTU4N0wxOS4wMDA3IDYuODExMTVDMTkuMTYxNiA3LjIyMDc0IDE5LjczOTIgNy4yMjA3NCAxOS45MDAxIDYuODExMTZMMjAuMzMwNSA1LjcxNTg3QzIwLjM3OTYgNS41OTA4MiAyMC40NzgyIDUuNDkxODQgMjAuNjAyOCA1LjQ0MjUyTDIxLjY5NDEgNS4wMTA1NEMyMi4xMDIyIDQuODQ5IDIyLjEwMjIgNC4yNjkzNCAyMS42OTQxIDQuMTA3OEwyMC42MDI4IDMuNjc1ODNDMjAuNDc4MiAzLjYyNjUxIDIwLjM3OTYgMy41Mjc1MiAyMC4zMzA1IDMuNDAyNDdMMTkuOTAwMSAyLjMwNzE5WiIgZmlsbD0iIzFDMjc0QyIvPg0KPHBhdGggZD0iTTE2LjAzMjggOC4xMjk2N0MxNS44NzE4IDcuNzIwMDkgMTUuMjk0MyA3LjcyMDA5IDE1LjEzMzMgOC4xMjk2N0wxNC45NzY0IDguNTI5MDJDMTQuOTI3MyA4LjY1NDA3IDE0LjgyODcgOC43NTMwNSAxNC43MDQxIDguODAyMzdMMTQuMzA2MiA4Ljk1OTg3QzEzLjg5ODEgOS4xMjE0MSAxMy44OTgxIDkuNzAxMDcgMTQuMzA2MiA5Ljg2MjYxTDE0LjcwNDEgMTAuMDIwMUMxNC44Mjg3IDEwLjA2OTQgMTQuOTI3MyAxMC4xNjg0IDE0Ljk3NjQgMTAuMjkzNUwxNS4xMzMzIDEwLjY5MjhDMTUuMjk0MyAxMS4xMDI0IDE1Ljg3MTggMTEuMTAyNCAxNi4wMzI4IDEwLjY5MjhMMTYuMTg5NyAxMC4yOTM1QzE2LjIzODggMTAuMTY4NCAxNi4zMzc0IDEwLjA2OTQgMTYuNDYyIDEwLjAyMDFMMTYuODU5OSA5Ljg2MjYxQzE3LjI2OCA5LjcwMTA3IDE3LjI2OCA5LjEyMTQxIDE2Ljg1OTkgOC45NTk4N0wxNi40NjIgOC44MDIzN0MxNi4zMzc0IDguNzUzMDUgMTYuMjM4OCA4LjY1NDA3IDE2LjE4OTcgOC41MjkwMkwxNi4wMzI4IDguMTI5NjdaIiBmaWxsPSIjMUMyNzRDIi8+DQo8cGF0aCBkPSJNMTIgMjJDMTcuNTIyOCAyMiAyMiAxNy41MjI4IDIyIDEyQzIyIDExLjUzNzMgMjEuMzA2NSAxMS40NjA4IDIxLjA2NzIgMTEuODU2OEMxOS45Mjg5IDEzLjc0MDYgMTcuODYxNSAxNSAxNS41IDE1QzExLjkxMDEgMTUgOSAxMi4wODk5IDkgOC41QzkgNi4xMzg0NSAxMC4yNTk0IDQuMDcxMDUgMTIuMTQzMiAyLjkzMjc2QzEyLjUzOTIgMi42OTM0NyAxMi40NjI3IDIgMTIgMkM2LjQ3NzE1IDIgMiA2LjQ3NzE1IDIgMTJDMiAxNy41MjI4IDYuNDc3MTUgMjIgMTIgMjJaIiBmaWxsPSIjMUMyNzRDIi8+DQo8L3N2Zz4=' // 다크 모드 아이콘
},
MESSAGE_KEYS: {
PROMPT: 'prompt',
ENTER_API_KEY: 'enterApiKey',
GEMINI_EMPTY: 'geminiEmpty',
PARSE_ERROR: 'parseError',
NETWORK_ERROR: 'networkError',
TIMEOUT: 'timeout',
LOADING: 'loading',
UPDATE_TITLE: 'updateTitle',
UPDATE_NOW: 'updateNow',
SEARCH_ON_GOOGLE: 'searchongoogle',
SEARCH_ON_BING: 'searchonbing'
}
};
// 지역화 모듈: 다국어 메시지 처리
const Localization = {
MESSAGES: {
[Config.MESSAGE_KEYS.PROMPT]: {
ko: `"${'${query}'}"에 대한 정보를 찾아줘`,
zh: `请以标记格式填写有关\"${'${query}'}\"的信息。`,
default: `Please write information about \"${'${query}'}\" in markdown format`
},
[Config.MESSAGE_KEYS.ENTER_API_KEY]: {
ko: 'Gemini API 키를 입력하세요:',
zh: '请输入 Gemini API 密钥:',
default: 'Please enter your Gemini API key:'
},
[Config.MESSAGE_KEYS.GEMINI_EMPTY]: {
ko: '⚠️ Gemini 응답이 비어있습니다.',
zh: '⚠️ Gemini 返回为空。',
default: '⚠️ Gemini response is empty.'
},
[Config.MESSAGE_KEYS.PARSE_ERROR]: {
ko: '❌ 파싱 오류:',
zh: '❌ 解析错误:',
default: '❌ Parsing error:'
},
[Config.MESSAGE_KEYS.NETWORK_ERROR]: {
ko: '❌ 네트워크 오류:',
zh: '❌ 网络错误:',
default: '❌ Network error:'
},
[Config.MESSAGE_KEYS.TIMEOUT]: {
ko: '❌ 요청 시간이 초과되었습니다.',
zh: '❌ 请求超时。',
default: '❌ Request timeout'
},
[Config.MESSAGE_KEYS.LOADING]: {
ko: '불러오는 중...',
zh: '加载中...',
default: 'Loading...'
},
[Config.MESSAGE_KEYS.UPDATE_TITLE]: {
ko: 'marked.min.js 업데이트 필요',
zh: '需要更新 marked.min.js',
default: 'marked.min.js update required'
},
[Config.MESSAGE_KEYS.UPDATE_NOW]: {
ko: '확인',
zh: '确认',
default: 'OK'
},
[Config.MESSAGE_KEYS.SEARCH_ON_GOOGLE]: {
ko: 'Google 에서 검색하기',
zh: '在 Google 上搜索',
default: 'Search on Google'
},
[Config.MESSAGE_KEYS.SEARCH_ON_BING]: {
ko: 'Bing 에서 검색하기',
zh: '在 Bing 上搜索',
default: 'Search on Bing'
}
},
getMessage(key, vars = {}) {
const lang = navigator.language;
const langKey = lang.includes('ko') ? 'ko' : lang.includes('zh') ? 'zh' : 'default';
const template = this.MESSAGES[key]?.[langKey] || this.MESSAGES[key]?.default || '';
return template.replace(/\$\{(.*?)\}/g, (_, k) => vars[k] || '');
}
};
// 디바이스 감지 모듈
const DeviceDetector = {
_cache: {
deviceType: null,
isGeminiAvailable: null
},
getDeviceType() {
if (this._cache.deviceType !== null) {
return this._cache.deviceType;
}
const userAgent = navigator.userAgent;
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const width = window.innerWidth;
let deviceType;
const isAndroid = /Android/i.test(userAgent);
const isIPhone = /iPhone/i.test(userAgent);
const hasMobileKeyword = /Mobile/i.test(userAgent);
const isWindows = /Windows NT/i.test(userAgent);
if (isWindows && !isTouchDevice && width > 1024) {
deviceType = 'desktop';
} else if ((isAndroid || isIPhone) && hasMobileKeyword) {
deviceType = 'mobile';
} else if (isAndroid && !hasMobileKeyword && width >= 768) {
deviceType = 'tablet';
} else if (isTouchDevice && width <= 1024) {
deviceType = 'mobile';
} else {
deviceType = 'desktop';
}
this._cache.deviceType = deviceType;
console.log(`Device Type: ${deviceType.charAt(0).toUpperCase() + deviceType.slice(1)}`);
return deviceType;
},
isDesktop() {
return this.getDeviceType() === 'desktop';
},
isMobile() {
return this.getDeviceType() === 'mobile';
},
isTablet() {
return this.getDeviceType() === 'tablet';
},
isGeminiAvailable() {
if (this._cache.isGeminiAvailable === null) {
const hasRHS = !!document.getElementById('rhs') ||
!!document.getElementById('b_context') || !!document.querySelector('.b_right');
this._cache.isGeminiAvailable = this.isDesktop() && hasRHS;
}
return this._cache.isGeminiAvailable;
},
resetCache() {
this._cache = {
deviceType: null,
isGeminiAvailable: null
};
},
isGoogle() {
return window.location.hostname.includes('google.com');
},
isBing() {
return window.location.hostname.includes('bing.com');
}
};
// 스타일 생성 모듈: CSS 스타일 정의
const StyleGenerator = {
// 공통 스타일 정의
commonStyles: {
'#b_results > li.b_ad a': { 'color': 'green !important' },
'#b_context, .b_context, .b_right': {
'color': 'initial !important',
'border': 'none !important',
'border-width': '0 !important',
'border-style': 'none !important',
'border-collapse': 'separate !important',
'background': 'transparent !important'
},
'#rhs': {
'float': 'right',
'padding-left': '16px',
'width': '432px',
'margin-top': '20px'
},
'#rhs #gemini-wrapper': {
'margin-bottom': '20px'
},
// Google 모바일 페이지의 gsr 요소 배경색 추가
'.mobile-useragent #gsr': {
'background-color': '#ffffff !important'
}
},
// Gemini 박스 스타일 정의
geminiBoxStyles: {
'#gemini-box': {
'width': '100%',
'max-width': '100%',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'padding': `${Config.UI.DEFAULT_PADDING}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN * 2.5}px`,
'font-family': 'sans-serif',
'overflow-x': 'auto',
'position': 'relative',
'box-sizing': 'border-box',
'color': 'initial !important'
}
},
// 테마별 스타일 정의
themeStyles: {
'#gemini-box': {
'background': `var(--gemini-background-color) !important`,
'border-color': `var(--gemini-border-color) !important`
},
'#gemini-box h3': {
'color': `var(--gemini-title-color) !important`
},
'#gemini-content, #gemini-content *': {
'color': `var(--gemini-text-color) !important`,
'background': 'transparent !important'
},
'#gemini-divider': {
'background': `var(--gemini-border-color) !important`
},
'#gemini-content pre': {
'background': `var(--gemini-code-block-bg) !important`,
'padding': `${Config.UI.DEFAULT_MARGIN + 2}px`,
'border-radius': Config.STYLES.BORDER_RADIUS,
'overflow-x': 'auto'
},
'#google-search-btn, #bing-search-btn': {
'border-color': `var(--gemini-button-border)`,
'background-color': `var(--gemini-button-bg)`,
'color': `var(--gemini-title-color)`,
},
'#marked-update-popup': {
'background': `var(--gemini-background-color)`,
'border-color': `var(--gemini-button-border)`,
},
'#marked-update-popup button': {
'border-color': `var(--gemini-button-border)`,
'background-color': `var(--gemini-button-bg)`,
'color': `var(--gemini-title-color)`,
}
},
// Gemini 콘텐츠 스타일 정의
contentStyles: {
'#gemini-content': {
'font-size': Config.STYLES.FONT_SIZE.TEXT,
'line-height': '1.6',
'white-space': 'pre-wrap',
'word-wrap': 'break-word',
'overflow-wrap': 'break-word',
'background': 'transparent !important'
},
'#gemini-content ul, #gemini-content ol': {
'list-style-type': 'none'
}
},
// Gemini 헤더 스타일 정의
headerStyles: {
'#gemini-header': {
'display': 'flex',
'align-items': 'center',
'justify-content': 'space-between',
'margin-bottom': `${Config.UI.DEFAULT_MARGIN}px`
},
'#gemini-title-wrap': {
'display': 'flex',
'align-items': 'center'
},
'#gemini-logo': {
'width': Config.STYLES.LOGO_SIZE,
'height': Config.STYLES.LOGO_SIZE,
'margin-right': `${Config.UI.DEFAULT_MARGIN}px`
},
'#gemini-box h3': {
'margin': '0',
'font-size': Config.STYLES.FONT_SIZE.TITLE,
'font-weight': 'bold'
},
'#gemini-refresh-btn': {
'width': Config.STYLES.ICON_SIZE,
'height': Config.STYLES.ICON_SIZE,
'cursor': 'pointer',
'opacity': '0.6',
'transition': 'transform 0.5s ease',
'margin-left': `${Config.UI.DEFAULT_MARGIN}px`
},
'#gemini-theme-toggle-btn': {
'width': Config.STYLES.ICON_SIZE,
'height': Config.STYLES.ICON_SIZE,
'cursor': 'pointer',
'opacity': '0.6',
'transition': 'transform 0.5s ease'
},
'#gemini-refresh-btn:hover, #gemini-theme-toggle-btn:hover': {
'opacity': '1',
'transform': 'rotate(360deg)'
},
'#gemini-divider': {
'height': '1px',
'margin': `${Config.UI.DEFAULT_MARGIN}px 0`
}
},
// Google/Bing 검색 버튼 스타일 정의
searchButtonStyles: {
'#google-search-btn, #bing-search-btn': {
'width': '100%',
'max-width': '100%',
'font-size': Config.STYLES.FONT_SIZE.TEXT,
'padding': `${Config.UI.DEFAULT_MARGIN}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN * 1.25}px`,
'cursor': 'pointer',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'font-family': 'sans-serif',
'display': 'flex',
'align-items': 'center',
'justify-content': 'center',
'gap': `${Config.UI.DEFAULT_MARGIN}px`,
'transition': 'transform 0.2s ease'
},
'#google-search-btn img, #bing-search-btn img': {
'width': Config.STYLES.SMALL_ICON_SIZE,
'height': Config.STYLES.SMALL_ICON_SIZE,
'vertical-align': 'middle',
'transition': 'transform 0.2s ease'
},
'.desktop-useragent #google-search-btn:hover, .desktop-useragent #bing-search-btn:hover': {
'transform': 'scale(1.1)'
},
'.desktop-useragent #google-search-btn:hover img, .desktop-useragent #bing-search-btn:hover img': {
'transform': 'scale(1.1)'
}
},
// 업데이트 팝업 스타일 정의
popupStyles: {
'#marked-update-popup': {
'position': 'fixed',
'top': '30%',
'left': '50%',
'transform': 'translate(-50%, -50%)',
'padding': `${Config.UI.DEFAULT_PADDING * 1.25}px`,
'z-index': Config.UI.Z_INDEX,
'border-width': '1px',
'border-style': 'solid',
'box-shadow': '0 2px 10px rgba(0,0,0,0.1)',
'text-align': 'center'
},
'#marked-update-popup button': {
'margin-top': `${Config.UI.DEFAULT_MARGIN * 1.25}px`,
'padding': `${Config.UI.DEFAULT_PADDING}px ${Config.UI.DEFAULT_PADDING}px`,
'cursor': 'pointer',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'font-family': 'sans-serif'
}
},
// 모바일 환경 스타일 정의
mobileStyles: {
'.mobile-useragent #google-search-btn, .mobile-useragent #bing-search-btn': {
'max-width': '100%',
'width': 'calc(100% - 16px)',
'margin-left': `${Config.UI.DEFAULT_MARGIN}px !important`,
'margin-right': `${Config.UI.DEFAULT_MARGIN}px !important`,
'margin-top': `${Config.UI.DEFAULT_MARGIN}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN}px`,
'padding': `${Config.UI.DEFAULT_PADDING * 0.75}px`,
'border-radius': '16px',
'box-sizing': 'border-box'
},
'.mobile-useragent #gemini-box': {
'padding': `${Config.UI.DEFAULT_PADDING * 0.75}px`,
'border-radius': '16px'
},
'.mobile-useragent #b_content': {
'overflow': 'visible !important',
'position': 'relative'
}
},
// 모든 스타일을 하나의 CSS 문자열로 변환
generateStyles() {
const styles = [
this.commonStyles,
this.geminiBoxStyles,
this.themeStyles,
this.contentStyles,
this.headerStyles,
this.searchButtonStyles,
this.popupStyles,
this.mobileStyles
];
// CSS 변수 정의 (root에 추가하여 테마 변경에 활용)
const cssVariables = `
:root {
--gemini-background-color: ${Config.STYLES.COLORS.BACKGROUND_LIGHT};
--gemini-border-color: ${Config.STYLES.COLORS.BORDER_LIGHT};
--gemini-text-color: ${Config.STYLES.COLORS.TEXT_LIGHT};
--gemini-title-color: ${Config.STYLES.COLORS.TITLE_LIGHT};
--gemini-button-bg: ${Config.STYLES.COLORS.BUTTON_BG_LIGHT};
--gemini-button-border: ${Config.STYLES.COLORS.BUTTON_BORDER_LIGHT};
--gemini-code-block-bg: ${Config.STYLES.COLORS.CODE_BLOCK_BG_LIGHT};
}
.dark-mode {
--gemini-background-color: ${Config.STYLES.COLORS.BACKGROUND_DARK};
--gemini-border-color: ${Config.STYLES.COLORS.BORDER_DARK};
--gemini-text-color: ${Config.STYLES.COLORS.TEXT_DARK};
--gemini-title-color: ${Config.STYLES.COLORS.TITLE_DARK};
--gemini-button-bg: ${Config.STYLES.COLORS.BUTTON_BG_DARK};
--gemini-button-border: ${Config.STYLES.COLORS.BUTTON_BORDER_DARK};
--gemini-code-block-bg: ${Config.STYLES.COLORS.CODE_BLOCK_BG_DARK};
}
`;
return cssVariables + styles.reduce((css, styleObj) => {
for (const [selector, props] of Object.entries(styleObj)) {
css += `${selector} {`;
for (const [prop, value] of Object.entries(props)) {
css += `${prop}: ${value};`;
}
css += '}';
}
return css;
}, '');
}
};
// 테마 관리 모듈: 테마 변경 감지 및 적용
const ThemeManager = {
currentTheme: 'light', // 초기 테마 설정
init() {
const savedTheme = localStorage.getItem(Config.STORAGE_KEYS.THEME_MODE);
if (savedTheme) {
this.currentTheme = savedTheme;
} else {
// 시스템 테마 감지 (선택 사항)
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.currentTheme = 'dark';
}
}
this.applyTheme();
},
applyTheme() {
if (this.currentTheme === 'dark') {
document.documentElement.classList.add('dark-mode');
} else {
document.documentElement.classList.remove('dark-mode');
}
},
toggleTheme() {
this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
localStorage.setItem(Config.STORAGE_KEYS.THEME_MODE, this.currentTheme);
this.applyTheme();
this.updateThemeToggleButtonIcon();
},
getThemeToggleButtonIcon() {
return this.currentTheme === 'light' ? Config.ASSETS.DARK_MODE_ICON : Config.ASSETS.LIGHT_MODE_ICON;
},
updateThemeToggleButtonIcon() {
const themeToggleButton = document.getElementById('gemini-theme-toggle-btn');
if (themeToggleButton) {
themeToggleButton.src = this.getThemeToggleButtonIcon();
themeToggleButton.title = this.currentTheme === 'light' ? 'Dark Mode' : 'Light Mode';
}
},
observeThemeChange() {
// 외부 테마 변경 감지 (예: 시스템 설정 변경) - 필요에 따라 추가
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
const newTheme = e.matches ? 'dark' : 'light';
if (this.currentTheme !== newTheme) {
this.currentTheme = newTheme;
localStorage.setItem(Config.STORAGE_KEYS.THEME_MODE, this.currentTheme);
this.applyTheme();
this.updateThemeToggleButtonIcon();
}
});
}
};
// 스타일 관리 모듈: 스타일 초기화 및 적용
const Styles = {
initStyles() {
const styleElement = document.createElement('style');
styleElement.id = 'bing-plus-styles';
styleElement.textContent = StyleGenerator.generateStyles(); // CSS 문자열 생성
document.head.appendChild(styleElement);
// <style> 요소 추가
this.applyMobileStyles();
},
applyMobileStyles() {
if (DeviceDetector.isMobile()) {
document.documentElement.classList.add('mobile-useragent');
} else if (DeviceDetector.isDesktop()) {
document.documentElement.classList.add('desktop-useragent');
}
}
};
// 유틸리티 모듈: 공통 유틸리티 함수
const Utils = {
getQuery() {
return new URLSearchParams(location.search).get('q');
},
getApiKey() {
let key = localStorage.getItem('geminiApiKey');
if (!key) {
key = prompt(Localization.getMessage(Config.MESSAGE_KEYS.ENTER_API_KEY));
if (key) localStorage.setItem('geminiApiKey', key);
}
return key;
}
};
// UI 생성 모듈: DOM 요소 생성
const UI = {
createSearchButton(query) {
const btn = document.createElement('button');
if (DeviceDetector.isGoogle()) {
btn.id = 'bing-search-btn';
btn.innerHTML = `
<img src="${Config.ASSETS.BING_LOGO}" alt="Bing Logo" style="width: ${Config.STYLES.SMALL_ICON_SIZE}; height: ${Config.STYLES.SMALL_ICON_SIZE}; vertical-align: middle;">
${Localization.getMessage(Config.MESSAGE_KEYS.SEARCH_ON_BING)}
`;
btn.onclick = () => window.open(`https://www.bing.com/search?q=${encodeURIComponent(query)}`, '_blank');
} else {
btn.id = 'google-search-btn';
btn.innerHTML = `
<img src="${Config.ASSETS.GOOGLE_LOGO}" alt="Google Logo">
${Localization.getMessage(Config.MESSAGE_KEYS.SEARCH_ON_GOOGLE)}
`;
btn.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(query)}`, '_blank');
}
return btn;
},
createGeminiBox(query, apiKey) {
const box = document.createElement('div');
box.id = 'gemini-box';
box.innerHTML = `
<div id="gemini-header">
<div id="gemini-title-wrap">
<img id="gemini-logo" src="${Config.ASSETS.GEMINI_LOGO}" alt="Gemini Logo">
<h3>Gemini Search Results</h3>
</div>
<div style="display: flex; align-items: center;">
<img id="gemini-theme-toggle-btn" title="${ThemeManager.currentTheme === 'light' ? 'Dark Mode' : 'Light Mode'}" src="${ThemeManager.getThemeToggleButtonIcon()}" />
<img id="gemini-refresh-btn" title="Refresh" src="${Config.ASSETS.REFRESH_ICON}" />
</div>
</div>
<hr id="gemini-divider">
<div id="gemini-content">${Localization.getMessage(Config.MESSAGE_KEYS.LOADING)}</div>
`;
box.querySelector('#gemini-refresh-btn').onclick = () => GeminiAPI.fetch(query, box.querySelector('#gemini-content'), apiKey, true);
box.querySelector('#gemini-theme-toggle-btn').onclick = () => ThemeManager.toggleTheme(); // 토글 버튼 클릭 이벤트
if (DeviceDetector.isDesktop()) {
VersionChecker.checkMarkedJsVersion();
}
return box;
},
createGeminiUI(query, apiKey) {
const wrapper = document.createElement('div');
wrapper.id = 'gemini-wrapper';
wrapper.appendChild(this.createSearchButton(query));
wrapper.appendChild(this.createGeminiBox(query, apiKey));
return wrapper;
},
removeExistingElements() {
document.querySelectorAll('#gemini-wrapper, #google-search-btn, #bing-search-btn').forEach(el => el.remove());
},
createRHSIfNeeded() {
if (DeviceDetector.isGoogle() && !document.getElementById('rhs')) {
const mainContent = document.getElementById('rcnt');
if (mainContent) {
const rhsDiv = document.createElement('div');
rhsDiv.id = 'rhs';
rhsDiv.setAttribute('jsname', 'Iclw3');
rhsDiv.style.cssText = `
float: right;
padding-left: 16px;
width: 432px;
margin-top: 20px;
`;
mainContent.appendChild(rhsDiv);
}
}
}
};
// Gemini API 모듈: Gemini API 호출 및 응답 처리
const GeminiAPI = {
fetch(query, container, apiKey, force = false) {
console.log('GeminiAPI.fetch 호출됨. 쿼리:', { query, apiKey, force });
const cacheKey = `${Config.CACHE.PREFIX}${query}`;
const cached = force ? null : sessionStorage.getItem(cacheKey);
if (cached) {
console.log('캐시된 응답 사용:', query);
if (container) container.innerHTML = marked.parse(cached);
return;
}
if (!apiKey) {
console.error('Gemini API 키가 누락되었습니다!');
if (container) container.textContent = Localization.getMessage(Config.MESSAGE_KEYS.ENTER_API_KEY);
return;
}
if (container) container.textContent = Localization.getMessage(Config.MESSAGE_KEYS.LOADING);
const promptText = Localization.getMessage(Config.MESSAGE_KEYS.PROMPT, { query });
console.log('Gemini API 요청 프롬프트:', promptText);
console.log('API URL:', `${Config.API.GEMINI_URL}${Config.API.GEMINI_MODEL}:generateContent?key=${apiKey}`);
GM_xmlhttpRequest({
method: 'POST',
url: `${Config.API.GEMINI_URL}${Config.API.GEMINI_MODEL}:generateContent?key=${apiKey}`,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
contents: [{ parts: [{ text: promptText }] }],
generationConfig: {
thinkingConfig: {
thinkingBudget: 0, // Disables thinking
},
},
}),
onload({ status, responseText }) {
console.log('GM_xmlhttpRequest onload. 상태:', status, '응답 텍스트 (일부):', responseText.substring(0, 200));
try {
const parsedResponse = JSON.parse(responseText);
const text = parsedResponse?.candidates?.[0]?.content?.parts?.[0]?.text;
if (text) {
sessionStorage.setItem(cacheKey, text);
if (container) container.innerHTML = marked.parse(text);
console.log('Gemini 응답 성공적으로 파싱 및 표시됨.');
} else {
if (container) {
if (parsedResponse.error) {
container.textContent = `❌ Gemini API 오류: ${parsedResponse.error.message ||
JSON.stringify(parsedResponse.error)}`;
console.error('Gemini API에서 오류를 반환했습니다:', parsedResponse.error);
} else {
container.textContent = Localization.getMessage(Config.MESSAGE_KEYS.GEMINI_EMPTY);
console.warn('Gemini 응답이 비어있거나 예상된 텍스트를 포함하지 않습니다.');
}
}
}
} catch (e) {
if (container) container.textContent = `${Localization.getMessage(Config.MESSAGE_KEYS.PARSE_ERROR)} ${e.message}`;
console.error('Gemini API 응답 파싱 오류:', e, '응답 텍스트:', responseText);
}
},
onerror(err) {
if (container) container.textContent = `${Localization.getMessage(Config.MESSAGE_KEYS.NETWORK_ERROR)} ${err.finalUrl ||
err.statusText || JSON.stringify(err)}`;
console.error('GM_xmlhttpRequest onerror:', err);
},
ontimeout() {
if (container) container.textContent = Localization.getMessage(Config.MESSAGE_KEYS.TIMEOUT);
console.warn('GM_xmlhttpRequest ontimeout 쿼리:', query);
}
});
}
};
// 링크 정리 모듈: 중간 URL 제거
const LinkCleaner = {
decodeRealUrl(url, key) {
const param = new URL(url).searchParams.get(key)?.replace(/^a1/, '');
if (!param) return null;
try {
const decoded = decodeURIComponent(atob(param.replace(/_/g, '/').replace(/-/g, '+')));
return decoded.startsWith('/') ? location.origin + decoded : decoded;
} catch {
return null;
}
},
resolveRealUrl(url) {
const rules = [
{ pattern: /bing\.com\/(ck\/a|aclick)/, key: 'u' },
{ pattern: /so\.com\/search\/eclk/, key: 'aurl' },
{ pattern: /google\.com\/url/, key: 'url' }
];
for (const { pattern, key } of rules) {
if (pattern.test(url)) {
const real = this.decodeRealUrl(url, key);
if (real && real !== url) return real;
}
}
return url;
},
convertLinksToReal(root) {
root.querySelectorAll('a[href]').forEach(a => {
const realUrl = this.resolveRealUrl(a.href);
if (realUrl && realUrl !== a.href) a.href = realUrl;
});
}
};
// 버전 확인 모듈: marked.js 버전 체크
const VersionChecker = {
compareVersions(current, latest) {
const currentParts = current.split('.').map(Number);
const latestParts = latest.split('.').map(Number);
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
const c = currentParts[i] ||
0;
const l = latestParts[i] || 0;
if (c < l) return -1;
if (c > l) return 1;
}
return 0;
},
checkMarkedJsVersion() {
localStorage.setItem(Config.STORAGE_KEYS.CURRENT_VERSION, Config.VERSIONS.MARKED_VERSION);
GM_xmlhttpRequest({
method: 'GET',
url: Config.API.MARKED_CDN_URL,
onload: ({ responseText }) => {
try {
const latest = JSON.parse(responseText).version;
localStorage.setItem(Config.STORAGE_KEYS.LATEST_VERSION, latest);
const lastNotified = localStorage.getItem(Config.STORAGE_KEYS.LAST_NOTIFIED);
if (this.compareVersions(Config.VERSIONS.MARKED_VERSION, latest) < 0 &&
(!lastNotified || this.compareVersions(lastNotified, latest) < 0)) {
const existingPopup = document.getElementById('marked-update-popup');
if (existingPopup) existingPopup.remove();
const popup = document.createElement('div');
popup.id = 'marked-update-popup';
popup.innerHTML = `
<p><b>${Localization.getMessage(Config.MESSAGE_KEYS.UPDATE_TITLE)}</b></p>
<p>Current: ${Config.VERSIONS.MARKED_VERSION}<br>Latest: ${latest}</p>
<button>${Localization.getMessage(Config.MESSAGE_KEYS.UPDATE_NOW)}</button>
`;
popup.querySelector('button').onclick = () => {
localStorage.setItem(Config.STORAGE_KEYS.LAST_NOTIFIED, latest);
popup.remove();
};
document.body.appendChild(popup);
}
} catch (e) {
console.warn('marked.min.js 버전 확인 오류:', e.message);
}
},
onerror: () => console.warn('marked.min.js 버전 확인 요청 실패')
});
}
};
// 이벤트 핸들러 모듈: URL 및 DOM 변경 감지
const EventHandler = {
observeUrlChange(onChangeCallback) {
let lastUrl = location.href;
const checkUrlChange = () => {
if (location.href !== lastUrl) {
lastUrl = location.href;
onChangeCallback();
}
};
const originalPushState = history.pushState;
history.pushState = function (...args) {
originalPushState.apply(this, args);
checkUrlChange();
};
const originalReplaceState = history.replaceState;
history.replaceState = function (...args) {
originalReplaceState.apply(this, args);
checkUrlChange();
};
window.addEventListener('popstate', checkUrlChange);
const observer = new MutationObserver(checkUrlChange);
const targetNode = document.querySelector('head > title') || document.body;
observer.observe(targetNode, { childList: true, subtree: true });
}
};
// 렌더링 상태 관리 모듈: UI 렌더링 상태 관리
const RenderState = {
isRendering: false,
geminiBoxExists: false,
startRendering() {
if (this.isRendering) return false;
this.isRendering = true;
return true;
},
finishRendering() {
this.isRendering = false;
},
maintainGeminiBoxPosition(wrapper) {
const existingGeminiWrapper = document.getElementById('gemini-wrapper');
if (existingGeminiWrapper) {
existingGeminiWrapper.remove();
}
if (DeviceDetector.isGoogle()) {
UI.createRHSIfNeeded();
const rhsTarget = document.getElementById('rhs');
if (rhsTarget) {
rhsTarget.prepend(wrapper);
this.geminiBoxExists = true;
} else {
console.warn('Google: #rhs 요소가 생성 시도 후에도 발견되지 않았습니다.');
this.geminiBoxExists = false;
}
} else if (DeviceDetector.isBing()) {
const bingContextTarget = document.getElementById('b_context') ||
document.querySelector('.b_right');
if (bingContextTarget) {
bingContextTarget.prepend(wrapper);
this.geminiBoxExists = true;
} else {
console.warn('Bing: #b_context 또는 .b_right 요소가 발견되지 않았습니다.');
this.geminiBoxExists = false;
}
}
}
};
// UI 렌더링 모듈: UI 렌더링 로직
const UIRenderer = {
renderDesktop(query, apiKey) {
const wrapper = UI.createGeminiUI(query, apiKey);
RenderState.maintainGeminiBoxPosition(wrapper);
if (RenderState.geminiBoxExists) {
window.requestIdleCallback(() => {
const content = wrapper.querySelector('#gemini-content');
if (content) {
const cache = sessionStorage.getItem(`${Config.CACHE.PREFIX}${query}`);
if (cache) {
content.innerHTML = marked.parse(cache);
} else {
window.requestIdleCallback(() => GeminiAPI.fetch(query, content, apiKey));
}
}
RenderState.finishRendering();
});
return true;
}
RenderState.finishRendering();
return false;
},
renderMobile(query) {
const contentTarget = document.getElementById('b_content') ||
document.getElementById('main');
if (!contentTarget) {
RenderState.finishRendering();
return false;
}
requestAnimationFrame(() => {
const searchBtn = UI.createSearchButton(query);
if (contentTarget.parentNode) {
contentTarget.parentNode.style.overflow = 'visible';
contentTarget.parentNode.style.position = 'relative';
contentTarget.parentNode.insertBefore(searchBtn, contentTarget);
} else {
document.body.prepend(searchBtn);
}
RenderState.finishRendering();
});
return true;
},
renderTablet() {
RenderState.finishRendering();
return true;
},
render() {
if (!RenderState.startRendering()) return;
const query = Utils.getQuery();
if (!query) {
RenderState.finishRendering();
return;
}
UI.removeExistingElements();
const deviceType = DeviceDetector.getDeviceType();
if (deviceType === 'desktop') {
const apiKey = Utils.getApiKey();
if (!apiKey) {
RenderState.finishRendering();
return;
}
this.renderDesktop(query, apiKey);
} else if (deviceType === 'mobile') {
this.renderMobile(query);
} else if (deviceType === 'tablet') {
this.renderTablet();
} else {
RenderState.finishRendering();
}
}
};
// 초기화 모듈: 스크립트 초기화
const Initializer = {
init() {
const initialize = () => {
ThemeManager.init(); // 테마 관리 초기화
Styles.initStyles();
LinkCleaner.convertLinksToReal(document);
const checkAndRender = () => {
const targetElement = document.getElementById('rhs') ||
document.getElementById('b_context') || document.querySelector('.b_right');
if (targetElement || DeviceDetector.isMobile() || DeviceDetector.isTablet()) {
UIRenderer.render();
} else {
if (DeviceDetector.isGoogle()) {
UI.createRHSIfNeeded();
setTimeout(checkAndRender, 100);
} else {
setTimeout(checkAndRender, 100);
}
}
};
checkAndRender();
EventHandler.observeUrlChange(() => {
UIRenderer.render();
LinkCleaner.convertLinksToReal(document);
});
ThemeManager.observeThemeChange();
};
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(initialize, 1);
} else {
document.addEventListener('DOMContentLoaded', initialize);
}
}
};
// 스크립트 실행
Initializer.init();
})();