피싱 링크 차단기

피싱 링크 차단기 Alt + Shift + P 단축키로 리스트 저장 / 로드 / 초기화 가능. (특정 주소만 따로 제거하려면 스크립트 대시보드 → 스크립트 수정 → 값 탭에서 따로 삭제)

// ==UserScript==
// @name         피싱 링크 차단기
// @namespace    mickey90427
// @version      0.1
// @description  피싱 링크 차단기 Alt + Shift + P 단축키로 리스트 저장 / 로드 / 초기화 가능. (특정 주소만 따로 제거하려면 스크립트 대시보드 → 스크립트 수정 → 값 탭에서 따로 삭제)
// @icon         https://raw.githubusercontent.com/githubkorean/Link-Blocker/refs/heads/main/icon.png
// @author       You
// @match        *://*/*
// @supportURL   https://github.com/githubkorean/Link-Blocker
// @homepageURL  https://github.com/githubkorean/Link-Blocker
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// @license      MIT
// ==/UserScript==

(async function () {
    'use strict';

    const STORAGE_KEY = 'phishingBlockedHosts';

    // 저장된 차단된 호스트 목록 가져오기
    const getBlockedHosts = async () => {
        const stored = await GM_getValue(STORAGE_KEY, '[]');
        return JSON.parse(stored);
    };

    // 차단된 호스트 목록에 추가
    const addBlockedHost = async host => {
        const list = await getBlockedHosts();
        if (!list.includes(host)) {
            list.push(host);
            await GM_setValue(STORAGE_KEY, JSON.stringify(list));
            updateLinks(); // 차단이 적용되자마자 바로 링크 업데이트
        }
    };

    // 모든 링크를 검사하고, 차단된 도메인 처리
    const updateLinks = async () => {
        const links = document.querySelectorAll('a[href]');
        const blockedHosts = await getBlockedHosts();

        links.forEach(link => {
            const href = link.getAttribute('href');
            if (!href.includes('@')) return;

            try {
                const actualHost = href.split('@').pop().split('/')[0];
                const userinfo = new URL(href, window.location.href).username;
                const isSuspect = userinfo || href.match(/https?:\/\/[^\/]+@/);

                if (isSuspect) {
                    if (blockedHosts.includes(actualHost)) {
                        const replacement = document.createElement('span');
                        replacement.textContent = '[차단된 URL]';
                        replacement.style.color = 'red';
                        link.replaceWith(replacement);
                        return;
                    }

                    link.style.backgroundColor = '#fff3cd';
                    link.style.border = '1px solid #ffa500';
                    link.title = '⚠️ 의심 링크 - 클릭 시 확인 요청됨';

                    link.addEventListener('click', async function (e) {
                        e.preventDefault();

                        const confirmed = confirm(
                            `⚠️ 해당 사이트는 피싱 사이트 일 수 있습니다. 이동하시겠습니까?\n\n실제 연결 도메인: ${actualHost}`
                        );

                        if (confirmed) {
                            window.location.href = href;
                        } else {
                            const block = confirm(`도메인 ${actualHost}를 차단 목록에 추가하시겠습니까?`);
                            if (block) {
                                await addBlockedHost(actualHost);
                                alert(`✅ 차단됨: ${actualHost}`);
                            }
                        }
                    });
                }
            } catch (err) {
                // Invalid URL
            }
        });
    };

    // 저장된 차단된 도메인 목록을 세이브하기
    const saveBlockedHosts = async () => {
        const list = await getBlockedHosts();
        const json = JSON.stringify(list);
        const blob = new Blob([json], { type: 'application/json' });
        const url = URL.createObjectURL(blob);

        const a = document.createElement('a');
        a.href = url;
        a.download = 'blocked_hosts.json';
        a.click();
        URL.revokeObjectURL(url);
        GM_notification("차단된 도메인 목록이 'blocked_hosts.json' 파일로 저장되었습니다.");
    };

    // JSON 파일로부터 차단된 호스트 목록 로드
    const loadBlockedHosts = async (file) => {
        const reader = new FileReader();
        reader.onload = async function (event) {
            try {
                const list = JSON.parse(event.target.result);
                await GM_setValue(STORAGE_KEY, JSON.stringify(list));
                GM_notification("차단된 도메인 목록이 로드되었습니다.");
                updateLinks(); // 실시간으로 적용
            } catch (err) {
                GM_notification("로드된 파일에 문제가 있습니다.");
            }
        };
        reader.readAsText(file);
    };

    // 차단된 도메인 목록 초기화
    const resetBlockedHosts = async () => {
        await GM_setValue(STORAGE_KEY, '[]');
        GM_notification("차단된 도메인 목록이 초기화되었습니다.");
        location.reload(); // 페이지 새로고침
    };

    // Shift + Alt + P 눌렀을 때 세이브/로드/초기화 처리
    window.addEventListener('keydown', async (e) => {
        if (e.shiftKey && e.altKey && e.key === 'P') {
            const action = prompt("세이브(1), 로드(2), 초기화(3)를 선택하세요: ");
            if (action === '1') {
                // 세이브
                await saveBlockedHosts();
            } else if (action === '2') {
                // 로드
                const input = document.createElement('input');
                input.type = 'file';
                input.accept = '.json';
                input.onchange = (e) => {
                    const file = e.target.files[0];
                    if (file) {
                        loadBlockedHosts(file);
                    }
                };
                input.click();
            } else if (action === '3') {
                // 초기화
                const confirmReset = confirm("차단된 도메인 목록을 초기화하시겠습니까?");
                if (confirmReset) {
                    await resetBlockedHosts();
                }
            } else {
                alert("잘못된 입력입니다.");
            }
        }
    });

    // 초기 링크 처리
    updateLinks();

})();