Last active
January 14, 2026 07:49
-
-
Save Baw-Appie/4b5564461479aa70f40b12b120dbd082 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 Minelist Hosting Provider Checker | |
| // @namespace http://tampermonkey.net/ | |
| // @version 0.2 | |
| // @description Displays detailed connection information for servers on Minelist. | |
| // @author You | |
| // @match https://minelist.kr/* | |
| // @match https://mine.page/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=minelist.kr | |
| // @require https://fastly.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js | |
| // @grant none | |
| // ==/UserScript== | |
| (() => { | |
| 'use strict'; | |
| const IP_REGEX = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/; | |
| /** | |
| * Observes a container for newly inserted elements and triggers a callback. | |
| * @param {string} containerSelector - The selector for the container to observe. | |
| * @param {string} elementSelector - The selector for the target elements to find within the container. | |
| * @param {(element: HTMLElement) => void} callback - The function to call for each new element found. | |
| */ | |
| const onElementInserted = (containerSelector, elementSelector, callback) => { | |
| const observer = new MutationObserver(mutations => { | |
| for (const mutation of mutations) { | |
| if (mutation.addedNodes.length) { | |
| $(mutation.addedNodes).find(elementSelector).each((i, el) => callback(el)); | |
| } | |
| } | |
| }); | |
| const target = $(containerSelector)[0]; | |
| if (target) { | |
| observer.observe(target, { childList: true, subtree: true }); | |
| } | |
| }; | |
| /** | |
| * Checks if a server address is blacklisted. | |
| * @param {string} serverAddress - The server address to check. | |
| * @returns {boolean} - Always returns false in this version. | |
| */ | |
| const isBlacklisted = (serverAddress) => false; | |
| /** | |
| * Checks server address is from known hosting service | |
| * @param {string} serverAddress - The server address to check. | |
| * @returns {object} hostingInfo - The hosting service information | |
| */ | |
| const getHosting = (serverAddress) => { | |
| const hostings = [ | |
| { name: "Stella IT", logo: { light: "https://static.stella-it-usercontent.com/logo/logo_black.svg", dark: "https://static.stella-it-usercontent.com/logo/logo_white.svg" }, ips: ["141.11.195."] }, | |
| { name: "SKH Systems", logo: { light: "https://iili.io/f8v9Kfj.png", dark: "https://iili.io/f8v9Kfj.png" }, ips: ["27.123.9.", "165.101.60.", "165.101.61."] }, | |
| { name: "MCV.KR", logo: { light: "https://iili.io/f8vCOx4.md.png", dark: "https://iili.io/f8vndzv.md.png" }, ips: ["172.104.117."] }, | |
| ] | |
| for (const hosting of hostings) { | |
| if ( | |
| Array.isArray(hosting.ips) && | |
| hosting.ips.some(prefix => serverAddress.startsWith(prefix)) | |
| ) { | |
| return hosting; | |
| } | |
| } | |
| return null | |
| } | |
| /** | |
| * Removes the trailing dot from a domain name if it exists. | |
| * @param {string} serverAddress - The address to clean. | |
| * @returns {string} - The cleaned address. | |
| */ | |
| const removeTrailingDot = (serverAddress) => serverAddress.endsWith('.') ? serverAddress.slice(0, -1) : serverAddress; | |
| /** | |
| * Queries the Google Public DNS API. | |
| * @param {string} name - The name to resolve. | |
| * @param {string} [type="A"] - The DNS record type to query. | |
| * @returns {Promise<object>} - The DNS query result as a JSON object. | |
| */ | |
| const queryDNS = async (name, type = "A") => { | |
| const response = await fetch(`https://dns.google/resolve?name=${name}&type=${type}`); | |
| return response.json(); | |
| }; | |
| /** | |
| * Fetches ASN (Autonomous System Number) information for a given IP. | |
| * @param {string} ip - The IP address. | |
| * @returns {Promise<object>} - The ASN information from ipinfo.io. | |
| */ | |
| const getAsInfo = async (ip) => { | |
| const response = await fetch(`https://ipinfo.io/${ip}/json?token=app_test`); | |
| return response.json(); | |
| }; | |
| /** | |
| * Processes a DNS record to extract connection details. | |
| * @param {object} record - A DNS record from the Google DNS API. | |
| * @returns {Promise<string|null>} - A formatted string with connection details or null. | |
| */ | |
| const getHostingInformationHTML = (address) => { | |
| const info = getHosting(address) | |
| if(!info) return "" | |
| const isDark = $("html").attr("data-bs-theme") == "dark" | |
| return ` | |
| <img src="${isDark ? info.logo.dark : info.logo.light}" alt="${info.name}" style="display:block; max-width: 150px; margin: 10px auto;" /> | |
| ` | |
| } | |
| const processDnsRecord = async (record) => { | |
| const dataParts = record.data.split(" "); | |
| let ip | |
| let text = "" | |
| if (dataParts.length > 1) { | |
| // SRV Record (e.g., _minecraft._tcp.example.com) | |
| const [, , port, target] = dataParts; | |
| const cleanTarget = removeTrailingDot(target); | |
| if (isBlacklisted(cleanTarget)) return `${cleanTarget}:${port}`; | |
| const aRecord = await queryDNS(cleanTarget, "A"); | |
| ip = aRecord.Answer?.find(ans => ans.type === 1)?.data; | |
| if (ip && IP_REGEX.test(ip)) { | |
| const as = await getAsInfo(ip); | |
| text += `${cleanTarget} - ${ip}:${port} [${as.org || 'N/A'}]`; | |
| } else { | |
| text += `${cleanTarget}:${port}`; | |
| } | |
| } else { | |
| // A Record | |
| ip = dataParts[0]; | |
| if (IP_REGEX.test(ip)) { | |
| const as = await getAsInfo(ip); | |
| text += `${ip} [${as.org || 'N/A'}]`; | |
| } | |
| } | |
| return { text, ip }; | |
| }; | |
| /** | |
| * Fetches and displays detailed server information under the server address element. | |
| * @param {HTMLElement} element - The HTML element containing the server address. | |
| */ | |
| const addAddressElement = async (element) => { | |
| const serverAddress = element.innerText.trim(); | |
| if (!serverAddress) return; | |
| const [aRecords, srvRecords] = await Promise.all([ | |
| queryDNS(serverAddress, "A"), | |
| queryDNS(`_minecraft._tcp.${serverAddress}`, "SRV"), | |
| ]); | |
| const recordsToProcess = srvRecords.Answer || aRecords.Answer || []; | |
| const results = (await Promise.all(recordsToProcess.map(processDnsRecord))).filter(Boolean); | |
| if (results.length > 0) { | |
| const resultHTML = `<ul style="margin:0; list-style-type: none; padding-left: 10px;">${results.map(v => `<li>${v.text}</li>`).join("")}</ul>`; | |
| element.innerHTML = `${serverAddress}${resultHTML}`; | |
| const badge = $(element).parent().parent().parent().next().children() | |
| badge.html(badge.html() + results.map(v => getHostingInformationHTML(v.ip)).join("")) | |
| } | |
| }; | |
| console.log("===== Minelist Hosting Provider Checker Initialized ====="); | |
| // Process servers already on the page | |
| // Process servers added dynamically (e.g., by search or page navigation) | |
| $(".copyable.copyable-ip").each((i, el) => addAddressElement(el)); | |
| $("button[data-action='theme#toggle']").each((i, el) => $(el).on('click', () => location.reload())); | |
| onElementInserted('body', '.copyable.copyable-ip', (el) => addAddressElement(el)); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment