need proper input in subnet octets to allow to filter by any pattern in ranges in octets as:
then can filter by any octet subnet position independently as: "185.177"

need some universal input method so it comprehend any input method in IP pattern and add filter correcly without false undefined characters errors and misformat in all possible/impossible variations/permutations.




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

class BridgeChecker(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Tor Bridge Connectivity Checker')
        
        main_layout = QVBoxLayout(self)
        
        # Interface Selection
        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
        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 Button
        check_button = QPushButton('Check Bridges', self)
        check_button.clicked.connect(self.start_bridge_check)
        main_layout.addWidget(check_button)

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

        # Blacklist by First Octet Input
        self.first_octet_input = QPlainTextEdit(self)
        self.first_octet_input.setPlaceholderText("Enter first subnet octet (0-255)...")
        main_layout.addWidget(QLabel("Blacklist by First Octet:"))
        main_layout.addWidget(self.first_octet_input)

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

        # Button to add filter
        add_filter_button = QPushButton('Add Filter', self)
        add_filter_button.clicked.connect(self.add_filter)
        main_layout.addWidget(add_filter_button)

        # Button to remove selected filter
        remove_filter_button = QPushButton('Remove Selected Filter', self)
        remove_filter_button.clicked.connect(self.remove_filter)
        main_layout.addWidget(remove_filter_button)

        # Bridge and RTT Display
        display_layout = QVBoxLayout()
        
        # Horizontal Layout for Lists
        list_layout = QHBoxLayout()
        
        # Bridges
        self.bridges_list = QListWidget(self)
        
        # RTTs
        self.rtt_labels = QListWidget(self)
        self.rtt_labels.setFocusPolicy(Qt.NoFocus)

        # Set fixed width for RTTs to match approximate width for up to 10 digits plus " ms"
        font_metrics = self.rtt_labels.fontMetrics()
        max_digits = 10
        additional_width = font_metrics.horizontalAdvance(' ms')
        self.rtt_labels.setFixedWidth(font_metrics.horizontalAdvance('9' * max_digits) + additional_width)

        list_layout.addWidget(self.bridges_list)
        list_layout.addWidget(self.rtt_labels)
        
        # Link scroll bars
        self.bridges_list.verticalScrollBar().valueChanged.connect(
            self.rtt_labels.verticalScrollBar().setValue
        )
        self.rtt_labels.verticalScrollBar().valueChanged.connect(
            self.bridges_list.verticalScrollBar().setValue
        )
        
        display_layout.addLayout(list_layout)

        # Copy All Button
        copy_all_button = QPushButton('Copy All', self)
        copy_all_button.clicked.connect(self.copy_all_to_clipboard)
        display_layout.addWidget(copy_all_button)

        main_layout.addLayout(display_layout)

        # Console Output
        console_label = QLabel("Console Log:")
        self.console_display = QPlainTextEdit(self)
        self.console_display.setReadOnly(True)
        main_layout.addWidget(console_label)
        main_layout.addWidget(self.console_display)

    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)
        self.worker = BridgeCheckWorker(parent=self)
        self.worker.setBridgeData(cleaned_bridge_data)
        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):
        # Remove any existing latency values from the input
        clean_data = re.sub(r'\s+\d+(\.\d+)?\s*ms', '', bridge_data)
        return clean_data

    def is_valid_ip(self, ip):
        # Validate that the IP is in the correct format
        pattern = re.compile(r'^(?:\d{1,3}\.){3}\d{1,3}$')
        if pattern.match(ip):  
            return all(0 <= int(octet) <= 255 for octet in ip.split('.'))
        return False

    def add_filter(self):
        first_octet = self.first_octet_input.toPlainText().strip()
        if first_octet.isdigit() and 0 <= int(first_octet) <= 255:
            filter_entry = f"{first_octet}.*.*.*"
            if not self.active_filters_list.findItems(filter_entry, Qt.MatchExactly):
                self.active_filters_list.addItem(filter_entry)
                self.first_octet_input.clear()  # Clear the input field
            else:
                self.update_console("This filter is already in the list.")
        else:
            self.update_console("Invalid first octet. Please enter a number between 0 and 255.")

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

    def display_results(self, sorted_bridges):
        self.bridges_list.clear()
        self.rtt_labels.clear()

        # Load blacklist from the active filters list
        blacklist = [item.text() for item in self.active_filters_list.findItems("*", Qt.MatchWildcard)]

        # Checking if the Extract Clones option is selected
        extract_clones = self.extract_clones_checkbox.isChecked()

        unique_bridges = {}

        for bridge, latency in sorted_bridges:
            # Extract the first octet
            ip_port_match = re.search(r'(\d{1,3})\.\d+\.\d+\.\d+', bridge)
            if ip_port_match:
                first_octet = ip_port_match.group(1)
                filter_match = any(first_octet == filter_entry.split('.')[0] for filter_entry in blacklist)

                if not filter_match:  # Only add if it doesn't match the blacklist
                    if extract_clones and first_octet not in unique_bridges:
                        unique_bridges[first_octet] = (bridge, latency)

        # Display the remaining bridges
        for bridge, latency in unique_bridges.values():
            self.bridges_list.addItem(bridge)
            self.rtt_labels.addItem(f"{latency:.2f} ms")

    def copy_all_to_clipboard(self):
        clipboard = QApplication.clipboard()
        results = []
        for i in range(self.bridges_list.count()):
            bridge = self.bridges_list.item(i).text()
            results.append(bridge)
        clipboard.setText('\n'.join(results))


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)
        unique_bridges = {bridge: latency for bridge, latency in responsive_bridges}
        sorted_bridges = sorted(unique_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.append((bridge_string, latency))
        return responsive_bridges

    def extract_bridges(self, bridge_data):
        # Regex pattern for Type One and Type Two bridges
        pattern = re.compile(
            r'(?:(\d{1,3}(?:\.\d{1,3}){3}:\d+\s+[A-F0-9]{40})|'  # Type One
            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+))',  # Type Two
            re.MULTILINE
        )
        # Combine matches to handle potential tuple scenarios from regex groups
        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)  # Disable Nagle's algorithm
        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_())


""".



also, need script to save in "bridges.cfg" all settings and filters applied properly. and maybe also open "bridges.cfg" on startup and link filters to UI if available.