Hunter view

Send the current website to Hunter

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Hunter view
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Send the current website to Hunter
// @author       0cat
// @match        http://*/*
// @match        https://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *
// @license MIT
// ==/UserScript==

GM_registerMenuCommand("Hunter Login", openLoginForm, "l");
GM_registerMenuCommand("Hunter 识别结果", HunterFind, "s");

// 创建自定义输入表单
function openLoginForm() {
    const body = document.getElementsByTagName('body')[0];
    const loginDiv = document.createElement('div');
    loginDiv.innerHTML = `
    <div style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); background-color:white; padding:20px; border-radius:10px; box-shadow:0 0 10px rgba(0, 0, 0, 0.5); z-index:100000;">
        <h3>Login to Hunter</h3>
        <label>Username (一般是手机号):
            <input type="text" id="hunterUsername" style="width:100%;" placeholder="e.g., 1234567890" />
        </label><br/><br/>
        <label>Hunter ApiKey:
            <input type="password" id="hunterKey" style="width:100%;" />
        </label><br/><br/>
        <button id="loginSubmit">Submit</button>
        <button id="loginCancel">Cancel</button>
    </div>
    `;
    body.appendChild(loginDiv);

    document.getElementById('loginSubmit').onclick = function() {
        const username = document.getElementById('hunterUsername').value;
        const HunterKey = document.getElementById('hunterKey').value;
        if (username && HunterKey) {
            GM_setValue("username", username);
            GM_setValue("HunterKey", HunterKey);
            alert("Login successful!");
            body.removeChild(loginDiv);  // Remove form after login
        } else {
            alert("Please fill in both fields.");
        }
    };

    document.getElementById('loginCancel').onclick = function() {
        body.removeChild(loginDiv);
    };
}

function HunterFind() {
    var username = GM_getValue("username");
    var HunterKey = GM_getValue("HunterKey");

    if (!username || !HunterKey) {
        console.error("No username or HunterKey found");
        return;
    }

    const body = document.getElementsByTagName('body')[0];
    const div = document.createElement('div');
    div.innerHTML = `<div style="font-size:14px;color:rgba(0,0,0,0.65);box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1) ;background-color: #fff;border-radius: 5px;border: 1px solid #ebebeb;right:20px;top:20px;position: fixed;z-index: 1000000">
        <div style="display: flex;justify-content: flex-end;margin-right: 8px;">
          <div id="expandBtn" style="color:rgb(24, 36, 127);cursor: pointer;margin-left: 8px;margin-top: 4px;font-size: 12px;margin-bottom: 4px;display: none">Hunter展开</div>
          <div id="hideBtn" style="cursor: pointer;font-size: 12px;margin-top: 2px;color:rgb(96, 98, 102);">隐藏</div>
        </div>
        <div id="contentDiv" class="plugs_content" style="padding-bottom: 10px;">
          <div style="margin-left: 4px;border-radius: 2px;display: inline-block;padding: 8px;padding-right: 40px;">
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">RDAP_IP_CIDR:</div>
              <div class="rdap_cidr" style="word-wrap:break-word;word-break:normal;overflow: hidden;">N/A</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">RDAP_描述:</div>
              <div class="rdap_description" style="word-wrap:break-word;word-break:normal;overflow: hidden;">N/A</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">地区:</div>
              <div class="area" style="word-wrap:break-word;word-break:normal;overflow: hidden;">null</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">运营商和org:</div>
              <div class="org" style="word-wrap:break-word;word-break:normal;overflow: hidden;">null</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">ICP:</div>
              <div class="icpnumber" style="word-wrap:break-word;word-break:normal;overflow: hidden;">null</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">协议:</div>
              <div class="protocol" style="word-wrap:break-word;word-break:normal;overflow: hidden;">null</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">端口:</div>
              <div class="port">null</div>
            </div>
            <div style="margin-bottom: 4px;display: flex">
              <div style="margin-right: 6px;white-space: nowrap">剩余积分:</div>
              <div class="surplus">null</div>
            </div>
          </div>
        <div class="copy-text-data" style="cursor: pointer;display: flex;margin-top: 8px;border-top:1px solid #ebebeb;justify-content: flex-end;padding-right: 4px;">
              复制
        </div>
          <ul class="demo1" style="width: 100%;max-height: 350px;max-width: 600px; overflow: auto;">
          <div style="display: flex;margin-top: 8px;">
            <div style="padding: 0 10px;">
              <div class="table_title">null</div>
            </div>
            <div style="padding: 0 10px;">
              <div class="table_protocol">null</div>
            </div>
            <div style="padding: 0 10px;">
              <div class="table_port">null</div>
            </div>
            <div style="padding: 0 10px;">
              <div class="table_code">null</div>
            </div>
            <div style="padding: 0 10px;">
              <div class="table_url">
                <div>
                  <a href="" class="url1" style="color: #1890ff;text-decoration: none">url</a>
                </div>
              </div>
            </div>
          </div>
          </ul>
        </div>
      </div>`;
    body.appendChild(div);
    // 隐藏/展开功能
    const hideBtn = document.getElementById('hideBtn');
    const expandBtn = document.getElementById('expandBtn');
    const contentDiv = document.getElementById('contentDiv');

    hideBtn.onclick = function() {
        contentDiv.style.display = 'none';
        hideBtn.style.display = 'none';
        expandBtn.style.display = 'block';
    };

    expandBtn.onclick = function() {
        contentDiv.style.display = 'block';
        hideBtn.style.display = 'block';
        expandBtn.style.display = 'none';
    };

    var target = window.location.hostname;

    var isValidIP_reg = /(\d{1,3}\.){3}\d{1,3}/;

    // 如果是 IP,则执行 RDAP 查询
    if (isValidIP_reg.test(target)) {
        // 发起 RDAP 请求
        var rdap_url = `https://rdap.apnic.net/ip/${target}`;
        GM_xmlhttpRequest({
            method: "GET",
            url: rdap_url,
            onload: function(xhr) {
                if (xhr.status !== 200) {
                    console.error("RDAP Request failed, status code:", xhr.status);
                    return;
                }
                const rdap_res = JSON.parse(xhr.responseText);

                // 提取 CIDR 和描述
                const handle = rdap_res.handle;
                const cidr = rdap_res.cidr0_cidrs[0].v4prefix + "/" + rdap_res.cidr0_cidrs[0].length;
                const description = rdap_res.remarks[0].description[0] || 'N/A';

                // 将 CIDR 和描述信息填充到小卡片中
                const rdap_cidr_element = document.getElementsByClassName('rdap_cidr')[0];
                const rdap_description_element = document.getElementsByClassName('rdap_description')[0];
                rdap_cidr_element.textContent = cidr || 'N/A';
                rdap_description_element.textContent = description || 'N/A';

            },
            onerror: function(xhr) {
                console.error("RDAP Request error:", xhr);
            }
        });
    }

    var Hunter_url = "https://hunter.qianxin.com/openApi/search?username=" + username + "&api-key=" + HunterKey + "&page=1&page_size=50&is_web=3&start_time=" + getNewDate("before", 6) + "&end_time=" + getNewDate() + "&search=";

    var search, url;

    if (isValidIP_reg.test(target)) {
        search = btoa('ip=="' + target + '"');
        url = Hunter_url + search;
    } else {
        search = btoa('domain=="' + target + '"');
        url = Hunter_url + search;
    }

    if (!url) {
        console.error("URL generation failed.");
        return;
    }

    // 发起 Hunter 查询请求
    try {
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function(xhr) {
                if (xhr.status !== 200) {
                    console.error("Hunter Request failed, status code:", xhr.status);
                    return;
                }
                const res = JSON.parse(xhr.responseText);

                const target_data = res.data?.arr || [];
                if (target_data.length === 0) {
                    return;
                }

                // 获取 HTML 元素
                const area = document.getElementsByClassName('area')[0];
                const org = document.getElementsByClassName('org')[0];
                const protocol = document.getElementsByClassName('protocol')[0];
                const port = document.getElementsByClassName('port')[0];
                const surplus = document.getElementsByClassName('surplus')[0];
                const icpnumber = document.getElementsByClassName('icpnumber')[0];

                const table_title = document.getElementsByClassName('table_title')[0];
                const table_protocol = document.getElementsByClassName('table_protocol')[0];
                const table_port = document.getElementsByClassName('table_port')[0];
                const table_code = document.getElementsByClassName('table_code')[0];
                const table_url = document.getElementsByClassName('table_url')[0];

                // 遍历数据并展示到页面
                let title_innerHTML = `<div style='white-space: nowrap'>标题</div>`;
                let protocol_innerHTML = `<div style='white-space: nowrap'>协议</div>`;
                let port_innerHTML = `<div style='white-space: nowrap'>端口</div>`;
                let code_innerHTML = `<div style='white-space: nowrap'>状态码</div>`;
                let url_innerHTML = `<div style='white-space: nowrap'>url</div>`;

                target_data.forEach(item => {
                    title_innerHTML += `<div style="white-space: nowrap;height: 20px">${item.web_title || 'N/A'}</div>`;
                    protocol_innerHTML += `<div style="white-space: nowrap;height: 20px">${item.protocol || 'N/A'}</div>`;
                    code_innerHTML += `<div style="white-space: nowrap;height: 20px">${item.status_code || 'N/A'}</div>`;
                    url_innerHTML += `<div style="white-space: nowrap;height: 20px"><a href="${item.url}" target='_blank' style="color: #1890ff;text-decoration: none">${item.url}</a></div>`;
                    port_innerHTML += `<div style="white-space: nowrap;height: 20px">${item.port || 'N/A'}</div>`;
                });

                table_title.innerHTML = title_innerHTML;
                table_protocol.innerHTML = protocol_innerHTML;
                table_code.innerHTML = code_innerHTML;
                table_port.innerHTML = port_innerHTML;
                table_url.innerHTML = url_innerHTML;

                const target_location = target_data[0] || {};
                area.textContent = [target_location.country || '', target_location.province || '', target_location.city || ''].filter(item => item).join('-') || 'N/A';
                org.textContent = Array.from(new Set(target_data.filter(item => item.as_org).map(item => item.as_org))).join(',') || 'N/A';
                icpnumber.textContent = target_data[0].number || 'N/A';
                protocol.textContent = Array.from(new Set(target_data.filter(item => item.protocol).map(item => item.protocol))).join(',') || 'N/A';
                port.textContent = Array.from(new Set(target_data.filter(item => item.port).map(item => item.port))).join(',') || 'N/A';
                surplus.textContent = res.data.rest_quota?.split(':')[1] || 'N/A';

            },
            onerror: function(xhr) {
                console.error("Hunter Request error:", xhr);
            }
        });
    } catch (e) {
        console.error("Error while sending request:", e);
    }
}

function getNewDate(flag, many) {
    const thirtyDays = [4, 6, 9, 11];
    const thirtyOneDays = [1, 3, 5, 7, 8, 10, 12];
    const currDate = new Date();
    const year = currDate.getFullYear();
    let month = currDate.getMonth() + 1;
    let countDays = 0;
    let targetDateMilli = 0;
    let GMTDate = '';
    let targetYear = '';
    let targetMonth = '';
    let targetDate = '';
    let dealDate = '';
    let hh = currDate.getHours() < 10 ? "0" + currDate.getHours() : currDate.getHours();
    let mm = currDate.getMinutes() < 10 ? "0" + currDate.getMinutes() : currDate.getMinutes();
    let ss = currDate.getSeconds() < 10 ? "0" + currDate.getSeconds() : currDate.getSeconds();

    const isLeapYear = !!((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);

    for (let i = 0; i < many; i++) {
        if (flag === 'before') {
            month = month - 1 <= 0 ? 12 : month - 1;
        } else {
            month = month + 1 > 12 ? 1 : month + 1;
        }
        thirtyDays.includes(month) ? (countDays += 30) : thirtyOneDays.includes(month) ? (countDays += 31) : isLeapYear ? (countDays += 29) : (countDays += 28);
    }

    targetDateMilli = currDate.setDate(currDate.getDate() - (flag === 'before' ? countDays : countDays * -1));
    GMTDate = new Date(targetDateMilli);
    targetYear = GMTDate.getFullYear();
    targetMonth = GMTDate.getMonth() + 1;
    targetDate = GMTDate.getDate();
    targetMonth = targetMonth.toString().padStart(2, '0');
    targetDate = targetDate.toString().padStart(2, '0');
    dealDate = `${targetYear}-${targetMonth}-${targetDate} ${hh}:${mm}:${ss}`;
    return escape(dealDate);
}