Bypass Google Search Region

Add button to bypass Google Search region

当前为 2025-05-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Bypass Google Search Region
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.5
  5. // @description Add button to bypass Google Search region
  6. // @match *://*.google.com/search*
  7. // @match *://*.google.co.kr/search*
  8. // @match *://*.google.*/search*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const DESKTOP_ID = 'bypass-desktop-button';
  16. const MOBILE_ID = 'bypass-mobile-button';
  17. const ACTIVE_SAFE = 'off';
  18. const DEFAULT_GL = 'GH'; // 기본값: Ghana
  19.  
  20. // 지역 코드와 이름을 매핑한 객체 (알파벳 순으로 정렬)
  21. const REGION_OPTIONS = {
  22. 'CN': 'China',
  23. 'GH': 'Ghana',
  24. 'JP': 'Japan',
  25. 'KR': 'Korea',
  26. 'US': 'USA'
  27. };
  28.  
  29. // 페이지 언어를 확인 (ko, en만 체크)
  30. function getPageLanguage() {
  31. const lang = navigator.language || navigator.userLanguage;
  32. if (lang.startsWith('ko')) return 'ko';
  33. if (lang.startsWith('en')) return 'en';
  34. return null;
  35. }
  36.  
  37. // 쿠키 설정을 안정적으로 수행
  38. function ensureBypassCookie(gl) {
  39. const expectedValue = `gl=${gl}:safe=${ACTIVE_SAFE}`;
  40. const maxAge = 60 * 60 * 24 * 365 * 5; // 5년
  41.  
  42. const hasPrefCookie = document.cookie.includes(`PREF=${expectedValue}`);
  43. if (!hasPrefCookie) {
  44. const cookieValue = `PREF=${expectedValue}; max-age=${maxAge}; path=/`;
  45. document.cookie = `${cookieValue}; domain=.google.com`;
  46. document.cookie = `${cookieValue}; domain=${window.location.hostname}`;
  47. }
  48. }
  49.  
  50. // 우회 검색을 적용하는 함수
  51. function applyBypass(gl) {
  52. if (!gl) return;
  53.  
  54. const url = new URL(window.location.href);
  55. url.searchParams.set('gl', gl); // 선택한 국가 코드로 gl 파라미터 설정
  56. url.searchParams.set('safe', ACTIVE_SAFE);
  57. url.searchParams.set('pws', '0'); // 개인화된 검색 결과 비활성화
  58.  
  59. ensureBypassCookie(gl);
  60.  
  61. try {
  62. localStorage.setItem('selected_gl', gl); // 현재 선택한 국가
  63. sessionStorage.setItem('last_used_gl', gl); // 마지막 우회에 사용한 국가
  64. localStorage.setItem('google_settings_safe', ACTIVE_SAFE);
  65. } catch (e) {}
  66.  
  67. // 페이지 리디렉션
  68. window.location.href = url.toString();
  69. }
  70.  
  71. // 우회 검색을 해제하는 함수
  72. function clearBypass() {
  73. const url = new URL(window.location.href);
  74. url.searchParams.delete('gl');
  75. url.searchParams.delete('safe');
  76. url.searchParams.delete('pws');
  77.  
  78. // 쿠키와 로컬 스토리지에서 설정 삭제
  79. document.cookie = `PREF=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.google.com`;
  80. document.cookie = `PREF=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname}`;
  81.  
  82. try {
  83. localStorage.removeItem('selected_gl'); // 선택한 국가 초기화
  84. localStorage.removeItem('google_settings_safe');
  85. // last_used_gl은 유지 (sessionStorage)
  86. } catch (e) {}
  87.  
  88. // 페이지 리디렉션
  89. window.location.href = url.toString();
  90. }
  91.  
  92. // 현재 우회 상태를 확인하는 함수
  93. function isBypassActive() {
  94. const url = new URL(window.location.href);
  95. return url.searchParams.has('gl') && url.searchParams.get('safe') === ACTIVE_SAFE;
  96. }
  97.  
  98. // 우회 상태를 토글하는 함수
  99. function toggleBypass() {
  100. if (isBypassActive()) {
  101. clearBypass();
  102. } else {
  103. // 마지막 사용 gl을 가져오되, 없으면 기본값(GH) 사용
  104. let gl = sessionStorage.getItem('last_used_gl');
  105. if (!gl) {
  106. gl = DEFAULT_GL;
  107. }
  108. applyBypass(gl);
  109. }
  110. }
  111.  
  112. // 버튼 텍스트를 현재 언어 및 상태에 따라 업데이트
  113. function updateText(btn) {
  114. if (btn) {
  115. const lang = getPageLanguage();
  116. const bypassText = lang === 'ko' ? '우회 검색' : 'Bypass Search';
  117. const clearText = lang === 'ko' ? '우회 해제' : 'Remove Bypass';
  118. btn.textContent = isBypassActive() ? clearText : bypassText;
  119. }
  120. }
  121.  
  122. // 지역 선택 팝업 생성
  123. function createRegionPopup(triggerEl) {
  124. const existing = document.getElementById('gl-popup');
  125. if (existing) existing.remove();
  126.  
  127. const popup = document.createElement('div');
  128. popup.id = 'gl-popup';
  129. popup.style.position = 'absolute';
  130. popup.style.top = `${triggerEl.getBoundingClientRect().bottom + window.scrollY}px`;
  131. popup.style.left = `${triggerEl.getBoundingClientRect().left + window.scrollX}px`;
  132. popup.style.background = '#fff';
  133. popup.style.border = '1px solid #ccc';
  134. popup.style.zIndex = '9999';
  135. popup.style.fontSize = '14px';
  136. popup.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)';
  137.  
  138. const currentGl = localStorage.getItem('selected_gl');
  139.  
  140. Object.entries(REGION_OPTIONS).forEach(([code, name]) => {
  141. const item = document.createElement('div');
  142. item.textContent = name;
  143. item.style.padding = '6px 12px';
  144. item.style.cursor = 'pointer';
  145. item.style.fontWeight = (code === currentGl) ? 'bold' : 'normal'; // 현재 선택 강조
  146. item.onclick = () => {
  147. localStorage.setItem('selected_gl', code); // 선택된 지역 기억
  148. applyBypass(code); // 선택된 지역으로 우회 적용
  149. };
  150. popup.appendChild(item);
  151. });
  152.  
  153. document.body.appendChild(popup);
  154. document.addEventListener('click', function onClickOutside(e) {
  155. if (!popup.contains(e.target)) {
  156. popup.remove();
  157. document.removeEventListener('click', onClickOutside);
  158. }
  159. });
  160. }
  161.  
  162. // 데스크톱 버튼 추가
  163. function addDesktopButton() {
  164. const interval = setInterval(() => {
  165. const tool = document.querySelector('#hdtb-tls');
  166. if (tool && tool.parentElement && !document.getElementById(DESKTOP_ID)) {
  167. clearInterval(interval);
  168. const btn = document.createElement('div');
  169. btn.id = DESKTOP_ID;
  170. btn.className = 'hdtb-mitem';
  171. btn.style.cursor = 'pointer';
  172. btn.style.userSelect = 'none';
  173. updateText(btn); // 버튼 텍스트를 현재 언어에 맞게 업데이트
  174. btn.onclick = toggleBypass;
  175.  
  176. const arrow = document.createElement('div');
  177. arrow.className = 'hdtb-mitem';
  178. arrow.style.cursor = 'pointer';
  179. arrow.style.marginLeft = '6px';
  180. arrow.style.marginRight = '12px'; // 화살표와 버튼 사이에 여유 공간 추가
  181. arrow.textContent = '▼';
  182. arrow.onclick = (e) => {
  183. e.stopPropagation();
  184. createRegionPopup(arrow);
  185. };
  186.  
  187. tool.parentElement.insertBefore(arrow, tool.nextSibling);
  188. tool.parentElement.insertBefore(btn, arrow);
  189. }
  190. }, 500);
  191. }
  192.  
  193. // 모바일 버튼 추가
  194. function addMobileButton() {
  195. if (document.getElementById(MOBILE_ID)) return;
  196.  
  197. const lang = getPageLanguage();
  198. const label = lang === 'ko' ? '고급검색' : (lang === 'en' ? 'Advanced Search' : null);
  199. if (!label) return;
  200.  
  201. const advancedSearch = Array.from(document.querySelectorAll('a')).find(a => a.textContent.trim() === label);
  202. if (!advancedSearch || !advancedSearch.parentElement) return;
  203.  
  204. const clone = advancedSearch.parentElement.cloneNode(true);
  205. const link = clone.querySelector('a');
  206. if (!link) return;
  207.  
  208. clone.id = MOBILE_ID;
  209. updateText(link); // 버튼 텍스트를 현재 언어에 맞게 업데이트
  210. link.style.cursor = 'pointer';
  211. link.removeAttribute('href');
  212. link.onclick = toggleBypass;
  213.  
  214. const arrow = document.createElement('a');
  215. arrow.textContent = '▼';
  216. arrow.style.marginLeft = '6px';
  217. arrow.style.marginRight = '12px'; // 화살표와 버튼 사이에 여유 공간 추가
  218. arrow.style.cursor = 'pointer';
  219. arrow.onclick = (e) => {
  220. e.stopPropagation();
  221. createRegionPopup(arrow);
  222. };
  223.  
  224. advancedSearch.parentElement.insertAdjacentElement('afterend', clone);
  225. clone.insertAdjacentElement('afterend', arrow);
  226. }
  227.  
  228. // 쿠키 주기적 유지
  229. function keepBypassCookieAlive() {
  230. const gl = sessionStorage.getItem('last_used_gl');
  231. if (gl) ensureBypassCookie(gl);
  232. }
  233.  
  234. window.addEventListener('load', keepBypassCookieAlive);
  235. setInterval(keepBypassCookieAlive, 60 * 60 * 1000); // 1시간마다
  236.  
  237. // DOM 변화 감지하여 모바일 버튼 계속 감시
  238. const observer = new MutationObserver(() => {
  239. addMobileButton();
  240. });
  241. observer.observe(document.body, { childList: true, subtree: true });
  242.  
  243. // 초기 실행
  244. addDesktopButton();
  245. addMobileButton();
  246. })();