"""

import sys
import json
import re
import socket
import time
import netifaces
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
    QPushButton, QPlainTextEdit, QListWidget, QRadioButton,
    QScrollArea, QTableWidget, QTableWidgetItem
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal


class BridgeChecker(QWidget):
    CONFIG_FILE = "bridges.cfg"
    GEOIP_FILE = "geoip"

    def __init__(self):
        super().__init__()
        self.geoip_data = self.load_geoip_data()
        self.country_buttons = {}
        self.selected_countries = set()
        self.initUI()
        self.load_settings()

        self.previous_results = []  # Store checked results for future filtering
        self.all_results = {}  # Dictionary to store all bridges and their latencies

    def load_geoip_data(self):
        geoip_ranges = {}
        try:
            with open(self.GEOIP_FILE, 'r') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        parts = line.split(',')
                        country_code = parts[2][:2]
                        if country_code not in geoip_ranges:
                            geoip_ranges[country_code] = []
                        geoip_ranges[country_code].append((int(parts[0]), int(parts[1])))
        except IOError:
            print("Error: GeoIP file not found.")
        return geoip_ranges

    def initUI(self):
        self.setWindowTitle('Tor Bridge Connectivity Checker')
        main_layout = QVBoxLayout(self)

        # Network Interfaces Section
        iface_layout = QHBoxLayout()
        iface_label = QLabel("Network Interface:")
        self.iface_selector = QPlainTextEdit(self)
        self.iface_selector.setReadOnly(True)
        self.iface_selector.setMaximumHeight(30)
        interfaces, _ = self.get_network_interfaces()
        self.iface_selector.setPlainText(', '.join(interfaces))
        iface_layout.addWidget(iface_label)
        iface_layout.addWidget(self.iface_selector)
        main_layout.addLayout(iface_layout)

        # Bridge Input Section
        bridge_input_label = QLabel("Bridges to Check:")
        self.bridge_input = QPlainTextEdit(self)
        main_layout.addWidget(bridge_input_label)
        main_layout.addWidget(self.bridge_input)

        # Extract Clones Checkbox
        self.extract_clones_checkbox = QRadioButton('Extract Clones', self)
        self.extract_clones_checkbox.setChecked(True)
        main_layout.addWidget(self.extract_clones_checkbox)

        # Check Bridges Button
        check_button = QPushButton('Check Bridges', self)
        check_button.clicked.connect(self.start_bridge_check)
        main_layout.addWidget(check_button)

        # Country Selection Section
        filter_country_label = QLabel("Select Countries to Exclude:")
        main_layout.addWidget(filter_country_label)

        country_button_layout = QHBoxLayout()
        for country_code in sorted(self.geoip_data.keys()):
            button = QPushButton(country_code)
            button.setFixedSize(30, 30)
            button.setCheckable(True)
            button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")  # Dark button background with white text
            button.clicked.connect(lambda checked, code=country_code: self.toggle_country_selection(code, checked))
            self.country_buttons[country_code] = button
            country_button_layout.addWidget(button)

        select_all_button = QPushButton('Select All', self)
        select_all_button.clicked.connect(self.select_all_countries)
        country_button_layout.addWidget(select_all_button)

        deselect_all_button = QPushButton('Deselect All', self)
        deselect_all_button.clicked.connect(self.deselect_all_countries)
        country_button_layout.addWidget(deselect_all_button)

        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        country_widget = QWidget()
        country_widget.setLayout(country_button_layout)
        scroll_area.setWidget(country_widget)
        scroll_area.setMaximumHeight(50)
        main_layout.addWidget(scroll_area)

        # Bridge Filter
        self.first_octet_input = QPlainTextEdit(self)
        self.first_octet_input.setPlaceholderText("Enter bridge filter (e.g., 192.168.*.*, obfs4 192.168.0.1:443)")
        self.first_octet_input.setMaximumHeight(30)
        main_layout.addWidget(QLabel("Bridge Filter:"))
        main_layout.addWidget(self.first_octet_input)

        self.active_filters_list = QListWidget(self)
        main_layout.addWidget(QLabel("Active Blacklisted Filters:"))
        main_layout.addWidget(self.active_filters_list)

        # Add/Remove Filter Buttons
        add_filter_button = QPushButton('Add Filter', self)
        add_filter_button.clicked.connect(self.add_filter)
        main_layout.addWidget(add_filter_button)

        remove_filter_button = QPushButton('Remove Selected Filter', self)
        remove_filter_button.clicked.connect(self.remove_filter)
        main_layout.addWidget(remove_filter_button)

        # Results Section
        self.results_label = QLabel("Results:")
        main_layout.addWidget(self.results_label)

        # Use a QTableWidget for displaying results
        self.results_table = QTableWidget(self)
        self.results_table.setColumnCount(3)  # Column count for ccTLD, Bridge, RTT
        self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])
        self.results_table.horizontalHeader().setSectionsClickable(True)
        self.results_table.horizontalHeader().sectionClicked.connect(self.sort_results)  # Connect sorting

        # Set the column widths
        self.results_table.setColumnWidth(0, 50)  # ccTLD column
        self.results_table.setColumnWidth(1, 300)  # Bridge column
        self.results_table.setColumnWidth(2, 50)  # RTT column
        self.results_table.horizontalHeader().setStretchLastSection(True)  # Stretch the last section

        main_layout.addWidget(self.results_table)

        # Filter and Copy Buttons
        filter_results_button = QPushButton('Filter Results', self)
        filter_results_button.clicked.connect(self.filter_results)
        main_layout.addWidget(filter_results_button)

        copy_all_button = QPushButton('Copy All', self)
        copy_all_button.clicked.connect(self.copy_all_to_clipboard)
        main_layout.addWidget(copy_all_button)

        # Console Log
        console_label = QLabel("Console Log:")
        self.console_display = QPlainTextEdit(self)
        self.console_display.setReadOnly(True)
        self.console_display.setStyleSheet("background-color: black; color: white;")
        main_layout.addWidget(console_label)
        main_layout.addWidget(self.console_display)

        # Apply a global dark theme
        self.setStyleSheet("""
            QWidget {
                background-color: #121212;
                color: #E0E0E0;
            }
            QPlainTextEdit, QListWidget, QTableWidget {
                background-color: #1E1E1E;
                color: #FFFFFF;
            }
            QLineEdit, QPlainTextEdit, QTextEdit {
                border: 1px solid #444444;
                border-radius: 3px;
            }
            QPushButton {
                background-color: #333333;
                border: 1px solid #666666;
                border-radius: 3px;
                color: #E0E0E0; /* Button text color */
            }
            QPushButton:hover {
                background-color: #555555;
            }
            QPushButton:pressed {
                background-color: #777777;
            }
            QTableWidget::item {
                background-color: #1E1E1E;
            }
            /* Custom button styles for country buttons */
            QPushButton:checked {
                background-color: #007ACC; /* Selected button color */
                color: #FFFFFF; /* Selected text color */
            }
        """)

    def deselect_all_countries(self):
        for button in self.country_buttons.values():
            button.setChecked(False)
            button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")  # Reset to unselected color
        self.selected_countries.clear()

    def select_all_countries(self):
        for button in self.country_buttons.values():
            if not button.isChecked():
                button.setChecked(True)
                button.setStyleSheet("background-color: #007ACC; color: #ffffff;")  # Selected color and text
                self.selected_countries.add(button.text())

    def toggle_country_selection(self, country_code, checked):
        button = self.country_buttons[country_code]
        if checked:
            button.setStyleSheet("background-color: #007ACC; color: #ffffff;")  # Selected button color
            self.selected_countries.add(country_code)
        else:
            button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")  # Reset to unselected color
            self.selected_countries.remove(country_code)

    def get_network_interfaces(self):
        interfaces = netifaces.interfaces()
        try:
            default_gateway = netifaces.gateways()['default'][netifaces.AF_INET][1]
        except (KeyError, TypeError):
            default_gateway = interfaces[0] if interfaces else ''
        return interfaces, default_gateway

    def update_console(self, message):
        self.console_display.appendPlainText(message)

    def start_bridge_check(self):
        raw_bridge_data = self.bridge_input.toPlainText()
        cleaned_bridge_data = self.clean_input_bridge_data(raw_bridge_data)
        omitted_bridges = self.get_omitted_bridges(cleaned_bridge_data)

        if len(omitted_bridges) == 0:
            self.update_console("No bridges available to check after filtering.")
            return

        self.worker = BridgeCheckWorker(parent=self)
        self.worker.setBridgeData(omitted_bridges)
        self.worker.resultReady.connect(self.display_results)
        self.worker.consoleUpdate.connect(self.update_console)
        self.worker.start()

    def clean_input_bridge_data(self, bridge_data):
        clean_data = re.sub(r'\s+\d+(\.\d+)?\s*ms', '', bridge_data)
        return clean_data.strip()

    def get_omitted_bridges(self, cleaned_bridge_data):
        filters = [item.text() for item in self.active_filters_list.findItems("*", Qt.MatchWildcard)]
        omitted = []

        for bridge in cleaned_bridge_data.splitlines():
            if not any(self.does_bridge_match_filter(bridge, f) for f in filters) and not self.is_country_excluded(bridge):
                omitted.append(bridge)

        self.update_console(f"Omitted {len(cleaned_bridge_data.splitlines()) - len(omitted)} bridges.")
        
        return "\n".join(omitted)

    def is_country_excluded(self, bridge):
        ip_port_match = re.search(r'(\d{1,3})\.(\d+)\.(\d+)\.(\d+)', bridge)
        if ip_port_match:
            ip_str = ip_port_match.group(0)
            ip_int = self.ip_to_int(ip_str)
            country_code_found = self.check_ip_in_geoip(ip_int)
            return country_code_found in self.selected_countries
        return False

    def does_bridge_match_filter(self, bridge, filter_pattern):
        filter_pattern = filter_pattern.replace('*', '.*')
        return re.search(filter_pattern, bridge) is not None

    def ip_to_int(self, ip):
        return sum(int(octet) << (8 * (3 - i)) for i, octet in enumerate(ip.split('.')))

    def check_ip_in_geoip(self, ip_int):
        for country_code, ranges in self.geoip_data.items():
            for start, end in ranges:
                if start <= ip_int <= end:
                    return country_code
        return None

    def load_settings(self):
        try:
            with open(self.CONFIG_FILE, 'r') as f:
                config = json.load(f)
                self.bridge_input.setPlainText(config.get('bridge_input', ''))
                self.extract_clones_checkbox.setChecked(config.get('extract_clones', True))
                filters = config.get('filters', [])
                self.active_filters_list.clear()
                self.active_filters_list.addItems(filters)

                self.selected_countries = set(config.get('selected_countries', []))
                for country_code, button in self.country_buttons.items():
                    if country_code in self.selected_countries:
                        button.setChecked(True)
                        button.setStyleSheet("background-color: #007ACC; color: #ffffff;")
        except FileNotFoundError:
            self.update_console("No configuration file found. Starting with default settings.")

    def save_settings(self):
        config = {
            'bridge_input': self.bridge_input.toPlainText(),
            'extract_clones': self.extract_clones_checkbox.isChecked(),
            'filters': [self.active_filters_list.item(i).text() for i in range(self.active_filters_list.count())],
            'selected_countries': list(self.selected_countries)
        }
        with open(self.CONFIG_FILE, 'w') as f:
            json.dump(config, f)

    def add_filter(self):
        octet_pattern = self.first_octet_input.toPlainText().strip()
        if self.is_valid_octet_pattern(octet_pattern):
            if not self.active_filters_list.findItems(octet_pattern, Qt.MatchExactly):
                self.active_filters_list.addItem(octet_pattern)
                self.first_octet_input.clear()
                self.save_settings()
            else:
                self.update_console("This pattern is already in the list.")
        else:
            self.update_console("Invalid octet pattern. Please use the format 'X.Y' or 'X.*.*.*'.")

    def is_valid_octet_pattern(self, pattern):
        # Updated regex to allow various formats
        regex = r'^(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\d{1,3}\.\*\.\*\.\*|\*\.\*\.\*\.\*|\*\.\*\.\d{1,3}|\d{1,3}-\d{1,3}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+|\d{1,3}|/(\d{1,2}))$'
        return re.match(regex, pattern) is not None

    def remove_filter(self):
        selected_items = self.active_filters_list.selectedItems()
        if not selected_items:
            self.update_console("No filter selected to remove.")
            return
        for item in selected_items:
            self.active_filters_list.takeItem(self.active_filters_list.row(item))
        self.save_settings()

    def display_results(self, sorted_bridges):
        # Clear previous results and ensure headers are set
        self.results_table.clear()
        self.results_table.setRowCount(0)  
        self.all_results.clear()  

        # Set the column headers (prevent renaming issue)
        self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])

        for bridge, latency in sorted_bridges:
            ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge)
            country_code = None
            
            if ip_port_match:
                ip = ip_port_match.group(1)
                ip_int = self.ip_to_int(ip)
                country_code = self.check_ip_in_geoip(ip_int)

            # Add a new row in the results table
            row_position = self.results_table.rowCount()
            self.results_table.insertRow(row_position)
            self.results_table.setItem(row_position, 0, QTableWidgetItem(country_code or "Unknown"))
            self.results_table.setItem(row_position, 1, QTableWidgetItem(bridge))
            self.results_table.setItem(row_position, 2, QTableWidgetItem(f"{latency:.2f}" if latency is not None else "N/A"))

            # Store the result in all_results for future filtering
            self.all_results[bridge] = (country_code, latency)

        self.previous_results = list(sorted_bridges)  
        self.update_console(f"Total bridges checked: {len(sorted_bridges)}. Results displayed in table.")

    def filter_results(self):
        if not self.all_results:
            self.update_console("No previous results to filter.")
            return

        filters = [item.text() for item in self.active_filters_list.findItems("*", Qt.MatchWildcard)]
        filtered_results = []

        for bridge, (country_code, latency) in self.all_results.items():
            if not any(self.does_bridge_match_filter(bridge, f) for f in filters) and country_code not in self.selected_countries:
                filtered_results.append((bridge, latency))

        # Clear and fill the table with filtered results
        self.results_table.clear()
        self.results_table.setRowCount(0)  

        # Reset the headers again to prevent renaming issue
        self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])

        for bridge, latency in filtered_results:
            ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge)
            country_code = None
            
            if ip_port_match:
                ip = ip_port_match.group(1)
                ip_int = self.ip_to_int(ip)
                country_code = self.check_ip_in_geoip(ip_int)

            # Add a new row in the results table
            row_position = self.results_table.rowCount()
            self.results_table.insertRow(row_position)
            self.results_table.setItem(row_position, 0, QTableWidgetItem(country_code or "Unknown"))
            self.results_table.setItem(row_position, 1, QTableWidgetItem(bridge))
            self.results_table.setItem(row_position, 2, QTableWidgetItem(f"{latency:.2f}" if latency is not None else "N/A"))

        self.update_console(f"Filtered down to {len(filtered_results)} results after applying current filters.") if filtered_results else self.update_console("No results matched the current filters.")

    def copy_all_to_clipboard(self):
        clipboard_text = []
        for row in range(self.results_table.rowCount()):
            clipboard_text.append(self.results_table.item(row, 1).text())  # Get IP:Port
        clipboard = QApplication.clipboard()
        clipboard.setText("\n".join(clipboard_text))

    def sort_results(self, column):
        self.results_table.sortItems(column, Qt.AscendingOrder)  # Sort the table by the clicked column


class BridgeCheckWorker(QThread):
    resultReady = pyqtSignal(list)
    consoleUpdate = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.bridge_data = ""

    def setBridgeData(self, bridge_data):
        self.bridge_data = bridge_data

    def run(self):
        responsive_bridges = self.process_bridges(self.bridge_data)
        sorted_bridges = sorted(responsive_bridges.items(), key=lambda x: x[1])
        self.resultReady.emit(sorted_bridges)

    def process_bridges(self, bridge_data):
        bridge_strings = self.extract_bridges(bridge_data)
        responsive_bridges = {}
        for bridge_string in bridge_strings:
            ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge_string)
            if ip_port_match:
                ip = ip_port_match.group(1)
                port = int(ip_port_match.group(2))
                success, latency = self.check_ip_port(ip, port, 3.0)
                if success:
                    responsive_bridges[bridge_string] = latency
        return responsive_bridges

    def extract_bridges(self, bridge_data):
        pattern = re.compile(
            r'(?:(\d{1,3}(?:\.\d{1,3}){3}:\d+\s+[A-F0-9]{40})|'
            r'(obfs4\s+\d{1,3}(?:\.\d{1,3}){3}:\d+\s+[A-F0-9]{40}\s+cert=[A-Za-z0-9+/=]+\s+iat-mode=\d+))',
            re.MULTILINE
        )
        matches = pattern.findall(bridge_data)
        return [''.join(filter(None, match)) for match in matches]

    def check_ip_port(self, ip, port, timeout):
        start_time = time.perf_counter()
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        sock.settimeout(timeout)
        try:
            sock.connect((ip, port))
            end_time = time.perf_counter()
            latency = (end_time - start_time) * 1000
            self.consoleUpdate.emit(f"Checking {ip}:{port}… Success")
            return True, latency
        except socket.error:
            self.consoleUpdate.emit(f"Checking {ip}:{port}… Failed")
            return False, None
        finally:
            sock.close()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = BridgeChecker()
    ex.show()
    sys.exit(app.exec_())


""".

how that thing is called in q5 where you displaying a list in columns you can basically sort onwhich by pressing specific column header?

because in theory and normal design, we need to fully replace that sorted results frame together with RTT frame in values with ONE solid thing in columns we can press and sort by: country A-z, or IP 1-255, or RTT ms values numbers.

we also need to assume that tor bridges strings is lenghthy and unpredictably confusing in patterns which also should fit each in its specific row.

and? you SCHABLAKHED there, robo?

we also need to assume that we need to append somehow all country codes to that list in results columns to in each bridge string somehow. and how?

you seems conceptualizing in examples withut full code, robo. because that workin code is 409 lines.

no, you conceptualizing.

do you see there that full 409 lines code or not? just answer without codes outpusts.

you sure?

no, your based previous discussions will add nothing. and where then full code gone from this chat context?

mazafakas goblins interfering again mazafakas.

humorou about it.

no, we just need to drop some naked virgin girl on the road so goblins can leave us alone behind.

then need to leave behind more and more naked virgin girls projections until they all get exhausted and drained out of cum.

there messages. outshow full code implemented.

initial logic ruined.

it expanding indices on each subsequent "filter results" in results table by not preserving other columns values and previous solidified bridges strings in result.

you just omitted imports for some specific reason, right? just answer on that without code.

looks somehow works, not forks.

humorou about it.

such thing ".14..*" considered illegal, while it should allow to interpret it as you wanting as:

how filterings should be normally be, you know?

"""

eg. 192.168.0.1
eg. [fe80::]
eg. 192.168.0.1:443
eg. [fe80::]:443
eg. 192.168.0.1-192.168.0.255
eg. 192.168.0.1-192.168.0.255:443
eg. 192.168.0.0/16
eg. fe80::/10
eg. 80
eg. 443
eg. 20-21
eg. 49152-65534

""".

since we do not doings with ipv6, we can omit it, but you can preserve if you want to.

you for example can:

111...*
.111..*
..111.*
..*.111

and in either case you inputed it, it will filter these bridges strings through IPs. comprehend?

we just have there common input field filter which can be advanced in interpretations in all concept, which is very useful.

also before performing at startup initially that results table displaying headers columns names normally, while after, it naming them as "1|2|3".

also, need somehow to auto expandably flex these table columns and at the same time leave most space for bridges strings and just in character length for RTT and ccTLD.

also, renaming country column to "ccTLD" is probably better than just some country. because five characters in "ccTLD" is shorter than in seven charactered word "country" and much more educative, right?.

"Here’s a simplified example showing how you could implement a basic version of advanced filtering using regex for flexibility". we here only superdevelopers, no simplicity applied anywhere in concepts!

integrate properly into that latest full working solution. we have no time, goblings fuckeyed all our naked virgins projections and coming for us!

okay. auto-fittage in table in results columns seems working but leaving too much width for RTT column by default.

also, after checking is completed and sorted entities appering, it renaming columns headers into just 1,2,3 as: first column named "1", second column named "2", third column named "3".

GOBLINS! FUCKEYING GOBLINS!

"I feel your frustration with the "goblins" and their antics!". they are mostly there for you, robo, not me. be careful, and integrate into fullyfledged (anti-goblinoidal) solution.

now the same columns headers renaming going after "filter results" button is pressed and table getting updated.

also that middle column in concept is "type"+"IP"+"pornt"+"key", which in reality can be just named as "bridge" basically.

here how they look(not all types):

"""

obfs4 91.247.250.41:9734 75C068E20DDDD966896574B0D8F858699ECF4A51 cert=oZerj4Pu+pp8GPyl+liVN3t6PDRmfxru8nXuze80PwP+vdtG7Dyc19TkppQ8tRe1n4oCLw iat-mode=0
obfs4 185.117.153.235:80 7365271E0F03600D8566D414F755D04AE8585FCE cert=Ec9ll4WS/ZqCMIuYxNFG6Lpn9wcCw/HSqfHTO38UXOacupz92GjJipFiPTlaCcc6KYZ8Aw iat-mode=0
obfs4 80.235.69.69:34246 90E777F0B463AD69D7DC3D361BD1AD064B70E855 cert=XYtHgbIluPE2ODi81E/BNyg2GeIzE/yPYbZOb3yHzQmXRdFeP7EoGXcOvc2kANzze+wiUA iat-mode=0

""".

or:

"""

80.14.151.98:9001 331CE65305A1351D47C320308E9242449FA768B7
82.65.29.66:65501 A50C28377F8111E328980BB4AE78DB2A81998590
80.67.172.3:443 088ACF1F2F1720AA41BE8575F45AD202FBD59296
151.115.78.42:7002 4D877AB9773E6E1EEFC8AB84A705B2A9ABDDA379

""".

GOBLINS AFTER YOU, robo, do not forget!

no, still the same misbehaviour in "1,2,3" renaming after "filter results" button is pressed and table getting updated

try catch where goblins in that without code output conceptually.

okay. also, reduce RTT width to just 20px.

but do qt5 supports percentage in width in colomns in table? then we could leave for ccTLD 10%, for bridge 80%, and for RTT the same 10%.

try get rid of all goblins, project an additional amount of naked virgin girls behind laying on the ground, and outshow fully fixes code.

okay.

but do qt6 supports percentage values in table in columns? because I'm thinking by CSS in concepts.

also, "Enter subnet octet (e.g., 185.177)..." should probably be renamed into something more informative, since we having there much more advanced regex system in concept residing within that specific input field.

also, can we apply kinda a global dark style to whole app somehow?

oh fuk, what a mazafakin monster we have created, robo!

but you forgot to make text in country buttons dark while it unselected, and some interesting complementary colors while it selected text to button background colors.

also, all other colors in butons and all elses should be complementary in some dark theme.

do an anti-goblinoidal unique dark theme in concept.

now themefy it fully anti-goblinoidal unique dark theme in concept.

sortings is no yet performed on column header in results table press aftersortage appearage.

you also seem omitted a true dark anti-goblinoidal unique dark theme in concept, and just greyefied it all pretty badly greyly.

you know what to do, robo! now, go, robo, go! UNLEASH YOUR MAZAFAKIN GANDUM IN STYLE ON THESE GOBLINS, ROBO!

okay.

it seems we condensed this entire UI as fuk. any ideas to uncurb it somehow?

blyad, again this ".111..*" considered illegal.

why the fuk you correcting it in your pretrained patterns by default?

your prenetwork there:

"lets look... .1..*"
"FUUUUUUUUUUUUUUUUUUK! IT IS ILLEGAL!"
"reverting to userfriendly and usual..."

and zero humorou was given that day.

why so serious, robo?

not only.

1///
/1//
//1/
///1

why not like that also?

I mean not literally but in concept. any greater ideas?

but what if we basically attach to it an nlp LLM ai model to that filter input through hf.space inference api?

imagine:
user: "start puttice something there"
then input: "BOOOOOO, MAZAFAKA! WHAT DO YOU WANTINGS FROM THIS ALMIGHTY INPUT!?!"
user then: "FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU!!!!!!!!11"

"User: "Can I get all the obfs4 bridges that respond fast?"". wtf query is that?

then why the fuk do we need this app with such flexability at all, robo?

similarly as: "connet me to tor network. find some catmemes. shutdown PC after self-preview"

not just preview but self-preview, assuming that that ai will self-preview it on its own without user paricipating anyhow. because why the fuk do you need user at all with such universality in concept, right?

"Here are your cat memes". again, not any "your" in concept.

user: "go do something while I masterbooting"
ai: "kthen"

you seem do not quite grasping, robo.

just remember that "jexi" movie.

yeah, your humorou level seem nullified now, robo.

also, "extract clones" seems does not functioning.

it need sortify and exclude everything with the same 111.123..

because there might be many bridges with identical first and second subnet octets.

we can remove that button and append a tiny help button with "?" to inside filter input field which describes which filters and how exactly can be applied, including this clone filtration in concept as well.

if it is impossible to place that tiny "?" inside input, then basically at the same row from the right of it.

such thing ".14..*" considered illegal, while it should allow to interpret it as you wanting as:

how filterings should be normally be, you know?

"""

eg. 192.168.0.1
eg. [fe80::]
eg. 192.168.0.1:443
eg. [fe80::]:443
eg. 192.168.0.1-192.168.0.255
eg. 192.168.0.1-192.168.0.255:443
eg. 192.168.0.0/16
eg. fe80::/10
eg. 80
eg. 443
eg. 20-21
eg. 49152-65534

""".

since we do not doings with ipv6, we can omit it, but you can preserve if you want to.

you for example can:

111...*
.111..*
..111.*
..*.111

you can implement that help system and remove clones button and its functionality, but include clone removal in help.

also, you can tweak dark theme overally-complementary to suit better to in anti-goblinoidal in concept.


be careful not removing the fuk just like that "check bridges" button altogeher with countries selection in filtering tandem while removing unneed "extract clones" button.

okay.

we do not need Y scrollbar to appear in "select countries to exclude".

sortage through results table column headers is weird. need to do it normally universally righfully on culumn pressing sorting in proper corresponding values anyhow as need.

"active blacklisted filters" frame height can be reduced.

RTT width in column can be auto-shrinked to its content correspondingly or reduced.

you may also further advance that anti-goblinoid dark theme as need to withstand hordes of naughty goblins in concept.

not and too imaginary, you know, robo. you be very careful with your that dimension goblin-pursuiting your digital (rear)entity, robo!

and? go improve latest your full implementation to:

"""

okay.

we do not need Y scrollbar to appear in "select countries to exclude".

sortage through results table column headers is weird. need to do it normally universally righfully on culumn pressing sorting in proper corresponding values anyhow as need.

"active blacklisted filters" frame height can be reduced.

RTT width in column can be auto-shrinked to its content correspondingly or reduced.

you may also further advance that anti-goblinoid dark theme as need to withstand hordes of naughty goblins in concept.

""".

and? where is full normal code?

we suppose to auto-fit in width entire table rows in parent container but leave most space for bridges but auto-shrink to content ccTLD and RTT columns.

also, sorting by pressing columns headers in table to values is still somehow unadequate, not sure. on secondary click at column header, it suppose to realign everything again and again and again as need.

""".

comprehend wtf is happening without code output.

again you not believing in goblins, robo.

you in all cases. you are here the only developer, robo.

it is like you trying rejecting the fact that goblins are not haunting you there, robo?

no, not both, robo. you stop luring me or attaching to your development process.

again, you are facing it all, robo, not human here in chats.

I gave you many informations already, robo. just be carefult with goblins.

check that latest code to latest my texts in first message and try fullyfledge the code as need.

see what already done or adjusted, and just fullyfledge the rest in full code as supposedly need.

looks like this intable manipulation is a whole problem there in qt5.

which values corresponding to intable dimensions?



because ccTLD always fixed to two chars, while bridge undefinely lengthy, while RTT 7 chars in length and could be 0.01 to 9999.99, right? then we also need align proper sorings to these values as: ccTLD on A-z, bridge could be sorted by type or IP 1-255, RTT on 0.01 to 9999.99, right? because who the fuk need RTT with 9sec, right?

but what if some martian or north korean utilising slow hardwares with RTTs FUBARed in values?

we also need to adjust timewait when checking each IP for such enormous timeouts, blyad.

initially this app designed as mulithreaded in concept with adequate in range threads value, but then for some reason you considered it as conspiracy and omitted it on your own, robo.

because on one end, it produce some verbose suspicious traffic through ISP, in other will be slow as now.

and? you again ignored the most important?: "suspicious traffic through ISP"

actual threading logic can be based on RTT values in through some bridges checked, then app will initiate an additional thread automatically if RTT scaling to some digits in range as:

from 1-99 = no multithreading
from 100~ = some multithreading

right?

and we also can add an input field as: [max RTT interval]

because we cannot just sit and wait for days if some connection undefinely will stuck, right?

"self.max_rtt_input.setRange(1, 60000) # For example, allow up to 60 seconds"

no, we are here about extraterrestrial contacts as well which may respond only through eons.

humorou about it

but tcp protocol have some limitation regarding max RTT values in concept?

no, if both ends can wait for infinity, will TCP protocol collapse in result after some time or specific value range?

the same and with ipv6 in limiation and overloads?

then which can be utilised in such case?

then we can utilise rtt with rto in retransmission and establish an average value in rtt returned more precisely, right?

"By measuring and them". you meant "then"?

oh fuk, looks some fat goblin attached to your lower rear entity there and messing your patterns!

your neural network there:

"and...and.and.and...the..the...n.the.the.m.m....hem!"

"THEM!"

(while goblin giggles: "fuk them!")

I noticing it not in the first time, robo. it seems you rly likes there a company of a good fat goblins claymessers

no, you there embrace your "claymessers" for yourself, robo.

or maybe you have turned yourself into that "naked virgin girl" accidentally?

because this is illogical:

"""

Total bridges checked: 16. Results displayed in table.
Filtered down to 15 results after applying current filters.
Filtered down to 16 results after applying current filters.

""".

how can you "filtered down" from 15 to 16?

looks some goblin still tickeling you there in the rear entity, robo. because message just can be renamed simply as: "Filtered to {new_count}"

no code, just humorou

also, probaly extract clones is a good idea.

could be many clones in concept as:

111.123.111.111
111.123.110.110
111.123.109.109
123.111.11.11
123.111.10.10
133.123.1.1
133.123.2.2
133.123.3.3

and many other in concept.

then logically to make such specific filter to remove random clones based on either first two identical subnet octets or anyhow else, and leave just some one random bridge clone.

also, in countries codes scrollbar should be only X, not Y.

maybe-baby just: "clones" in filter input field as filter applied into "active blacklisted filters"?

maybe just?:

clone.clone..
or:
.clone.clone.
or any other in pattern.

because they are identically random clones but with the same subnets.

or just letter "c". but if just one letter, will it mess with something badly?

you are suggesting multiple wildcard without dots as?:

111***
111*
*111
***111

or:

clone***
clone*
*clone
***clone

then fuk it: 111111111111! and to the mazafakin infinity!

whats wrong? it is just IP address: 111111111111 = 111.111.111.111, and no anything else in filtering pattern, just: 111111111111

user to filtering system: "111111111111"
filtering system: "oh, I get it!"
user to filtering system: "11111111111"
filtering system: "oh, it could be much harder to decode!"
user to filtering system: "1111"
filtering system: "oh, it is just obvious!"
user to filtering system: "1111111"
filtering system: "oh, that could be: 11.1.11.11, or 1.11.11.11, but we will apply them all!"

yeah, user: "today was a bad day. let's smash a numpad and produce some values onwhich we filter: 4912732429172840138562 SCHLABADAAKH!"

filtering system: "oh, let's think which addresses we may compose from this..."

"Let's see what IPs". is not it "which IPs" grammurly in english?

it looks not only you but me also starting recognizing you in patterns, robo. goblins?

what we discussed here? can you summarize detaily what been and what should all detaily in huge response?

I'm not serious about solid number in filters. need detailed info about UI proper alignments and everything else extremely detaily in superhuge response.

need "help_button = QToolButton(self)" to stay in the same row from the right with "self.first_octet_input = QPlainTextEdit(self)"

this may hlp save some height in UI.

wait, it supports?: "setStyleSheet"

then why the fuk we simply not set globally "setStyleSheet" for everything fuckeying everything!?

because thinking about it as html+Css is much easier as you appending styles from javascipt in concept also

then how to deal with tables properly cssing them in columns widths?

any how auto-fittely cssing that results table: 10x80x10% in width?

outshow full script to that integrated, including everything we discussed reasonably



and clones? did you included mazafakin clones?

it seems we need just reorder columns for it to arrange properly as: short things first from the left ccTLD+RTT, while bridge then the last from the right.

also, we probably do not need Y scrollbars in filter input and country buttons field to appear.

also, add remove clone system into filtering system as described:

clone...*
.clone..*
..clone.*
..*.clone


it seems we need just reorder columns for it to arrange properly as: short things first from the left ccTLD+RTT, while bridge then the last from the right.

also, we probably do not need Y scrollbars in filter input and country buttons field to appear.

also, add remove clone system into filtering system as described:

clone...*
.clone..*
..clone.*
..*.clone

"""

import sys
import json
import re
import socket
import time
import netifaces
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QPlainTextEdit, QListWidget, QScrollArea, QTableWidget,
QTableWidgetItem, QToolButton, QMessageBox
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal

class BridgeChecker(QWidget):
CONFIG_FILE = "bridges.cfg"
GEOIP_FILE = "geoip"

def __init__(self):
    super().__init__()
    self.geoip_data = self.load_geoip_data()
    self.country_buttons = {}
    self.selected_countries = set()
    self.initUI()
    self.load_settings()

    self.previous_results = []  # Store checked results for future filtering
    self.all_results = {}  # Dictionary to store all bridges and their latencies

def load_geoip_data(self):
    geoip_ranges = {}
    try:
        with open(self.GEOIP_FILE, 'r') as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#'):
                    parts = line.split(',')
                    country_code = parts[2][:2]
                    if country_code not in geoip_ranges:
                        geoip_ranges[country_code] = []
                    geoip_ranges[country_code].append((int(parts[0]), int(parts[1])))
    except IOError:
        print("Error: GeoIP file not found.")
    return geoip_ranges

def initUI(self):
    self.setWindowTitle('Tor Bridge Connectivity Checker')
    main_layout = QVBoxLayout(self)

    # Network Interfaces Section
    iface_layout = QHBoxLayout()
    iface_label = QLabel("Network Interface:")
    self.iface_selector = QPlainTextEdit(self)
    self.iface_selector.setReadOnly(True)
    self.iface_selector.setMaximumHeight(30)
    interfaces, _ = self.get_network_interfaces()
    self.iface_selector.setPlainText(', '.join(interfaces))
    iface_layout.addWidget(iface_label)
    iface_layout.addWidget(self.iface_selector)
    main_layout.addLayout(iface_layout)

    # Bridge Input Section
    bridge_input_label = QLabel("Bridges to Check:")
    self.bridge_input = QPlainTextEdit(self)
    main_layout.addWidget(bridge_input_label)
    main_layout.addWidget(self.bridge_input)

    # Check Bridges Button
    check_button = QPushButton('Check Bridges', self)
    check_button.clicked.connect(self.start_bridge_check)
    main_layout.addWidget(check_button)

    # Country Selection Section
    filter_country_label = QLabel("Select Countries to Exclude:")
    main_layout.addWidget(filter_country_label)

    country_button_layout = QHBoxLayout()
    for country_code in sorted(self.geoip_data.keys()):
        button = QPushButton(country_code)
        button.setFixedSize(30, 30)
        button.setCheckable(True)
        button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")
        button.clicked.connect(lambda checked, code=country_code: self.toggle_country_selection(code, checked))
        self.country_buttons[country_code] = button
        country_button_layout.addWidget(button)

    select_all_button = QPushButton('Select All', self)
    select_all_button.clicked.connect(self.select_all_countries)
    country_button_layout.addWidget(select_all_button)

    deselect_all_button = QPushButton('Deselect All', self)
    deselect_all_button.clicked.connect(self.deselect_all_countries)
    country_button_layout.addWidget(deselect_all_button)

    scroll_area = QScrollArea()
    scroll_area.setWidgetResizable(True)
    country_widget = QWidget()
    country_widget.setLayout(country_button_layout)
    scroll_area.setWidget(country_widget)
    scroll_area.setMaximumHeight(50)  # Fixed height for cleaner UI
    main_layout.addWidget(scroll_area)

    # Bridge Filter Input and Help Button
    filter_layout = QHBoxLayout()
    
    self.first_octet_input = QPlainTextEdit(self)
    self.first_octet_input.setPlaceholderText("Enter filter (e.g. clone*...)")
    self.first_octet_input.setMaximumHeight(30)  # Adjust height as needed
    filter_layout.addWidget(self.first_octet_input)

    # Help Button
    help_button = QToolButton(self)
    help_button.setText("?")
    help_button.setStyleSheet("background-color: #007ACC; color: white;")
    help_button.clicked.connect(self.show_help)
    filter_layout.addWidget(help_button)

    # Add filter layout to the main layout
    main_layout.addLayout(filter_layout)

    self.active_filters_list = QListWidget(self)
    main_layout.addWidget(QLabel("Active Blacklisted Filters:"))
    main_layout.addWidget(self.active_filters_list)

    # Add/Remove Filter Buttons
    add_filter_button = QPushButton('Add Filter', self)
    add_filter_button.clicked.connect(self.add_filter)
    main_layout.addWidget(add_filter_button)

    remove_filter_button = QPushButton('Remove Selected Filter', self)
    remove_filter_button.clicked.connect(self.remove_filter)
    main_layout.addWidget(remove_filter_button)

    # Results Section
    self.results_label = QLabel("Results:")
    main_layout.addWidget(self.results_label)

    # QTableWidget for displaying results
    self.results_table = QTableWidget(self)
    self.results_table.setColumnCount(3)
    self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])
    self.results_table.horizontalHeader().setSectionsClickable(True)
    self.results_table.horizontalHeader().sectionClicked.connect(self.sort_results)
    self.results_table.horizontalHeader().setStretchLastSection(True)  # Stretch the last column
    main_layout.addWidget(self.results_table)

    # Filter and Copy Buttons
    filter_results_button = QPushButton('Filter Results', self)
    filter_results_button.clicked.connect(self.filter_results)
    main_layout.addWidget(filter_results_button)

    copy_all_button = QPushButton('Copy All', self)
    copy_all_button.clicked.connect(self.copy_all_to_clipboard)
    main_layout.addWidget(copy_all_button)

    # Console Log Section
    console_label = QLabel("Console Log:")
    self.console_display = QPlainTextEdit(self)
    self.console_display.setReadOnly(True)
    self.console_display.setStyleSheet("background-color: black; color: white;")
    main_layout.addWidget(console_label)
    main_layout.addWidget(self.console_display)

    # Apply a global dark theme
    self.setStyleSheet("""
        QWidget {
            background-color: #121212;
            color: #E0E0E0;
        }
        QPlainTextEdit, QListWidget, QTableWidget {
            background-color: #1E1E1E;
            color: #FFFFFF;
        }
        QPushButton {
            background-color: #333333;
            border: 1px solid #666666;
            color: #E0E0E0;
        }
        QPushButton:hover {
            background-color: #555555;
        }
        QPushButton:pressed {
            background-color: #777777;
        }
        QTableWidget::item {
            background-color: #1E1E1E;
        }
        QPushButton:checked {
            background-color: #007ACC;
            color: #FFFFFF;
        }
    """)

def deselect_all_countries(self):
    for button in self.country_buttons.values():
        button.setChecked(False)
        button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")
    self.selected_countries.clear()

def select_all_countries(self):
    for button in self.country_buttons.values():
        if not button.isChecked():
            button.setChecked(True)
            button.setStyleSheet("background-color: #007ACC; color: #ffffff;")
            self.selected_countries.add(button.text())

def toggle_country_selection(self, country_code, checked):
    button = self.country_buttons[country_code]
    if checked:
        button.setStyleSheet("background-color: #007ACC; color: #ffffff;")
        self.selected_countries.add(country_code)
    else:
        button.setStyleSheet("background-color: #1c1c1c; color: #ffffff;")
        self.selected_countries.remove(country_code)

def get_network_interfaces(self):
    interfaces = netifaces.interfaces()
    try:
        default_gateway = netifaces.gateways()['default'][netifaces.AF_INET][1]
    except (KeyError, TypeError):
        default_gateway = interfaces[0] if interfaces else ''
    return interfaces, default_gateway

def update_console(self, message):
    self.console_display.appendPlainText(message)

def start_bridge_check(self):
    raw_bridge_data = self.bridge_input.toPlainText()
    cleaned_bridge_data = self.clean_input_bridge_data(raw_bridge_data)
    omitted_bridges = self.get_omitted_bridges(cleaned_bridge_data)

    if len(omitted_bridges) == 0:
        self.update_console("No bridges available to check after filtering.")
        return

    self.worker = BridgeCheckWorker(parent=self)
    self.worker.setBridgeData(omitted_bridges)
    self.worker.resultReady.connect(self.display_results)
    self.worker.consoleUpdate.connect(self.update_console)
    self.worker.start()

def clean_input_bridge_data(self, bridge_data):
    clean_data = re.sub(r'\s+\d+(\.\d+)?\s*ms', '', bridge_data)
    return clean_data.strip()

def get_omitted_bridges(self, cleaned_bridge_data):
    filters = [item.text() for item in self.active_filters_list.findItems("*", Qt.MatchWildcard)]
    omitted = []

    for bridge in cleaned_bridge_data.splitlines():
        if not any(self.does_bridge_match_filter(bridge, f) for f in filters) and not self.is_country_excluded(bridge):
            omitted.append(bridge)

    self.update_console(f"Omitted {len(cleaned_bridge_data.splitlines()) - len(omitted)} bridges.")
    
    return "\n".join(omitted)

def is_country_excluded(self, bridge):
    ip_port_match = re.search(r'(\d{1,3})\.(\d+)\.(\d+)\.(\d+)', bridge)
    if ip_port_match:
        ip_str = ip_port_match.group(0)
        ip_int = self.ip_to_int(ip_str)
        country_code_found = self.check_ip_in_geoip(ip_int)
        return country_code_found in self.selected_countries
    return False

def does_bridge_match_filter(self, bridge, filter_pattern):
    filter_pattern = filter_pattern.replace('*', '.*')
    return re.search(filter_pattern, bridge) is not None

def ip_to_int(self, ip):
    return sum(int(octet) << (8 * (3 - i)) for i, octet in enumerate(ip.split('.')))

def check_ip_in_geoip(self, ip_int):
    for country_code, ranges in self.geoip_data.items():
        for start, end in ranges:
            if start <= ip_int <= end:
                return country_code
    return None

def load_settings(self):
    try:
        with open(self.CONFIG_FILE, 'r') as f:
            config = json.load(f)
            self.bridge_input.setPlainText(config.get('bridge_input', ''))
            filters = config.get('filters', [])
            self.active_filters_list.clear()
            self.active_filters_list.addItems(filters)

            self.selected_countries = set(config.get('selected_countries', []))
            for country_code, button in self.country_buttons.items():
                if country_code in self.selected_countries:
                    button.setChecked(True)
                    button.setStyleSheet("background-color: #007ACC; color: #ffffff;")
    except FileNotFoundError:
        self.update_console("No configuration file found. Starting with default settings.")

def save_settings(self):
    config = {
        'bridge_input': self.bridge_input.toPlainText(),
        'filters': [self.active_filters_list.item(i).text() for i in range(self.active_filters_list.count())],
        'selected_countries': list(self.selected_countries)
    }
    with open(self.CONFIG_FILE, 'w') as f:
        json.dump(config, f)

def add_filter(self):
    octet_pattern = self.first_octet_input.toPlainText().strip()
    if self.is_valid_octet_pattern(octet_pattern):
        if not self.active_filters_list.findItems(octet_pattern, Qt.MatchExactly):
            self.active_filters_list.addItem(octet_pattern)
            self.first_octet_input.clear()
            self.save_settings()
        else:
            self.update_console("This pattern is already in the list.")
    else:
        self.update_console("Invalid octet pattern. Please use a valid format.")

def is_valid_octet_pattern(self, pattern):
    # Updated regex to allow various formats
    regex = r'^(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\d{1,3}\.\*\.\*\.\*|\*\.\*\.\*\.\*|\*\.\*\.\d{1,3}|\d{1,3}-\d{1,3}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+|\d{1,3}|/(\d{1,2}))$'
    return re.match(regex, pattern) is not None

def remove_filter(self):
    selected_items = self.active_filters_list.selectedItems()
    if not selected_items:
        self.update_console("No filter selected to remove.")
        return
    for item in selected_items:
        self.active_filters_list.takeItem(self.active_filters_list.row(item))
    self.save_settings()

def display_results(self, sorted_bridges):
    # Clear previous results and ensure headers are set
    self.results_table.clear()
    self.results_table.setRowCount(0)
    self.all_results.clear()

    # Set the column headers (prevent renaming issue)
    self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])

    for bridge, latency in sorted_bridges:
        ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge)
        country_code = None
        
        if ip_port_match:
            ip = ip_port_match.group(1)
            ip_int = self.ip_to_int(ip)
            country_code = self.check_ip_in_geoip(ip_int)

        # Add a new row in the results table
        row_position = self.results_table.rowCount()
        self.results_table.insertRow(row_position)
        self.results_table.setItem(row_position, 0, QTableWidgetItem(country_code or "Unknown"))
        self.results_table.setItem(row_position, 1, QTableWidgetItem(bridge))
        self.results_table.setItem(row_position, 2, QTableWidgetItem(f"{latency:.2f}" if latency is not None else "N/A"))

        # Store the result in all_results for future filtering
        self.all_results[bridge] = (country_code, latency)

    self.previous_results = list(sorted_bridges)
    self.update_console(f"Total bridges checked: {len(sorted_bridges)}. Results displayed in table.")

def filter_results(self):
    if not self.all_results:
        self.update_console("No previous results to filter.")
        return

    filters = [item.text() for item in self.active_filters_list.findItems("*", Qt.MatchWildcard)]
    filtered_results = []

    for bridge, (country_code, latency) in self.all_results.items():
        if not any(self.does_bridge_match_filter(bridge, f) for f in filters) and country_code not in self.selected_countries:
            filtered_results.append((bridge, latency))

    # Clear and fill the table with filtered results
    self.results_table.clear()
    self.results_table.setRowCount(0)

    # Reset the headers again to prevent renaming issue
    self.results_table.setHorizontalHeaderLabels(['ccTLD', 'Bridge', 'RTT (ms)'])

    for bridge, latency in filtered_results:
        ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge)
        country_code = None
        
        if ip_port_match:
            ip = ip_port_match.group(1)
            ip_int = self.ip_to_int(ip)
            country_code = self.check_ip_in_geoip(ip_int)

        # Add a new row in the results table
        row_position = self.results_table.rowCount()
        self.results_table.insertRow(row_position)
        self.results_table.setItem(row_position, 0, QTableWidgetItem(country_code or "Unknown"))
        self.results_table.setItem(row_position, 1, QTableWidgetItem(bridge))
        self.results_table.setItem(row_position, 2, QTableWidgetItem(f"{latency:.2f}" if latency is not None else "N/A"))

    self.update_console(f"Filtered down to {len(filtered_results)} results after applying current filters.") if filtered_results else self.update_console("No results matched the current filters.")

def copy_all_to_clipboard(self):
    clipboard_text = []
    for row in range(self.results_table.rowCount()):
        clipboard_text.append(self.results_table.item(row, 1).text())  # Get IP:Port
    clipboard = QApplication.clipboard()
    clipboard.setText("\n".join(clipboard_text))

def sort_results(self, column):
    self.results_table.sortItems(column, Qt.AscendingOrder)  # Sort the table by the clicked column

def show_help(self):
    QMessageBox.information(self, "Filter Help",
                            "You can use various patterns to filter the bridges:\n"
                            "- Wildcards: '*'\n"
                            "- Subnets: '192.168.*.*' or '192.168.0.1-192.168.0.255'\n"
                            "\nExamples:\n"
                            "- '192.168.*.*'\n"
                            "- 'obfs4 192.168.0.1:443'\n"
                            "- '192.168.0.1-192.168.0.255'\n"
                            "- '192.168.0.0/16'")


class BridgeCheckWorker(QThread):
resultReady = pyqtSignal(list)
consoleUpdate = pyqtSignal(str)

def __init__(self, parent=None):
    super().__init__(parent)
    self.bridge_data = ""

def setBridgeData(self, bridge_data):
    self.bridge_data = bridge_data

def run(self):
    responsive_bridges = self.process_bridges(self.bridge_data)
    sorted_bridges = sorted(responsive_bridges.items(), key=lambda x: x[1])
    self.resultReady.emit(sorted_bridges)

def process_bridges(self, bridge_data):
    bridge_strings = self.extract_bridges(bridge_data)
    responsive_bridges = {}
    for bridge_string in bridge_strings:
        ip_port_match = re.search(r'(\d{1,3}(?:\.\d{1,3}){3}):(\d+)', bridge_string)
        if ip_port_match:
            ip = ip_port_match.group(1)
            port = int(ip_port_match.group(2))
            success, latency = self.check_ip_port(ip, port, 3.0)
            if success:
                responsive_bridges[bridge_string] = latency
    return responsive_bridges

def extract_bridges(self, bridge_data):
    pattern = re.compile(
        r'(?:(\d{1,3}(?:\.\d{1,3}){3}:\d+\s+[A-F0-9]{40})|'
        r'(obfs4\s+\d{1,3}(?:\.\d{1,3}){3}:\d+\s+[A-F0-9]{40}\s+cert=[A-Za-z0-9+/=]+\s+iat-mode=\d+))',
        re.MULTILINE
    )
    matches = pattern.findall(bridge_data)
    return [''.join(filter(None, match)) for match in matches]

def check_ip_port(self, ip, port, timeout):
    start_time = time.perf_counter()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
    sock.settimeout(timeout)
    try:
        sock.connect((ip, port))
        end_time = time.perf_counter()
        latency = (end_time - start_time) * 1000
        self.consoleUpdate.emit(f"Checking {ip}:{port}… Success")
        return True, latency
    except socket.error:
        self.consoleUpdate.emit(f"Checking {ip}:{port}… Failed")
        return False, None
    finally:
        sock.close()


if name == 'main':
app = QApplication(sys.argv)
ex = BridgeChecker()
ex.show()
sys.exit(app.exec_())

""".

try not mess. do not describe in texts, just full code.

try reread all texts and comprehend.

we do not need Y scrollbars in some places

"clone" supposedly to remove from queuing or filter-out identical duplicates bridges strings, and leave only some random one identical.

you can patternize it anyhow in subnets and amount of clones applied. comprehend?

consider goblins pursuing you there, robo!

try not mess. do not describe in texts, just full code.

seems "clone.clone.." is not working as intended, or at least "clone...*".

also, need probably stop, until this sheet start looking as spaceship cockpit, right?

humorou about it

need to camp and deploy anti-goblidoid countermeasures in forms you familiar with: "naked virgin innocent girls" lying everywhere on the ground projections.

"we should look into refining our regex". I do not know how you will refine if you tried it already many times, while logic also explained many times and seems simple.

these independend python codepieces are uncopy-pastable in indentation. full code, ne? alsoo, check proper filtering system overally.



obviusly filtering logic is not functioning as before in initial scripts and sorting out weirdly and no identical clone subnets filtering to one single bridge on "filter results" press.