一个基于修改Hosts让登录MC不再困难的软件
import sys
import ctypes

from minecraft_cn_login_fix_tool.app.main_window import MainWindow

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget


def is_admin():
    return ctypes.windll.shell32.IsUserAnAdmin()


def main():
    QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)
    app.font().families().append("Microsoft YaHei UI")
    main_app = MainWindow()
    main_app.show()
    app.exec_()


if __name__ == '__main__':
    #if int(is_admin()) == 1:
        sys.exit(main())
    #else:
    #    app = QApplication(sys.argv)
    #    w = QWidget()
    #    error_window = QMessageBox.critical(w, "警告", "请使用管理员身份运行！", QMessageBox.Yes)


main.py
SkyDynamic/MinecraftCNLoginFixTool
from typing import Union, List

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from qfluentwidgets import SettingCard, FluentIconBase, ComboBox


class ComboBoxSettingCard(SettingCard):

    def __init__(self, value: str, values: List[str], icon: Union[str, QIcon, FluentIconBase], title, content=None,
                 parent=None):
        super().__init__(icon, title, content, parent)

        self.comboBox = ComboBox(self)
        self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight)
        self.hBoxLayout.addSpacing(16)

        self.comboBox.addItems(values)
        self.comboBox.setCurrentText(value)
        self.comboBox.setMinimumWidth(140)




minecraft_cn_login_fix_tool/app/components/combo_box_setting_card.py
SkyDynamic/MinecraftCNLoginFixTool
from PyQt5 import QtGui
from PyQt5.QtCore import Qt, QSettings, QVariant, QSize, QPoint
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QHBoxLayout, QStackedWidget
from qfluentwidgets import NavigationInterface, qrouter, FluentIcon
from qframelesswindow import FramelessWindow, StandardTitleBar

from .pages.login_fix_page import LoginPage
from .pages.auth_fix_page import AuthPage

class MainWindow(FramelessWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.title = StandardTitleBar(self)
        self.title.titleLabel.setStyleSheet("""
            QLabel{
                background: transparent;
                font: 13px 'Microsoft YaHei';
                padding: 0 4px
            }
        """)
        self.setTitleBar(self.title)
        self.hBoxLayout = QHBoxLayout(self)
        self.navigationInterface = NavigationInterface(
            self, showMenuButton=True, showReturnButton=True)
        self.stackWidget = QStackedWidget(self)

        self.login_interface = LoginPage(self)
        self.auth_interface = AuthPage(self)

        self.stackWidget.addWidget(self.login_interface)
        self.stackWidget.addWidget(self.auth_interface)

        self.initLayout()
        self.initNavigation()
        self.initWindow()

    def initLayout(self):
        self.hBoxLayout.setSpacing(0)
        self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
        self.hBoxLayout.addWidget(self.navigationInterface)
        self.hBoxLayout.addWidget(self.stackWidget)
        self.hBoxLayout.setStretchFactor(self.stackWidget, 1)

        self.titleBar.raise_()
        self.navigationInterface.displayModeChanged.connect(self.titleBar.raise_)

    def initNavigation(self):
        self.navigationInterface.addItem(
            routeKey=self.login_interface.objectName(),
            icon=FluentIcon.DEVELOPER_TOOLS,
            text="启动器登录/皮肤下载",
            onClick=lambda: self.switchTo(self.login_interface)
        )
        
        self.navigationInterface.addItem(
            routeKey=self.auth_interface.objectName(),
            icon=FluentIcon.GAME,
            text="验证服务器",
            onClick=lambda: self.switchTo(self.auth_interface)
        )

        qrouter.setDefaultRouteKey(self.stackWidget, self.login_interface.objectName())
        self.navigationInterface.setCurrentItem(self.login_interface.objectName())

        self.stackWidget.currentChanged.connect(self.onCurrentInterfaceChanged)
        self.stackWidget.setCurrentIndex(0)

    def switchTo(self, widget):
        self.stackWidget.setCurrentWidget(widget)

    def initWindow(self):
        self.readSettings()
        self.setWindowIcon(QIcon('resources/icon.ico'))
        self.setWindowTitle(self.tr("Minecraft CN Login Fix Tool V1.1.0"))
        self.titleBar.setAttribute(Qt.WA_StyledBackground)

    def onCurrentInterfaceChanged(self, index):
        widget = self.stackWidget.widget(index)
        self.navigationInterface.setCurrentItem(widget.objectName())

    def resizeEvent(self, e):
        self.titleBar.move(46, 0)
        self.titleBar.resize(self.width() - 46, self.titleBar.height())

    def closeEvent(self, event: QtGui.QCloseEvent) -> None:
        super().closeEvent(event)
        self.saveSettings()

    def readSettings(self):
        settings = QSettings("SkyDynamic", "SimpleMCServerCreater")
        size = settings.value("size", QVariant(QSize(900, 700)))
        pos = settings.value("pos", QVariant(QPoint(200, 200)))
        self.resize(size)
        self.move(pos)

    def saveSettings(self):
        settings = QSettings("SkyDynamic", "SimpleMCServerCreater")
        settings.setValue("size", QVariant(self.size()))
        settings.setValue("pos", QVariant(self.pos()))

minecraft_cn_login_fix_tool/app/main_window.py
SkyDynamic/MinecraftCNLoginFixTool
import os

from PyQt5.QtCore import Qt, QThread, pyqtSignal, QThreadPool, QRunnable, QObject, pyqtSlot
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QSizePolicy, QHeaderView, QAbstractItemView, QFrame, QTableWidgetItem, QMessageBox
from qfluentwidgets import ScrollArea, PushButton, FluentIcon, TableWidget, StateToolTip

from ...utils.ip_data import *

from ping3 import ping

import re

server_data = []


def takePing(elem):
    return elem["ping"]


class Signal:
    def __init__(self, u, f, s, a, so):
        self.updateButtonSignal = u
        self.fillTableSignal = f
        self.stateTooltipSignal = s
        self.appendNewDataSignal = a
        self.sortNewDataSignal = so


class PingThread(QRunnable):
    
    def __init__(self):
        super().__init__()
        self.data = None
        self.signs: Signal = None
    
    def run(self):
        response = ping(self.data.get("ip"), size=1024, timeout=1)
        if not response:
            response = 999999
        else:
            response = int(response * 1000)
        self.data["ping"] = response
        self.signs.appendNewDataSignal.emit(self.data)
    
    def transfer(self, signal: Signal, data):
        self.signs = signal
        self.data = data


class Tasks(QObject):
    def __init__(self, signal):
        super(Tasks, self).__init__()
        self.signal = signal
        
        self.pool = QThreadPool()
        self.pool.globalInstance()
    
    def start(self):
        self.pool.setMaxThreadCount(len(server_data))
        for data in server_data:
            task_thread = PingThread()
            task_thread.transfer(self.signal, data)
            task_thread.setAutoDelete(True)
            self.pool.start(task_thread)
        
        self.pool.waitForDone()
        
        self.signal.sortNewDataSignal.emit()
        self.signal.stateTooltipSignal.emit(self.tr("TCPing完成！"), "", False)
        self.signal.fillTableSignal.emit()
        self.signal.updateButtonSignal.emit(True)


class UpdateThread(QThread):
    updateButtonSignal = pyqtSignal(bool)
    fillTableSignal = pyqtSignal()
    stateTooltipSignal = pyqtSignal(str, str, bool)
    appendNewDataSignal = pyqtSignal(dict)
    sortNewDataSignal = pyqtSignal()
    
    def __init__(self, parent: ScrollArea = None) -> None:
        super().__init__(parent)
    
    def run(self) -> None:
        self.stateTooltipSignal.emit("正在测试各个IP延迟中...",
                                     "这需要一些时间, 请耐心等待!", True)
        task = Tasks(Signal(
            self.updateButtonSignal,
            self.fillTableSignal,
            self.stateTooltipSignal,
            self.appendNewDataSignal,
            self.sortNewDataSignal
        ))
        task.start()


class AuthPage(ScrollArea):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.installer_list = []
        self.loader_list = []
        self.new_data = []
        
        self.setObjectName("auth_fix_page")
        
        self.vBoxLayout = QVBoxLayout(self)
        
        self.update_button = PushButton("测试延迟", self, FluentIcon.SYNC)
        self.update_button.clicked.connect(self.__on_update_button_clicked)
        self.confirm_button = PushButton("选取并修改Hosts", self, FluentIcon.PENCIL_INK)
        self.confirm_button.clicked.connect(self.__on_confirm_button_clicked)
        self.delete_button = PushButton("清除本软件修改的hosts", self, FluentIcon.DELETE)
        self.delete_button.clicked.connect(self.__on_delete_button_clicked)
        
        self.buttonLayout = QHBoxLayout()
        self.buttonLayout.setContentsMargins(8, 8, 8, 8)
        self.buttonLayout.addWidget(self.update_button)
        self.buttonLayout.addWidget(self.confirm_button)
        self.buttonLayout.addWidget(self.delete_button)
        
        self.vBoxLayout.addLayout(self.buttonLayout)
        self.vBoxLayout.setContentsMargins(16, 32, 16, 16)
        
        self.tableFrame = TableFrame(self)
        self.vBoxLayout.addWidget(self.tableFrame)
        
        self.resize(1024, 768)
        
        self.__fill_table()
    
    def __on_update_button_clicked(self):
        self.update_button.setEnabled(False)
        update_thread = UpdateThread(self)
        update_thread.updateButtonSignal.connect(self.__update_data_updateButton_signalReceive)
        update_thread.stateTooltipSignal.connect(self.__update_data_stateTooltip_signalReceive)
        update_thread.fillTableSignal.connect(self.__update_data_fillTable_signalReceive)
        update_thread.appendNewDataSignal.connect(self.__append_new_data)
        update_thread.sortNewDataSignal.connect(self.__sort_new_data_signalReceive)
        update_thread.start()
    
    def __on_confirm_button_clicked(self):
        hosts_list = []
        new_hosts_list = []
        if self.tableFrame.table.currentRow() == -1:
            return
        data = server_data[self.tableFrame.table.currentRow()]
        for domain in AUTHSERVER_DOMAIN:
            hosts_list.append(data.get("ip") + " " + domain + " #MCLFT_\n")
        try:
            with open("C:/Windows/System32/drivers/etc/hosts", "r", encoding="utf8") as hosts_file:
                origin_hosts_lines = hosts_file.readlines()
            
            with open("C:/Windows/System32/drivers/etc/hosts", "w", encoding="utf8") as hosts_file:
                for line in origin_hosts_lines:
                    if re.search('#MCLFT_', line) and line.split()[1] in AUTHSERVER_DOMAIN:
                        continue
                    new_hosts_list.append(line)
                hosts_file.write("".join(new_hosts_list + hosts_list))
                os.popen("ipconfig /flushdns")
        except Exception as e:
            QMessageBox.critical(self, "错误", str(e.__class__), QMessageBox.Yes)
        QMessageBox.information(self, '警告', '修改Hosts完成！', QMessageBox.Yes)
    
    def __on_delete_button_clicked(self):
        new_hosts_list = []
        try:
            with open("C:/Windows/System32/drivers/etc/hosts", "r", encoding="utf8") as hosts_file:
                origin_hosts_lines = hosts_file.readlines()
            
            with open("C:/Windows/System32/drivers/etc/hosts", "w", encoding="utf8") as hosts_file:
                for line in origin_hosts_lines:
                    if re.search('#MCLFT_', line):
                        continue
                    new_hosts_list.append(line)
                hosts_file.write("".join(new_hosts_list))
                os.popen("ipconfig /flushdns")
        except Exception as e:
            QMessageBox.critical(self, "错误", str(e), QMessageBox.Yes)
        QMessageBox.information(self, '警告', '本软件修改的Hosts删除完毕！', QMessageBox.Yes)
    
    def __update_data_updateButton_signalReceive(self, status):
        self.update_button.setEnabled(status)
    
    def __update_data_stateTooltip_signalReceive(self, title, content, status):
        if status:
            self.stateTooltip = StateToolTip(title, content, self.window())
            self.stateTooltip.move(self.stateTooltip.getSuitablePos())
            self.stateTooltip.show()
        else:
            self.stateTooltip.setContent(title)
            self.stateTooltip.setState(True)
            self.stateTooltip = None
    
    def __sort_new_data_signalReceive(self):
        self.new_data.sort(key=takePing)
    
    def __update_data_fillTable_signalReceive(self):
        self.__fill_table(self.new_data)
    
    def __append_new_data(self, data):
        self.new_data.append(data)
    
    def __fill_table(self, data=None):
        global server_data
        if not data:
            processed_data = []
            for key in AUTHSERVER_IP.keys():
                IP_LIST = AUTHSERVER_IP.get(key)
                for ip in IP_LIST:
                    processed_data.append({"ip": ip, "country": key, "ping": None})
            self.tableFrame.table.setRowCount(len(processed_data))
            for i, data in enumerate(processed_data):
                ip_item = QTableWidgetItem(data.get("ip"))
                ip_item.setTextAlignment(Qt.AlignCenter)
                country_item = QTableWidgetItem(data.get("country"))
                country_item.setTextAlignment(Qt.AlignCenter)
                self.tableFrame.table.setItem(i, 0, ip_item)
                self.tableFrame.table.setItem(i, 1, country_item)
            server_data = processed_data
        else:
            for i, data_ in enumerate(data):
                ip_item = QTableWidgetItem(data_.get("ip"))
                ip_item.setTextAlignment(Qt.AlignCenter)
                country_item = QTableWidgetItem(data_.get("country"))
                country_item.setTextAlignment(Qt.AlignCenter)
                ping_item = QTableWidgetItem(str(data_.get("ping")) if str(data_.get("ping")) != "999999" else "连接超时")
                ping_item.setTextAlignment(Qt.AlignCenter)
                self.tableFrame.table.setItem(i, 0, ip_item)
                self.tableFrame.table.setItem(i, 1, country_item)
                self.tableFrame.table.setItem(i, 2, ping_item)
            server_data = data


class TableFrame(QFrame):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        
        self.vBoxLayout = QVBoxLayout(self)
        self.vBoxLayout.setContentsMargins(0, 8, 0, 0)
        
        # 表格部分
        self.table = TableWidget(self)
        self.table.verticalHeader().hide()
        self.table.setColumnCount(3)
        self.table.setRowCount(0)
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        
        self.table.setHorizontalHeaderLabels([
            "IP",
            "地区",
            "延迟"
        ])
        
        self.table.setColumnWidth(0, 250)
        self.table.setColumnWidth(1, 100)
        
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        
        self.vBoxLayout.addWidget(self.table, Qt.AlignLeft)

minecraft_cn_login_fix_tool/app/pages/auth_fix_page.py
SkyDynamic/MinecraftCNLoginFixTool
import os

from PyQt5.QtCore import Qt, QThread, pyqtSignal, QRunnable, QObject, QThreadPool
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QSizePolicy, QHeaderView, QAbstractItemView, QFrame, QTableWidgetItem, QMessageBox
from qfluentwidgets import ScrollArea, PushButton, FluentIcon, TableWidget, StateToolTip

from ...utils.ip_data import *

from ping3 import ping

import re

server_data = []


def takePing(elem):
    return elem["ping"]


class Signal:
    def __init__(self, u, f, s, a, so):
        self.updateButtonSignal = u
        self.fillTableSignal = f
        self.stateTooltipSignal = s
        self.appendNewDataSignal = a
        self.sortNewDataSignal = so


class PingThread(QRunnable):
    
    def __init__(self):
        super().__init__()
        self.data = None
        self.signs: Signal = None
    
    def run(self):
        response = ping(self.data.get("ip"), size=1024, timeout=1)
        if not response:
            response = 999999
        else:
            response = int(response * 1000)
        self.data["ping"] = response
        self.signs.appendNewDataSignal.emit(self.data)
    
    def transfer(self, signal: Signal, data):
        self.signs = signal
        self.data = data


class Tasks(QObject):
    def __init__(self, signal):
        super(Tasks, self).__init__()
        self.signal = signal
        
        self.pool = QThreadPool()
        self.pool.globalInstance()
    
    def start(self):
        self.pool.setMaxThreadCount(len(server_data) + 1)
        for data in server_data:
            task_thread = PingThread()
            task_thread.transfer(self.signal, data)
            task_thread.setAutoDelete(True)
            self.pool.start(task_thread)
        
        self.pool.waitForDone()
        
        self.signal.sortNewDataSignal.emit()
        self.signal.stateTooltipSignal.emit(self.tr("TCPing完成！"), "", False)
        self.signal.fillTableSignal.emit()
        self.signal.updateButtonSignal.emit(True)


class UpdateThread(QThread):
    updateButtonSignal = pyqtSignal(bool)
    fillTableSignal = pyqtSignal()
    stateTooltipSignal = pyqtSignal(str, str, bool)
    appendNewDataSignal = pyqtSignal(dict)
    sortNewDataSignal = pyqtSignal()
    
    def __init__(self, parent: ScrollArea = None) -> None:
        super().__init__(parent)
    
    def run(self) -> None:
        self.stateTooltipSignal.emit("正在测试各个IP延迟中...",
                                     "这需要一些时间, 请耐心等待!", True)
        task = Tasks(Signal(
            self.updateButtonSignal,
            self.fillTableSignal,
            self.stateTooltipSignal,
            self.appendNewDataSignal,
            self.sortNewDataSignal
        ))
        task.start()


class LoginPage(ScrollArea):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        self.installer_list = []
        self.loader_list = []
        self.new_data = []
        
        self.setObjectName("login_fix_page")
        
        self.vBoxLayout = QVBoxLayout(self)
        
        self.update_button = PushButton("测试延迟", self, FluentIcon.SYNC)
        self.update_button.clicked.connect(self.__on_update_button_clicked)
        self.confirm_button = PushButton("选取并修改Hosts", self, FluentIcon.PENCIL_INK)
        self.confirm_button.clicked.connect(self.__on_confirm_button_clicked)
        self.delete_button = PushButton("清除本软件修改的hosts", self, FluentIcon.DELETE)
        self.delete_button.clicked.connect(self.__on_delete_button_clicked)
        
        self.buttonLayout = QHBoxLayout()
        self.buttonLayout.setContentsMargins(8, 8, 8, 8)
        self.buttonLayout.addWidget(self.update_button)
        self.buttonLayout.addWidget(self.confirm_button)
        self.buttonLayout.addWidget(self.delete_button)
        
        self.vBoxLayout.addLayout(self.buttonLayout)
        self.vBoxLayout.setContentsMargins(16, 32, 16, 16)
        
        self.tableFrame = TableFrame(self)
        self.vBoxLayout.addWidget(self.tableFrame)
        
        self.resize(1024, 768)
        
        self.__fill_table()
    
    def __on_update_button_clicked(self):
        self.update_button.setEnabled(False)
        update_thread = UpdateThread(self)
        update_thread.updateButtonSignal.connect(self.__update_data_updateButton_signalReceive)
        update_thread.stateTooltipSignal.connect(self.__update_data_stateTooltip_signalReceive)
        update_thread.fillTableSignal.connect(self.__update_data_fillTable_signalReceive)
        update_thread.appendNewDataSignal.connect(self.__append_new_data)
        update_thread.sortNewDataSignal.connect(self.__sort_new_data_signalReceive)
        update_thread.start()
    
    def __on_confirm_button_clicked(self):
        hosts_list = []
        new_hosts_list = []
        if self.tableFrame.table.currentRow() == -1:
            return
        data = server_data[self.tableFrame.table.currentRow()]
        for domain in LOGIN_DOMAIN:
            hosts_list.append(data.get("ip") + " " + domain + " #MCLFT_\n")
        try:
            with open("C:/Windows/System32/drivers/etc/hosts", "r", encoding="utf8") as hosts_file:
                origin_hosts_lines = hosts_file.readlines()
            
            with open("C:/Windows/System32/drivers/etc/hosts", "w", encoding="utf8") as hosts_file:
                for line in origin_hosts_lines:
                    if re.search('#MCLFT_', line) and line.split()[1] in LOGIN_DOMAIN:
                        continue
                    new_hosts_list.append(line)
                hosts_file.write("".join(new_hosts_list + hosts_list))
                os.popen("ipconfig /flushdns")
        except Exception as e:
            QMessageBox.critical(self, "错误", str(e.__class__), QMessageBox.Yes)
        QMessageBox.information(self, '警告', '修改Hosts完成！', QMessageBox.Yes)
    
    def __on_delete_button_clicked(self):
        new_hosts_list = []
        try:
            with open("C:/Windows/System32/drivers/etc/hosts", "r", encoding="utf8") as hosts_file:
                origin_hosts_lines = hosts_file.readlines()
            
            with open("C:/Windows/System32/drivers/etc/hosts", "w", encoding="utf8") as hosts_file:
                for line in origin_hosts_lines:
                    if re.search('#MCLFT_', line):
                        continue
                    new_hosts_list.append(line)
                hosts_file.write("".join(new_hosts_list))
                os.popen("ipconfig /flushdns")
        except Exception as e:
            QMessageBox.critical(self, "错误", str(e), QMessageBox.Yes)
        QMessageBox.information(self, '警告', '本软件修改的Hosts删除完毕！', QMessageBox.Yes)
        
    def __update_data_updateButton_signalReceive(self, status):
        self.update_button.setEnabled(status)
    
    def __update_data_stateTooltip_signalReceive(self, title, content, status):
        if status:
            self.stateTooltip = StateToolTip(title, content, self.window())
            self.stateTooltip.move(self.stateTooltip.getSuitablePos())
            self.stateTooltip.show()
        else:
            self.stateTooltip.setContent(title)
            self.stateTooltip.setState(True)
            self.stateTooltip = None
    
    def __sort_new_data_signalReceive(self):
        self.new_data.sort(key=takePing)
    
    def __update_data_fillTable_signalReceive(self):
        self.__fill_table(self.new_data)
    
    def __append_new_data(self, data):
        self.new_data.append(data)
    
    def __fill_table(self, data=None):
        global server_data
        if not data:
            processed_data = []
            for key in LOGIN_IP.keys():
                IP_LIST = LOGIN_IP.get(key)
                for ip in IP_LIST:
                    processed_data.append({"ip": ip, "country": key, "ping": None})
            self.tableFrame.table.setRowCount(len(processed_data))
            for i, data in enumerate(processed_data):
                ip_item = QTableWidgetItem(data.get("ip"))
                ip_item.setTextAlignment(Qt.AlignCenter)
                country_item = QTableWidgetItem(data.get("country"))
                country_item.setTextAlignment(Qt.AlignCenter)
                self.tableFrame.table.setItem(i, 0, ip_item)
                self.tableFrame.table.setItem(i, 1, country_item)
            server_data = processed_data
        else:
            for i, data_ in enumerate(data):
                ip_item = QTableWidgetItem(data_.get("ip"))
                ip_item.setTextAlignment(Qt.AlignCenter)
                country_item = QTableWidgetItem(data_.get("country"))
                country_item.setTextAlignment(Qt.AlignCenter)
                ping_item = QTableWidgetItem(str(data_.get("ping")) if str(data_.get("ping")) != "999999" else "连接超时")
                ping_item.setTextAlignment(Qt.AlignCenter)
                self.tableFrame.table.setItem(i, 0, ip_item)
                self.tableFrame.table.setItem(i, 1, country_item)
                self.tableFrame.table.setItem(i, 2, ping_item)
            server_data = data


class TableFrame(QFrame):
    def __init__(self, parent=None) -> None:
        super().__init__(parent)
        
        self.vBoxLayout = QVBoxLayout(self)
        self.vBoxLayout.setContentsMargins(0, 8, 0, 0)
        
        # 表格部分
        self.table = TableWidget(self)
        self.table.verticalHeader().hide()
        self.table.setColumnCount(3)
        self.table.setRowCount(0)
        self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        
        self.table.setHorizontalHeaderLabels([
            "IP",
            "地区",
            "延迟"
        ])
        
        self.table.setColumnWidth(0, 250)
        self.table.setColumnWidth(1, 100)
        
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        
        self.vBoxLayout.addWidget(self.table, Qt.AlignLeft)

minecraft_cn_login_fix_tool/app/pages/login_fix_page.py
SkyDynamic/MinecraftCNLoginFixTool
LOGIN_IP = {
    "美国": [
        "13.107.213.47", "13.107.213.57", "13.107.213.49",
        "13.107.213.39", "13.107.213.33", "13.107.213.64",
        "13.107.213.50", "13.107.213.68", "13.107.213.69",
        "13.107.213.53", "13.107.213.59", "13.107.213.43",
        "13.107.213.35", "13.107.213.44", "13.107.213.31",
        "13.107.213.40", "13.107.213.41", "13.107.213.60",
        "13.107.219.36", "13.107.227.45", "13.107.213.51",
        "13.107.213.55", "13.107.227.36", "13.107.219.45",
        "13.107.213.42"
    ],
    "MICROSOFT.COM": [
        "13.107.246.47", "13.107.246.68", "13.107.246.64",
        "13.107.246.33", "13.107.246.60", "13.107.246.57",
        "13.107.246.18", "13.107.246.69", "13.107.246.43",
        "13.107.246.59", "13.107.246.40", "13.107.246.50",
        "13.107.246.39", "13.107.246.35", "13.107.246.44",
        "13.107.246.41", "13.107.246.42", "13.107.246.55",
        "13.107.246.53", "13.107.246.51", "13.107.246.31",
        "13.107.246.49"
    ]
}

AUTHSERVER_IP = {
    "美国": [
        '54.192.193.121', '13.225.86.78', '13.35.98.82',
        '99.84.226.87', '65.8.161.127', '99.86.1.83',
        '13.227.74.143', '99.84.70.83', '13.225.196.78',
        '13.224.95.78', '65.8.58.127', '99.84.107.108',
        '99.84.107.45', '99.84.107.49', '99.84.107.113',
        '143.204.128.91', '143.204.128.110', '143.204.128.126',
        '143.204.128.128', '65.9.71.54', '65.9.71.14',
        '65.9.71.8', '65.9.71.97', '13.225.143.68',
        '13.225.143.73', '13.225.143.47', '13.225.143.50',
        '65.8.17.17', '65.8.17.126', '65.8.17.117',
        '65.8.17.109', '99.84.79.30', '99.84.79.3',
        '99.84.79.94', '99.84.79.57', '13.226.175.36',
        '13.226.175.53', '13.226.175.80', '13.226.175.122',
        '13.227.73.68', '13.227.73.71', '13.227.73.103',
        '13.227.73.96', '13.225.206.124', '13.225.206.62',
        '13.225.206.49', '13.225.206.107', '54.230.48.56',
        '54.230.48.33', '54.230.48.72', '54.230.48.69',
        '13.224.96.13', '13.224.96.6', '13.224.96.77',
        '13.224.96.62', '216.137.37.66', '216.137.37.84',
        '216.137.37.81', '216.137.37.102', '65.8.69.51',
        '65.8.69.48', '65.8.69.78', '65.8.69.15',
        '99.86.189.129', '99.86.150.129', '18.65.138.18',
        '13.224.160.20', '13.225.99.128', '13.249.34.140',
        '13.225.192.128', '13.224.103.128', '65.8.103.18',
        '54.192.252.21', '13.249.160.130', '65.9.177.20'
    ],
    "芬兰": [
        '13.33.244.125', '13.33.244.27', '13.33.244.90',
        '13.33.244.50'
    ],
    "韩国": ['52.84.184.122', '54.230.255.146'],
    "英国": ['52.84.73.122'],
    "日本": ['13.33.212.20'],
    "法国": ['54.230.187.19'],
    "意大利": ['143.204.12.81'],
    "西班牙": ['54.240.186.133'],
    "新加坡": ['52.84.225.135'],
    "香港": ['54.192.72.20']
}

LOGIN_DOMAIN = [
    "resources.download.minecraft.net",
    "libraries.minecraft.net",
    "launcher.mojang.com",
    "launchermeta.mojang.com",
    "sessionserver.mojang.com",
    "api.minecraftservices.com",
    "textures.minecraft.net",
    "redstone-launcher.mojang.com",
    "api.mojang.com"
]

AUTHSERVER_DOMAIN = [
    "authserver.mojang.com"
]

minecraft_cn_login_fix_tool/utils/ip_data.py
SkyDynamic/MinecraftCNLoginFixTool