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.



"""

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 without unnecessary scrollbars
        no_scroll_area = QScrollArea()
        no_scroll_area.setWidgetResizable(True)
        country_widget = QWidget()
        country_widget.setLayout(country_button_layout)
        no_scroll_area.setWidget(country_widget)
        no_scroll_area.setMaximumHeight(50)  
        main_layout.addWidget(no_scroll_area)

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

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

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

        # 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)
        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.setColumnWidth(0, 50)  # ccTLD column width
        self.results_table.setColumnWidth(1, 300)  # Bridge column width
        self.results_table.setColumnWidth(2, 50)   # RTT column width

        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;
            }
            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):
        if column == 2:  # If sorting by RTT (3rd column), sort numerically
            self.results_table.sortItems(column, Qt.AscendingOrder)
        else:
            self.results_table.sortItems(column, Qt.AscendingOrder)  # Default alphabetical sorting for other columns

    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_())


""".

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.