Created
October 19, 2025 10:04
-
-
Save lunamoth/7fcefe5ccd97240ce3a2efe35de7629e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name 모든 사이트 우클릭/드래그/선택 차단 해제 | |
| // @name:en Enable Right-Click, Drag, and Select for All Sites | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.5 | |
| // @description 웹사이트에서 금지된 마우스 오른쪽 클릭, 드래그, 텍스트 선택을 여러 기법을 통해 종합적으로 해제합니다. | |
| // @description:en Comprehensive script to re-enable right-click, drag, and text selection on websites using multiple techniques. | |
| // @author Gemini | |
| // @match *://*/* | |
| // @grant GM_addStyle | |
| // @run-at document-start | |
| // @license MIT | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // 1. CSS를 이용한 선택/드래그 방지 무력화 (가장 먼저 실행) | |
| // !important를 사용하여 다른 스타일 규칙을 덮어씁니다. | |
| GM_addStyle(` | |
| *, html, body, div, p, span, a { | |
| -webkit-user-select: auto !important; | |
| -moz-user-select: auto !important; | |
| -ms-user-select: auto !important; | |
| user-select: auto !important; | |
| -webkit-user-drag: auto !important; | |
| -moz-user-drag: auto !important; | |
| -ms-user-drag: auto !important; | |
| user-drag: auto !important; | |
| touch-action: auto !important; | |
| } | |
| `); | |
| // 2. 이벤트 리스너 차단 (가장 핵심적인 부분) | |
| // 이벤트의 '캡처링' 단계에서 먼저 실행하여 후속 이벤트를 중단시킵니다. | |
| const eventsToStop = [ | |
| 'contextmenu', // 우클릭 메뉴 | |
| 'selectstart', // 텍스트 선택 시작 | |
| 'dragstart', // 드래그 시작 | |
| 'mousedown', // 마우스 버튼 누르기 (일부 사이트는 이걸로 선택 막음) | |
| 'keydown', // 키보드 입력 (F12, Ctrl+Shift+I 등 개발자 도구 방지) | |
| 'copy', // 복사 | |
| 'cut', // 잘라내기 | |
| 'paste' // 붙여넣기 | |
| ]; | |
| // 키보드 이벤트의 경우, 특정 키만 차단하는 로직을 무력화하기 위함 | |
| // 예를 들어, event.ctrlKey && event.keyCode == 'C' 와 같은 조건을 막습니다. | |
| const keyEventsToAllow = ['keydown', 'keypress', 'keyup']; | |
| const stopPropagation = e => { | |
| e.stopImmediatePropagation(); | |
| }; | |
| const allowEvent = e => { | |
| // 일부 사이트는 이벤트를 아예 막는 대신, 기본 동작을 막습니다. | |
| // 이 경우, stopImmediatePropagation()만으로는 부족할 수 있어, | |
| // 이벤트 전파를 허용하되, 기본 동작 방지를 막는 함수를 재정의합니다. | |
| // 이것은 매우 강력하지만, 일부 사이트 기능을 망가뜨릴 수 있어 주석 처리합니다. | |
| // 필요한 경우 주석을 해제하여 사용하세요. | |
| /* | |
| if (typeof e.preventDefault.isHijacked === 'undefined') { | |
| const originalPreventDefault = e.preventDefault; | |
| e.preventDefault = function() { | |
| console.log('preventDefault() is blocked by userscript.'); | |
| }; | |
| e.preventDefault.isHijacked = true; | |
| } | |
| */ | |
| return true; | |
| }; | |
| eventsToStop.forEach(eventName => { | |
| window.addEventListener(eventName, stopPropagation, true); // 캡처링 단계에서 실행 | |
| }); | |
| keyEventsToAllow.forEach(eventName => { | |
| window.addEventListener(eventName, allowEvent, true); | |
| }); | |
| // 3. HTML 인라인 속성 및 자바스크립트 속성 제거 | |
| // 페이지 로딩 후 및 동적으로 생성되는 요소에 대해 주기적으로 검사 및 제거합니다. | |
| const cleanupAttributes = (targetNode = document) => { | |
| const elements = targetNode.querySelectorAll('*'); | |
| const attributesToRemove = [ | |
| 'oncontextmenu', 'onselectstart', 'ondragstart', 'onmousedown', | |
| 'onkeydown', 'oncopy', 'oncut' | |
| ]; | |
| elements.forEach(el => { | |
| attributesToRemove.forEach(attr => { | |
| if (el.hasAttribute(attr)) { | |
| el.removeAttribute(attr); | |
| } | |
| // JavaScript로 직접 할당된 속성도 null로 만듭니다. | |
| if (el[attr]) { | |
| el[attr] = null; | |
| } | |
| }); | |
| // 일부 라이브러리는 style 속성으로 직접 user-select를 설정하기도 합니다. | |
| if (el.style.userSelect === 'none') { | |
| el.style.userSelect = 'auto'; | |
| } | |
| }); | |
| }; | |
| // 최초 페이지 로딩 시 실행 | |
| window.addEventListener('DOMContentLoaded', () => { | |
| cleanupAttributes(); | |
| }); | |
| // 4. 동적으로 추가되는 콘텐츠에 대한 처리 (MutationObserver) | |
| // AJAX나 SPA(Single Page Application) 환경에서 새로 생긴 요소도 처리합니다. | |
| const observer = new MutationObserver(mutations => { | |
| mutations.forEach(mutation => { | |
| mutation.addedNodes.forEach(node => { | |
| if (node.nodeType === 1) { // ELEMENT_NODE | |
| cleanupAttributes(node); | |
| } | |
| }); | |
| }); | |
| }); | |
| observer.observe(document.documentElement, { | |
| childList: true, // 자식 노드의 추가/제거 감지 | |
| subtree: true // 모든 하위 노드 감지 | |
| }); | |
| // 5. 일부 사이트에서 사용하는 이벤트 리스너 덮어쓰기 방지 (고급 기법) | |
| // EventTarget.prototype.addEventListener를 재정의하여 특정 이벤트 리스너 등록을 막습니다. | |
| const originalAddEventListener = EventTarget.prototype.addEventListener; | |
| EventTarget.prototype.addEventListener = function(type, listener, options) { | |
| if (eventsToStop.includes(type)) { | |
| console.log(`[Userscript] Blocked an attempt to add a '${type}' event listener.`); | |
| return; // 차단하려는 이벤트 리스너의 등록을 아예 막아버림 | |
| } | |
| originalAddEventListener.call(this, type, listener, options); | |
| }; | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment