FOFA IP and Domain Extractor (Dark Tabbed) - Auto Refresh

Extract IP:port and domains with dark themed tabbed interface, auto-refresh on pagination

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         FOFA IP and Domain Extractor (Dark Tabbed) - Auto Refresh
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Extract IP:port and domains with dark themed tabbed interface, auto-refresh on pagination
// @author       zephyrus
// @match        https://fofa.info/result*
// @match        https://*.fofa.info/result*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_notification
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Add custom CSS styles (same as before)
    GM_addStyle(`
        .fofa-extractor-container {
            margin: 20px auto;
            font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
            width: 450px;
        }
        .fofa-tab-container {
            display: flex;
            flex-direction: column;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            background: #2d2d2d;
        }
        .fofa-tab-header {
            display: flex;
            background: #1e1e1e;
            border-bottom: 1px solid #3a3a3a;
            justify-content: space-between;
            align-items: center;
            padding-right: 15px;
        }
        .fofa-tabs {
            display: flex;
        }
        .fofa-tab {
            padding: 10px 20px;
            cursor: pointer;
            font-weight: 500;
            color: #aaa;
            transition: all 0.3s;
            border-right: 1px solid #3a3a3a;
            position: relative;
        }
        .fofa-tab:last-child {
            border-right: none;
        }
        .fofa-tab.active {
            color: #05f2f2;
            background: #2d2d2d;
        }
        .fofa-tab.active:after {
            content: '';
            position: absolute;
            bottom: -1px;
            left: 0;
            right: 0;
            height: 2px;
            background: #05f2f2;
        }
        .fofa-tab-badge {
            display: inline-block;
            padding: 2px 6px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            font-size: 12px;
            margin-left: 8px;
            color: #ddd;
        }
        .fofa-tab-content {
            display: none;
            background: #2d2d2d;
            padding: 15px;
        }
        .fofa-tab-content.active {
            display: block;
        }
        .fofa-content-box {
            max-height: 400px;
            overflow-y: auto;
            padding: 12px;
            background: #252525;
            border-radius: 4px;
            line-height: 1.6;
            white-space: pre;
            font-size: 13px;
            color: #e0e0e0;
            border: 1px solid #3a3a3a;
            font-family: 'Consolas', 'Monaco', monospace;
        }
        .fofa-toolbar {
            display: flex;
            align-items: center;
            color: #aaa;
            font-size: 13px;
            gap: 10px;
        }
        .fofa-copy-btn {
            padding: 6px 14px;
            background: #05f2f2;
            color: #111;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 13px;
            transition: all 0.2s;
            font-weight: 500;
        }
        .fofa-copy-btn:hover {
            background: #05e0e0;
        }
        .fofa-copy-btn.copied {
            background: #05a2a2;
            color: #fff;
        }
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        ::-webkit-scrollbar-track {
            background: #1e1e1e;
        }
        ::-webkit-scrollbar-thumb {
            background: #05f2f2;
            border-radius: 4px;
        }
        @media (max-width: 768px) {
            .fofa-extractor-container {
                width: 90%;
            }
            .fofa-tab-header {
                flex-wrap: wrap;
            }
            .fofa-tab {
                flex: 1 0 auto;
                text-align: center;
                padding: 8px 12px;
            }
        }
    `);

    // 主提取函数
    const extractData = () => {
        const items = Array.from(document.querySelectorAll(".hsxa-meta-data-item"));
        const domains = new Set();
        const ips = new Set();

        items.forEach(item => {
            // Extract domain with port
            const domainLink = item.querySelector(".hsxa-host a");
            const domain = domainLink?.textContent.trim().replace(/^https?:\/\//, "");
            const portElement = item.querySelector(".hsxa-port");
            const port = portElement ? atob(portElement.href.match(/qbase64=([^&]+)/)?.[1]).match(/port="([^"]+)"/)?.[1] : "";

            if (domain && port) {
                domains.add(`${domain}:${port}`);
            }

            // Extract IP with port
            const ipElement = item.querySelector(".hsxa-jump-a");
            const ip = ipElement ? atob(ipElement.href.match(/qbase64=([^&]+)/)?.[1]).match(/ip="([^"]+)"/)?.[1] : "";

            if (ip && port) {
                ips.add(`${ip}:${port}`);
            }
        });

        return {
            domains: Array.from(domains).sort(),
            ips: Array.from(ips).sort()
        };
    };

    // 创建标签页界面
    const createTabbedInterface = (ips, domains) => {
        const container = document.createElement('div');
        container.className = 'fofa-tab-container';

        // 创建标签头
        const tabHeader = document.createElement('div');
        tabHeader.className = 'fofa-tab-header';

        const tabsContainer = document.createElement('div');
        tabsContainer.className = 'fofa-tabs';

        const ipTab = document.createElement('div');
        ipTab.className = 'fofa-tab active';
        ipTab.dataset.tab = 'ip';
        ipTab.innerHTML = `IP列表 <span class="fofa-tab-badge">${ips.length}</span>`;

        const domainTab = document.createElement('div');
        domainTab.className = 'fofa-tab';
        domainTab.dataset.tab = 'domain';
        domainTab.innerHTML = `域名列表 <span class="fofa-tab-badge">${domains.length}</span>`;

        tabsContainer.appendChild(ipTab);
        tabsContainer.appendChild(domainTab);
        tabHeader.appendChild(tabsContainer);

        // 创建工具栏
        const toolbar = document.createElement('div');
        toolbar.className = 'fofa-toolbar';

        const countInfo = document.createElement('div');
        countInfo.textContent = `IP: ${ips.length} | 域名: ${domains.length}`;

        const copyBtn = document.createElement('button');
        copyBtn.className = 'fofa-copy-btn';
        copyBtn.textContent = '复制当前';
        copyBtn.onclick = () => {
            const activeTab = container.querySelector('.fofa-tab.active').dataset.tab;
            const content = activeTab === 'ip' ? ips.join('\n') : domains.join('\n');
            GM_setClipboard(content);
            copyBtn.textContent = '已复制!';
            copyBtn.classList.add('copied');
            setTimeout(() => {
                copyBtn.textContent = '复制当前';
                copyBtn.classList.remove('copied');
            }, 2000);
        };

        toolbar.appendChild(countInfo);
        toolbar.appendChild(copyBtn);
        tabHeader.appendChild(toolbar);

        container.appendChild(tabHeader);

        // 创建标签内容
        const contentContainer = document.createElement('div');
        contentContainer.className = 'fofa-tab-contents';

        // IP 标签内容
        const ipContent = document.createElement('div');
        ipContent.className = 'fofa-tab-content active';
        ipContent.id = 'ip-tab';

        const ipContentBox = document.createElement('div');
        ipContentBox.className = 'fofa-content-box';
        ipContentBox.textContent = ips.join('\n');
        ipContent.appendChild(ipContentBox);

        // 域名标签内容
        const domainContent = document.createElement('div');
        domainContent.className = 'fofa-tab-content';
        domainContent.id = 'domain-tab';

        const domainContentBox = document.createElement('div');
        domainContentBox.className = 'fofa-content-box';
        domainContentBox.textContent = domains.join('\n');
        domainContent.appendChild(domainContentBox);

        contentContainer.appendChild(ipContent);
        contentContainer.appendChild(domainContent);
        container.appendChild(contentContainer);

        // 添加标签切换功能
        const tabs = container.querySelectorAll('.fofa-tab');
        tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                // 更新活动标签
                tabs.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');

                // 更新活动内容
                const tabName = tab.dataset.tab;
                container.querySelectorAll('.fofa-tab-content').forEach(content => {
                    content.classList.remove('active');
                });
                container.querySelector(`#${tabName}-tab`).classList.add('active');
            });
        });

        return container;
    };

    // 插入结果到页面
    const insertResults = () => {
        // 检查是否已经插入
        let extractorContainer = document.getElementById('fofa-extractor-container');

        // 如果结果列表不存在,移除提取器(如果有)
        if (!document.querySelector('.hsxa-meta-data-item')) {
            if (extractorContainer) {
                extractorContainer.remove();
            }
            return;
        }

        const { domains, ips } = extractData();

        // 如果没有数据,也移除提取器
        if (domains.length === 0 && ips.length === 0) {
            if (extractorContainer) {
                extractorContainer.remove();
            }
            return;
        }

        const targetElement = document.querySelector('div.contentContainer.resultIndex > div:nth-child(1) > div.relatedSearch');
        if (!targetElement) return;

        // 如果提取器已存在,更新内容
        if (extractorContainer) {
            const newContainer = createTabbedInterface(ips, domains);
            extractorContainer.replaceWith(newContainer);
            extractorContainer = newContainer;
            extractorContainer.id = 'fofa-extractor-container';
        } else {
            // 否则创建新的提取器
            extractorContainer = document.createElement('div');
            extractorContainer.id = 'fofa-extractor-container';
            extractorContainer.className = 'fofa-extractor-container';
            extractorContainer.appendChild(createTabbedInterface(ips, domains));
            targetElement.parentNode.insertBefore(extractorContainer, targetElement);
        }
    };

    // 使用 MutationObserver 监听结果列表的变化
    const observeResults = () => {
        const resultsContainer = document.querySelector('.hsxa-meta-table-list');
        if (!resultsContainer) {
            // 如果结果容器不存在,稍后重试
            setTimeout(observeResults, 500);
            return;
        }

        const observer = new MutationObserver((mutations) => {
            // 检查是否有子节点变化或属性变化(翻页时可能会重置整个容器)
            const needsUpdate = mutations.some(mutation =>
                mutation.type === 'childList' ||
                (mutation.type === 'attributes' && mutation.attributeName === 'class')
            );

            if (needsUpdate) {
                // 延迟执行以确保DOM完全更新
                setTimeout(insertResults, 300);
            }
        });

        // 开始观察结果容器
        observer.observe(resultsContainer, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class']
        });

        // 初始执行一次
        insertResults();
    };

    // 页面加载完成后开始观察
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(observeResults, 500);
    } else {
        window.addEventListener('DOMContentLoaded', () => {
            setTimeout(observeResults, 500);
        });
    }

    // 添加一个按钮点击事件监听器,因为FOFA可能在点击分页按钮时使用AJAX
    document.addEventListener('click', (e) => {
        if (e.target.closest('.ant-pagination-item, .ant-pagination-prev, .ant-pagination-next')) {
            // 分页按钮被点击,稍后检查更新
            setTimeout(insertResults, 1000);
        }
    });
})();