This site is developed to XHTML and CSS2 W3C standards. If you see this paragraph, your browser does not support those standards and you need to upgrade. Visit WaSP for a variety of options.

php pastebin - collaborative irc debugging view php source

Paste #708

Posted by: modern icq
Posted on: 2026-06-29 20:48:04
Age: 15 hrs ago
Views: 11
#!/usr/bin/env python3
"""
Modern ICQ Client for Linux
Built with PyQt6 and custom ICQ protocol library
FIXED STATUS DETECTION ONLY
"""

import sys
import time
from datetime import datetime
from typing import Dict, List, Optional
from dataclasses import dataclass, field

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QTreeWidget, QTreeWidgetItem, QTextEdit, QLineEdit, QPushButton,
    QLabel, QSplitter, QFrame, QStackedWidget,
    QMessageBox, QDialog, QFormLayout, QDialogButtonBox, QComboBox,
    QScrollBar, QMenu
)
from PyQt6.QtCore import (
    Qt, QSize, pyqtSignal, QTimer, QThread, QDateTime
)
from PyQt6.QtGui import (
    QFont, QColor, QTextCursor, QTextCharFormat, QBrush,
    QPalette, QIcon, QPixmap
)

# Import ICQ library
from icq_client import (
    ICQClient, S_ONLINE, S_AWAY, S_NA, S_DND, S_OCCUPIED,
    S_INVISIBLE, S_FFC, XSTATUS_MOBILE, XSTATUS_NAMES,
    status_to_int, status_to_str, FEEDBAG_CLASS_BUDDY,
    FEEDBAG_CLASS_GROUP, FeedbagItem
)

# ================ Data Models ================

@dataclass
class Contact:
    uin: str
    nickname: str = ""
    status: int = 0
    xstatus_index: int = 0
    xstatus_title: str = ""
    xstatus_message: str = ""
    unread_messages: int = 0
    group_id: int = 0
    authorized: bool = False

@dataclass
class ContactGroup:
    id: int
    name: str
    contacts: List[str] = field(default_factory=list)

@dataclass
class ChatMessage:
    text: str
    sender: str
    timestamp: datetime
    is_outgoing: bool = False

# ================ Styles ================

MODERN_STYLE = """
/* Main window */
QMainWindow {
    background-color: #0d1117;
    color: #c9d1d9;
}

/* Tree widget */
QTreeWidget {
    background-color: #161b22;
    border: none;
    color: #c9d1d9;
    font-size: 13px;
    outline: none;
}

QTreeWidget::item {
    padding: 8px;
    border-bottom: 1px solid #21262d;
    min-height: 50px;
}

QTreeWidget::item:selected {
    background-color: #1f2937;
    border-left: 3px solid #7c3aed;
}

QTreeWidget::item:hover {
    background-color: #1c2128;
}

QTreeWidget::branch {
    background-color: #161b22;
}

/* FIX: Input field - explicit colors */
QLineEdit {
    background-color: #21262d;
    border: 2px solid #30363d;
    border-radius: 8px;
    padding: 10px 15px;
    color: #c9d1d9;
    font-size: 14px;
    selection-background-color: #7c3aed;
    selection-color: #ffffff;
}

QLineEdit:focus {
    border-color: #7c3aed;
    background-color: #21262d;
    color: #c9d1d9;
}

QLineEdit:disabled {
    background-color: #161b22;
    color: #484f58;
}

/* FIX: Placeholder text */
QLineEdit::placeholder {
    color: #484f58;
}

/* Text edit for messages */
QTextEdit {
    background-color: #0d1117;
    border: none;
    color: #c9d1d9;
    font-size: 14px;
    padding: 15px;
    selection-background-color: #7c3aed;
    selection-color: #ffffff;
}

/* Buttons */
QPushButton {
    background-color: #238636;
    border: 1px solid #2ea043;
    border-radius: 8px;
    padding: 8px 16px;
    color: #ffffff;
    font-weight: bold;
    font-size: 13px;
}

QPushButton:hover {
    background-color: #2ea043;
}

QPushButton:pressed {
    background-color: #238636;
}

QPushButton:disabled {
    background-color: #21262d;
    color: #484f58;
    border-color: #30363d;
}

/* Labels */
QLabel {
    color: #8b949e;
    font-size: 12px;
}

/* Scroll bars */
QScrollBar:vertical {
    background-color: #161b22;
    width: 8px;
    border-radius: 4px;
    margin: 0px;
}

QScrollBar::handle:vertical {
    background-color: #30363d;
    border-radius: 4px;
    min-height: 30px;
}

QScrollBar::handle:vertical:hover {
    background-color: #484f58;
}

QScrollBar::add-line:vertical, 
QScrollBar::sub-line:vertical {
    height: 0px;
}

QScrollBar:horizontal {
    height: 0px;
}

/* Splitter */
QSplitter::handle {
    background-color: #21262d;
    width: 2px;
}

/* Chat header */
QFrame#chatHeader {
    background-color: #161b22;
    border-bottom: 1px solid #21262d;
    padding: 10px;
}

/* Combo box */
QComboBox {
    background-color: #21262d;
    border: 2px solid #30363d;
    border-radius: 8px;
    padding: 8px 15px;
    color: #c9d1d9;
    font-size: 13px;
    min-width: 140px;
}

QComboBox:hover {
    border-color: #7c3aed;
}

QComboBox::drop-down {
    border: none;
    padding-right: 10px;
}

QComboBox QAbstractItemView {
    background-color: #21262d;
    color: #c9d1d9;
    selection-background-color: #7c3aed;
    border-radius: 4px;
    padding: 4px;
    outline: none;
}

QComboBox QAbstractItemView::item {
    padding: 8px 12px;
    min-height: 30px;
}

/* Menu */
QMenu {
    background-color: #21262d;
    color: #c9d1d9;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 4px;
}

QMenu::item {
    padding: 8px 20px;
    border-radius: 4px;
}

QMenu::item:selected {
    background-color: #7c3aed;
    color: #ffffff;
}

QMenu::separator {
    height: 1px;
    background-color: #30363d;
    margin: 4px 10px;
}

/* Dialog */
QDialog {
    background-color: #161b22;
    border: 1px solid #21262d;
}

/* Message box */
QMessageBox {
    background-color: #161b22;
    color: #c9d1d9;
}

QMessageBox QLabel {
    color: #c9d1d9;
    font-size: 14px;
}

QMessageBox QPushButton {
    min-width: 80px;
    padding: 8px 16px;
}

/* Progress bar */
QProgressBar {
    background-color: #21262d;
    border: none;
    border-radius: 4px;
    text-align: center;
    color: #c9d1d9;
    font-size: 12px;
}

QProgressBar::chunk {
    background-color: #7c3aed;
    border-radius: 4px;
}
"""

# ================ FIXED: Status Functions ================

def create_status_text(status: int) -> str:
    """Return correct status text - FIXED to properly detect Online"""
    
    # Offline
    if status == 0xFFFFFFFF or status == -1:
        return "Offline"
    
    # Online - status 0 or S_ONLINE (0x00000000)
    if status == 0 or status == S_ONLINE:
        return "Online"
    
    # Use status_to_int for normalization
    actual_status = status_to_int(status)
    
    # Explicit checks for each status
    if actual_status == S_ONLINE or actual_status == 0:
        return "Online"
    elif actual_status == S_AWAY:
        return "Away"
    elif actual_status == S_NA:
        return "N/A"
    elif actual_status == S_DND:
        return "DND"
    elif actual_status == S_OCCUPIED:
        return "Occupied"
    elif actual_status == S_INVISIBLE:
        return "Invisible"
    elif actual_status == S_FFC:
        return "Free for Chat"
    else:
        # FIX: If we can't determine the status, default to Online
        # instead of showing N/A for unknown but online users
        return "Online"

def get_status_color(status: int) -> str:
    """Get color for status indicator"""
    if status == 0xFFFFFFFF or status == -1:
        return "#484f58"  # Gray for offline
    
    actual_status = status_to_int(status)
    
    colors = {
        S_ONLINE: "#3fb950",     # Green
        S_AWAY: "#d2991d",      # Yellow
        S_NA: "#f85149",        # Red
        S_DND: "#f85149",       # Red
        S_OCCUPIED: "#f0883e",  # Orange
        S_INVISIBLE: "#484f58", # Gray
        S_FFC: "#58a6ff",      # Blue
    }
    
    # FIX: Default to green (Online) instead of returning None
    return colors.get(actual_status, "#3fb950")

# ================ Worker Thread ================

class ICQWorker(QThread):
    """Thread for handling ICQ connection and events"""
    login_successful = pyqtSignal()
    login_failed = pyqtSignal(str)
    message_received = pyqtSignal(str, str)
    status_changed = pyqtSignal(str, int)
    user_offline = pyqtSignal(str)
    auth_request = pyqtSignal(str, str)
    contact_list_received = pyqtSignal(list)
    roster_loaded = pyqtSignal()
    user_info_received = pyqtSignal(str, dict)
    disconnected = pyqtSignal()
    message_ack = pyqtSignal(str, int)
    
    def __init__(self, uin: int, password: str):
        super().__init__()
        self.uin = uin
        self.password = password
        self.client = ICQClient()
        self.running = True
        self._info_buffer = {}
        self._keepalive_timer = time.time()
        self._last_message_time = 0
        
    def setup_client(self):
        """Configure ICQ client callbacks"""
        self.client.icq_server = "kicq.ru"
        self.client.icq_port = "5190"
        self.client.uin = self.uin
        self.client.password = self.password
        
        def on_login(cl):
            print("=== LOGIN SUCCESSFUL ===")
            self._keepalive_timer = time.time()
            time.sleep(0.5)
            if self.client.logged_in:
                cl.set_status(S_ONLINE)
                time.sleep(0.3)
                self._request_roster_safe()
            
        def on_message(cl, msg, uin):
            print(f"=== MESSAGE from {uin}")
            self.message_received.emit(uin, msg)
            
        def on_status(cl, uin, raw_status):
            # FIX: Better status normalization
            print(f"=== RAW STATUS from {uin}: {raw_status} (0x{raw_status:08X})")
            
            # Normalize status
            if raw_status == 0 or raw_status == S_ONLINE:
                status = S_ONLINE
            elif raw_status == 0xFFFFFFFF:
                status = 0xFFFFFFFF
            elif raw_status == S_INVISIBLE:
                status = S_INVISIBLE
            else:
                # Use status_to_int for other cases
                status = status_to_int(raw_status)
                # If result is 0, it's Online
                if status == 0:
                    status = S_ONLINE
            
            print(f"=== NORMALIZED STATUS: {uin} -> {create_status_text(status)}")
            self.status_changed.emit(uin, status)
            
        def on_offline(cl, uin):
            print(f"=== OFFLINE: {uin}")
            self.user_offline.emit(uin)
            
        def on_auth(cl, uin, reason):
            print(f"=== AUTH from {uin}")
            self.auth_request.emit(uin, reason)
            try:
                cl.send_auth_response(uin, True)
            except:
                pass
            
        def on_contact_list(cl, name, contacts):
            print(f"=== CONTACTS: {len(cl.feedbag_items)} items")
            if cl.feedbag_items:
                self.contact_list_received.emit(cl.feedbag_items)
                self.roster_loaded.emit()
                QTimer.singleShot(1000, lambda: self._request_all_statuses(cl))
            
        def on_user_info(cl, uin, info_type, info_data):
            if uin not in self._info_buffer:
                self._info_buffer[uin] = {}
            if isinstance(info_data, dict):
                self._info_buffer[uin].update(info_data)
            self.user_info_received.emit(uin, self._info_buffer[uin])
            
        def on_msg_ack(cl, uin, msg_id):
            self.message_ack.emit(uin, msg_id)
        
        self.client.on_login = on_login
        self.client.on_message_recv = on_message
        self.client.on_status_change = on_status
        self.client.on_user_offline = on_offline
        self.client.on_auth_request = on_auth
        self.client.on_contact_list_recv = on_contact_list
        self.client.on_user_general_info = on_user_info
        self.client.on_user_info_more = on_user_info
        self.client.on_msg_ack = on_msg_ack
        
    def _request_roster_safe(self):
        if not self.client.logged_in:
            return
        print("=== Requesting roster ===")
        try:
            self.client.request_feedbag()
        except Exception as e:
            print(f"Feedbag error: {e}")
            
    def _request_all_statuses(self, cl):
        if not self.running or not self.client.logged_in:
            return
            
        contacts_to_query = []
        for item in cl.feedbag_items:
            if item.class_id == FEEDBAG_CLASS_BUDDY and item.name.isdigit():
                contacts_to_query.append(int(item.name))
                
        print(f"Requesting info for {len(contacts_to_query)} contacts")
        
        for uin_int in contacts_to_query[:20]:
            if not self.running or not self.client.logged_in:
                break
            try:
                cl.request_info_short(uin_int)
                time.sleep(0.3)
            except Exception as e:
                print(f"Info error for {uin_int}: {e}")
        
    def run(self):
        self.setup_client()
        
        try:
            print(f"Connecting to {self.client.icq_server}:{self.client.icq_port}")
            self.client.login(S_ONLINE)
            
            if not self.client.wait_login(90):
                self.login_failed.emit("Login timeout")
                return
                
            print("Login successful")
            
            if self.client.logged_in:
                self.client.set_status(S_ONLINE)
            
            self.login_successful.emit()
            
            while self.running:
                if not self.client.logged_in:
                    print("Connection lost")
                    if self.running:
                        self.disconnected.emit()
                    break
                
                current_time = time.time()
                if current_time - self._keepalive_timer >= 40:
                    try:
                        if current_time - self._last_message_time > 5:
                            self.client.send_keep_alive()
                            self._keepalive_timer = current_time
                    except Exception as e:
                        print(f"Keep-alive error: {e}")
                
                time.sleep(0.5)
                
        except Exception as e:
            print(f"Worker error: {e}")
            import traceback
            traceback.print_exc()
            if self.running:
                self.login_failed.emit(str(e))
        finally:
            print("Worker stopping...")
                
    def send_message_safe(self, uin: str, msg: str) -> bool:
        if not self.client.logged_in:
            return False
        try:
            self.client.set_status(S_ONLINE)
            self.client.send_message(uin, msg)
            self._last_message_time = time.time()
            self._keepalive_timer = time.time()
            print(f"Message sent to {uin}")
            return True
        except Exception as e:
            print(f"Send error: {e}")
            return False
                
    def stop(self):
        print("Stopping worker...")
        self.running = False
        time.sleep(0.3)

# ================ Main Window ================

class ModernICQClient(QMainWindow):
    def __init__(self):
        super().__init__()
        self.contacts: Dict[str, Contact] = {}
        self.groups: Dict[int, ContactGroup] = {}
        self.chats: Dict[str, List[ChatMessage]] = {}
        self.current_chat_uin: Optional[str] = None
        self.worker: Optional[ICQWorker] = None
        self.loading_dialog = None
        self.init_ui()
        
    def init_ui(self):
        """Initialize the user interface"""
        self.setWindowTitle("Modern ICQ")
        self.setGeometry(100, 100, 1100, 700)
        self.setStyleSheet(MODERN_STYLE)
        
        central = QWidget()
        self.setCentralWidget(central)
        
        main_layout = QHBoxLayout(central)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        
        splitter = QSplitter(Qt.Orientation.Horizontal)
        
        self.create_contact_panel(splitter)
        self.create_chat_panel(splitter)
        
        splitter.setSizes([320, 780])
        main_layout.addWidget(splitter)
        
        QTimer.singleShot(100, self.show_login_dialog)
        
    def create_contact_panel(self, parent):
        """Create the contact list panel"""
        panel = QWidget()
        layout = QVBoxLayout(panel)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        
        # Header
        header = QFrame()
        header.setFixedHeight(50)
        header.setStyleSheet("""
            QFrame {
                background-color: #161b22;
                border-bottom: 1px solid #21262d;
            }
        """)
        header_layout = QHBoxLayout(header)
        header_layout.setContentsMargins(15, 0, 15, 0)
        
        title = QLabel("Messages")
        title.setStyleSheet("font-weight: bold; font-size: 16px; color: #c9d1d9;")
        header_layout.addWidget(title)
        header_layout.addStretch()
        
        add_btn = QPushButton("+")
        add_btn.setFixedSize(30, 30)
        add_btn.setStyleSheet("font-size: 18px;")
        add_btn.clicked.connect(self.show_add_contact_dialog)
        header_layout.addWidget(add_btn)
        
        layout.addWidget(header)
        
        # Search
        search_container = QWidget()
        search_container.setStyleSheet("background-color: #161b22;")
        search_layout = QHBoxLayout(search_container)
        search_layout.setContentsMargins(10, 5, 10, 10)
        
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("Search contacts...")
        self.search_input.textChanged.connect(self.filter_contacts)
        self.search_input.setMinimumHeight(35)
        self.search_input.setStyleSheet("""
            QLineEdit {
                background-color: #21262d;
                border: 1px solid #30363d;
                border-radius: 6px;
                padding: 8px 12px;
                color: #c9d1d9;
                font-size: 13px;
            }
            QLineEdit:focus {
                border-color: #7c3aed;
                color: #c9d1d9;
            }
            QLineEdit::placeholder {
                color: #484f58;
            }
        """)
        search_layout.addWidget(self.search_input)
        
        layout.addWidget(search_container)
        
        # Status selector
        status_container = QWidget()
        status_container.setStyleSheet("background-color: #161b22;")
        status_layout = QHBoxLayout(status_container)
        status_layout.setContentsMargins(10, 0, 10, 5)
        
        self.status_combo = QComboBox()
        self.status_combo.addItem("Online", S_ONLINE)
        self.status_combo.addItem("Away", S_AWAY)
        self.status_combo.addItem("N/A", S_NA)
        self.status_combo.addItem("DND", S_DND)
        self.status_combo.addItem("Invisible", S_INVISIBLE)
        self.status_combo.currentIndexChanged.connect(self.change_status)
        self.status_combo.setMinimumHeight(35)
        self.status_combo.setStyleSheet("""
            QComboBox {
                background-color: #21262d;
                border: 1px solid #30363d;
                border-radius: 6px;
                padding: 8px 12px;
                color: #c9d1d9;
                font-size: 13px;
            }
            QComboBox:hover {
                border-color: #7c3aed;
            }
            QComboBox QAbstractItemView {
                background-color: #21262d;
                color: #c9d1d9;
                selection-background-color: #7c3aed;
            }
        """)
        status_layout.addWidget(self.status_combo)
        
        layout.addWidget(status_container)
        
        # Contact tree
        self.contact_tree = QTreeWidget()
        self.contact_tree.setHeaderHidden(True)
        self.contact_tree.setIndentation(15)
        self.contact_tree.itemClicked.connect(self.contact_selected)
        self.contact_tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.contact_tree.customContextMenuRequested.connect(self.show_contact_context_menu)
        layout.addWidget(self.contact_tree)
        
        parent.addWidget(panel)
        
    def create_chat_panel(self, parent):
        """Create the chat panel"""
        panel = QWidget()
        layout = QVBoxLayout(panel)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        
        self.chat_stack = QStackedWidget()
        
        # Empty state
        empty_widget = QWidget()
        empty_widget.setStyleSheet("background-color: #0d1117;")
        empty_layout = QVBoxLayout(empty_widget)
        empty_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        empty_label = QLabel("Select a chat to start messaging")
        empty_label.setStyleSheet("font-size: 16px; color: #484f58;")
        empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        empty_layout.addWidget(empty_label)
        
        self.chat_stack.addWidget(empty_widget)
        
        # Chat view
        chat_widget = QWidget()
        chat_layout = QVBoxLayout(chat_widget)
        chat_layout.setContentsMargins(0, 0, 0, 0)
        chat_layout.setSpacing(0)
        
        # Chat header
        self.chat_header = QFrame()
        self.chat_header.setFixedHeight(55)
        self.chat_header.setStyleSheet("""
            QFrame {
                background-color: #161b22;
                border-bottom: 1px solid #21262d;
            }
        """)
        header_layout = QHBoxLayout(self.chat_header)
        header_layout.setContentsMargins(15, 0, 15, 0)
        
        info_layout = QVBoxLayout()
        info_layout.setSpacing(2)
        
        self.chat_title = QLabel("")
        self.chat_title.setStyleSheet("font-weight: bold; font-size: 14px; color: #c9d1d9;")
        info_layout.addWidget(self.chat_title)
        
        self.chat_status = QLabel("")
        self.chat_status.setStyleSheet("font-size: 11px;")
        info_layout.addWidget(self.chat_status)
        
        header_layout.addLayout(info_layout)
        header_layout.addStretch()
        
        chat_layout.addWidget(self.chat_header)
        
        # Messages area
        self.messages_area = QTextEdit()
        self.messages_area.setReadOnly(True)
        chat_layout.addWidget(self.messages_area)
        
        # Input area
        input_frame = QFrame()
        input_frame.setFixedHeight(65)
        input_frame.setStyleSheet("""
            QFrame {
                background-color: #161b22;
                border-top: 1px solid #21262d;
            }
        """)
        input_layout = QHBoxLayout(input_frame)
        input_layout.setContentsMargins(10, 10, 10, 10)
        
        self.message_input = QLineEdit()
        self.message_input.setPlaceholderText("Type a message...")
        self.message_input.returnPressed.connect(self.send_message)
        self.message_input.setMinimumHeight(40)
        self.message_input.setStyleSheet("""
            QLineEdit {
                background-color: #21262d;
                border: 1px solid #30363d;
                border-radius: 6px;
                padding: 8px 12px;
                color: #c9d1d9;
                font-size: 14px;
            }
            QLineEdit:focus {
                border-color: #7c3aed;
                color: #c9d1d9;
            }
            QLineEdit::placeholder {
                color: #484f58;
            }
        """)
        input_layout.addWidget(self.message_input)
        
        send_btn = QPushButton("Send")
        send_btn.setFixedSize(70, 40)
        send_btn.clicked.connect(self.send_message)
        input_layout.addWidget(send_btn)
        
        chat_layout.addWidget(input_frame)
        
        self.chat_stack.addWidget(chat_widget)
        
        layout.addWidget(self.chat_stack)
        
        parent.addWidget(panel)
        
    def show_login_dialog(self):
        """Show login dialog"""
        dialog = QDialog(self)
        dialog.setWindowTitle("Login to ICQ")
        dialog.setFixedSize(400, 280)
        
        layout = QVBoxLayout(dialog)
        layout.setSpacing(15)
        layout.setContentsMargins(30, 30, 30, 30)
        
        title = QLabel("Sign in to ICQ")
        title.setStyleSheet("font-size: 20px; font-weight: bold; color: #c9d1d9;")
        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(title)
        
        uin_input = QLineEdit()
        uin_input.setPlaceholderText("Enter your UIN")
        uin_input.setMinimumHeight(40)
        uin_input.setStyleSheet("""
            QLineEdit {
                background-color: #21262d;
                border: 1px solid #30363d;
                border-radius: 6px;
                padding: 10px 12px;
                color: #c9d1d9;
                font-size: 14px;
            }
            QLineEdit:focus {
                border-color: #7c3aed;
                color: #c9d1d9;
            }
            QLineEdit::placeholder {
                color: #484f58;
            }
        """)
        layout.addWidget(uin_input)
        
        pass_input = QLineEdit()
        pass_input.setPlaceholderText("Enter your password")
        pass_input.setEchoMode(QLineEdit.EchoMode.Password)
        pass_input.setMinimumHeight(40)
        pass_input.setStyleSheet(uin_input.styleSheet())
        layout.addWidget(pass_input)
        
        login_btn = QPushButton("Connect")
        login_btn.setMinimumHeight(40)
        login_btn.clicked.connect(dialog.accept)
        layout.addWidget(login_btn)
        
        if dialog.exec() == QDialog.DialogCode.Accepted:
            try:
                uin = int(uin_input.text())
                password = pass_input.text()
                if uin and password:
                    self.show_loading_dialog()
                    self.start_connection(uin, password)
            except ValueError:
                QMessageBox.critical(self, "Error", "Invalid UIN")
                
    def show_loading_dialog(self):
        """Show loading dialog"""
        self.loading_dialog = QDialog(self)
        self.loading_dialog.setWindowTitle("Connecting...")
        self.loading_dialog.setFixedSize(300, 100)
        
        layout = QVBoxLayout(self.loading_dialog)
        label = QLabel("Connecting to ICQ...")
        label.setStyleSheet("color: #c9d1d9; font-size: 16px;")
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(label)
        
        self.loading_dialog.show()
                
    def start_connection(self, uin: int, password: str):
        """Start ICQ connection"""
        if self.worker:
            self.worker.stop()
            self.worker.wait(2000)
            
        self.worker = ICQWorker(uin, password)
        self.worker.login_successful.connect(self.on_login_success)
        self.worker.login_failed.connect(self.on_login_failed)
        self.worker.message_received.connect(self.on_message)
        self.worker.status_changed.connect(self.on_contact_status_change)
        self.worker.user_offline.connect(self.on_contact_offline)
        self.worker.roster_loaded.connect(self.on_roster_loaded)
        self.worker.contact_list_received.connect(self.process_feedbag)
        self.worker.user_info_received.connect(self.update_contact_info)
        self.worker.disconnected.connect(self.on_disconnected)
        self.worker.message_ack.connect(self.on_message_ack)
        self.worker.start()
        
    def on_login_success(self):
        """Handle successful login"""
        print("=== Login successful ===")
        self.setWindowTitle(f"Modern ICQ - {self.worker.uin}")
        if self.loading_dialog:
            self.loading_dialog.close()
            self.loading_dialog = None
            
    def on_login_failed(self, error_msg: str):
        """Handle login failure"""
        if self.loading_dialog:
            self.loading_dialog.close()
            self.loading_dialog = None
        QMessageBox.critical(self, "Login Failed", error_msg)
        
    def on_disconnected(self):
        """Handle disconnection"""
        if self.worker and self.worker.running:
            QMessageBox.warning(self, "Disconnected", "Connection lost.")
        
    def process_feedbag(self, feedbag_items: List[FeedbagItem]):
        """Process feedbag items"""
        if not feedbag_items:
            return
            
        existing_statuses = {}
        for uin, contact in self.contacts.items():
            existing_statuses[uin] = contact.status
            
        self.contacts.clear()
        self.groups.clear()
        
        for item in feedbag_items:
            if item.class_id == FEEDBAG_CLASS_GROUP:
                gid = item.group_id if item.group_id > 0 else item.item_id
                gname = item.name if item.name else "General"
                if gid not in self.groups:
                    self.groups[gid] = ContactGroup(id=gid, name=gname)
                    
        if 0 not in self.groups:
            self.groups[0] = ContactGroup(id=0, name="Contacts")
            
        for item in feedbag_items:
            if item.class_id == FEEDBAG_CLASS_BUDDY and item.name.isdigit():
                uin = item.name
                gid = item.group_id if item.group_id in self.groups else 0
                # FIX: Default to S_ONLINE for new contacts
                old_status = existing_statuses.get(uin, S_ONLINE)
                
                contact = Contact(
                    uin=uin,
                    group_id=gid,
                    nickname=uin,
                    status=old_status
                )
                self.contacts[uin] = contact
                self.groups[gid].contacts.append(uin)
                
        print(f"Loaded {len(self.contacts)} contacts")
        self.build_contact_tree()
        
    def build_contact_tree(self):
        """Build contact tree"""
        self.contact_tree.clear()
        
        for group_id, group in self.groups.items():
            if not group.contacts:
                continue
                
            group_item = QTreeWidgetItem()
            group_item.setText(0, f"{group.name} ({len(group.contacts)})")
            group_item.setData(0, Qt.ItemDataRole.UserRole, f"group_{group_id}")
            
            font = QFont()
            font.setBold(True)
            font.setPointSize(12)
            group_item.setFont(0, font)
            group_item.setForeground(0, QColor("#8b949e"))
            
            for uin in group.contacts:
                if uin in self.contacts:
                    contact = self.contacts[uin]
                    contact_item = QTreeWidgetItem()
                    
                    name = contact.nickname if contact.nickname != uin else f"User {uin}"
                    
                    if contact.unread_messages > 0:
                        name = f"● {name}"
                        contact_item.setForeground(0, QColor("#c9d1d9"))
                        item_font = QFont()
                        item_font.setBold(True)
                        item_font.setPointSize(12)
                        contact_item.setFont(0, item_font)
                    else:
                        contact_item.setForeground(0, QColor("#8b949e"))
                        item_font = QFont()
                        item_font.setPointSize(12)
                        contact_item.setFont(0, item_font)
                    
                    contact_item.setText(0, name)
                    contact_item.setData(0, Qt.ItemDataRole.UserRole, uin)
                    
                    # FIX: Use the corrected status functions
                    status_text = create_status_text(contact.status)
                    status_color = get_status_color(contact.status)
                    contact_item.setText(1, status_text)
                    contact_item.setForeground(1, QColor(status_color))
                    
                    if contact.unread_messages > 0:
                        contact_item.setText(2, str(contact.unread_messages))
                        contact_item.setForeground(2, QColor("#7c3aed"))
                        badge_font = QFont()
                        badge_font.setBold(True)
                        contact_item.setFont(2, badge_font)
                    
                    group_item.addChild(contact_item)
                    
            self.contact_tree.addTopLevelItem(group_item)
            group_item.setExpanded(True)
            
    def on_roster_loaded(self):
        """Handle roster loading"""
        print(f"Roster loaded: {len(self.contacts)} contacts")
        
    def update_contact_info(self, uin: str, info: dict):
        """Update contact info"""
        if uin in self.contacts:
            contact = self.contacts[uin]
            if 'nick' in info and info['nick']:
                contact.nickname = info['nick']
            elif 'firstName' in info:
                first = info.get('firstName', '')
                last = info.get('lastName', '')
                if first or last:
                    contact.nickname = f"{first} {last}".strip()
            self.build_contact_tree()
            
    def on_message(self, uin: str, msg: str):
        """Handle incoming message"""
        print(f"Message from {uin}")
        
        if uin not in self.contacts:
            contact = Contact(uin=uin, nickname=f"User {uin}", status=S_ONLINE)
            self.contacts[uin] = contact
            if 0 not in self.groups:
                self.groups[0] = ContactGroup(id=0, name="Contacts")
            self.groups[0].contacts.append(uin)
            self.build_contact_tree()
        else:
            self.contacts[uin].status = S_ONLINE
            
        chat_msg = ChatMessage(
            text=msg,
            sender=uin,
            timestamp=datetime.now(),
            is_outgoing=False
        )
        
        if uin not in self.chats:
            self.chats[uin] = []
        self.chats[uin].append(chat_msg)
        
        if self.current_chat_uin == uin:
            self.display_message(chat_msg)
        else:
            if uin in self.contacts:
                self.contacts[uin].unread_messages += 1
                self.build_contact_tree()
                
    def on_message_ack(self, uin: str, msg_id: int):
        """Handle message acknowledgement"""
        pass
        
    def on_contact_status_change(self, uin: str, status: int):
        """Handle status change"""
        # FIX: Log the actual status being set
        print(f"Status change for {uin}: {status} -> {create_status_text(status)}")
        if uin in self.contacts:
            self.contacts[uin].status = status
            self.build_contact_tree()
            
            if self.current_chat_uin == uin:
                status_text = create_status_text(status)
                status_color = get_status_color(status)
                self.chat_status.setText(status_text)
                self.chat_status.setStyleSheet(f"color: {status_color}; font-size: 12px;")
                
    def on_contact_offline(self, uin: str):
        """Handle contact offline"""
        if uin in self.contacts:
            self.contacts[uin].status = 0xFFFFFFFF
            self.build_contact_tree()
            
    def filter_contacts(self, text: str):
        """Filter contacts"""
        for i in range(self.contact_tree.topLevelItemCount()):
            group_item = self.contact_tree.topLevelItem(i)
            visible = 0
            
            for j in range(group_item.childCount()):
                child = group_item.child(j)
                uin = child.data(0, Qt.ItemDataRole.UserRole)
                contact = self.contacts.get(uin)
                
                if contact:
                    name = contact.nickname or contact.uin
                    matches = text.lower() in name.lower() or text in uin
                    child.setHidden(not matches)
                    if matches:
                        visible += 1
                        
            group_item.setHidden(visible == 0)
            
    def contact_selected(self, item: QTreeWidgetItem, column: int):
        """Handle contact selection"""
        uin = item.data(0, Qt.ItemDataRole.UserRole)
        if uin and not uin.startswith("group_"):
            self.open_chat(uin)
            
    def show_contact_context_menu(self, pos):
        """Context menu"""
        item = self.contact_tree.itemAt(pos)
        if not item:
            return
            
        uin = item.data(0, Qt.ItemDataRole.UserRole)
        if not uin or uin.startswith("group_"):
            return
            
        menu = QMenu(self)
        menu.addAction("Open Chat", lambda: self.open_chat(uin))
        menu.addSeparator()
        menu.addAction("Remove Contact", lambda: self.remove_contact(uin))
        menu.exec(self.contact_tree.viewport().mapToGlobal(pos))
        
    def remove_contact(self, uin: str):
        """Remove contact"""
        reply = QMessageBox.question(
            self, "Remove", f"Remove {uin}?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        
        if reply == QMessageBox.StandardButton.Yes:
            if self.worker and self.worker.client.logged_in:
                try:
                    self.worker.client.remove_contact(int(uin))
                    if uin in self.contacts:
                        del self.contacts[uin]
                    for group in self.groups.values():
                        if uin in group.contacts:
                            group.contacts.remove(uin)
                    self.build_contact_tree()
                except Exception as e:
                    QMessageBox.critical(self, "Error", str(e))
            
    def open_chat(self, uin: str):
        """Open chat with contact"""
        self.current_chat_uin = uin
        self.chat_stack.setCurrentIndex(1)
        
        contact = self.contacts.get(uin, Contact(uin=uin, status=S_ONLINE))
        name = contact.nickname if contact.nickname != uin else f"User {uin}"
        self.chat_title.setText(name)
        
        # FIX: Use corrected status functions
        status_text = create_status_text(contact.status)
        status_color = get_status_color(contact.status)
        self.chat_status.setText(status_text)
        self.chat_status.setStyleSheet(f"color: {status_color}; font-size: 12px;")
        
        self.messages_area.clear()
        if uin in self.chats:
            for msg in self.chats[uin]:
                self.display_message(msg)
                
        if uin in self.contacts:
            self.contacts[uin].unread_messages = 0
            self.build_contact_tree()
            
        self.message_input.setFocus()
        
    def display_message(self, msg: ChatMessage):
        """Display message in chat"""
        time_str = msg.timestamp.strftime("%H:%M")
        
        if msg.is_outgoing:
            bg_color = "#238636"
            text_color = "#ffffff"
            align = "right"
        else:
            bg_color = "#21262d"
            text_color = "#c9d1d9"
            align = "left"
            
        html = f"""
        <div style="margin: 8px 0; text-align: {align};">
            <div style="display: inline-block; max-width: 70%; 
                        background-color: {bg_color}; 
                        color: {text_color};
                        padding: 10px 15px; 
                        border-radius: 12px;
                        box-shadow: 0 1px 2px rgba(0,0,0,0.3);">
                <div style="margin-bottom: 4px; line-height: 1.4;">{msg.text}</div>
                <div style="font-size: 10px; color: {text_color}99; text-align: right;">
                    {time_str}
                </div>
            </div>
        </div>
        """
        
        self.messages_area.append(html)
        scrollbar = self.messages_area.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())
        
    def send_message(self):
        """Send message"""
        if not self.current_chat_uin or not self.message_input.text():
            return
            
        uin = self.current_chat_uin
        text = self.message_input.text()
        
        msg = ChatMessage(
            text=text,
            sender=str(self.worker.uin) if self.worker else "me",
            timestamp=datetime.now(),
            is_outgoing=True
        )
        
        if uin not in self.chats:
            self.chats[uin] = []
        self.chats[uin].append(msg)
        
        self.display_message(msg)
        
        if self.worker:
            success = self.worker.send_message_safe(uin, text)
            if not success:
                QMessageBox.warning(self, "Error", "Failed to send message.")
            
        self.message_input.clear()
        self.message_input.setFocus()
        
    def change_status(self):
        """Change user status"""
        if self.worker and self.worker.client.logged_in:
            status = self.status_combo.currentData()
            try:
                self.worker.client.set_status(status)
            except Exception as e:
                print(f"Status change error: {e}")
            
    def show_add_contact_dialog(self):
        """Add contact dialog"""
        dialog = QDialog(self)
        dialog.setWindowTitle("Add Contact")
        dialog.setFixedSize(350, 200)
        
        layout = QVBoxLayout(dialog)
        layout.setContentsMargins(30, 30, 30, 30)
        
        title = QLabel("Add New Contact")
        title.setStyleSheet("font-size: 18px; font-weight: bold; color: #c9d1d9;")
        layout.addWidget(title)
        
        uin_input = QLineEdit()
        uin_input.setPlaceholderText("Enter UIN number")
        uin_input.setMinimumHeight(40)
        uin_input.setStyleSheet("""
            QLineEdit {
                background-color: #21262d;
                border: 1px solid #30363d;
                border-radius: 6px;
                padding: 10px 12px;
                color: #c9d1d9;
                font-size: 14px;
                margin: 10px 0;
            }
            QLineEdit:focus {
                border-color: #7c3aed;
                color: #c9d1d9;
            }
            QLineEdit::placeholder {
                color: #484f58;
            }
        """)
        layout.addWidget(uin_input)
        
        add_btn = QPushButton("Add Contact")
        add_btn.setMinimumHeight(40)
        add_btn.clicked.connect(dialog.accept)
        layout.addWidget(add_btn)
        
        if dialog.exec() == QDialog.DialogCode.Accepted:
            uin = uin_input.text().strip()
            if uin and uin.isdigit():
                if self.worker and self.worker.client.logged_in:
                    try:
                        self.worker.client.add_contact(int(uin))
                        QMessageBox.information(self, "Success", f"Contact {uin} added!")
                    except Exception as e:
                        QMessageBox.critical(self, "Error", f"Failed: {e}")
                    
    def closeEvent(self, event):
        """Handle close"""
        print("Closing...")
        if self.worker:
            self.worker.stop()
            self.worker.wait(2000)
        event.accept()

# ================ Main ================

def main():
    import os
    os.environ["QT_LOGGING_RULES"] = "*.debug=false"
    
    app = QApplication(sys.argv)
    app.setApplicationName("Modern ICQ")
    app.setStyle("Fusion")
    
    palette = QPalette()
    palette.setColor(QPalette.ColorRole.Window, QColor("#0d1117"))
    palette.setColor(QPalette.ColorRole.WindowText, QColor("#c9d1d9"))
    palette.setColor(QPalette.ColorRole.Base, QColor("#0d1117"))
    palette.setColor(QPalette.ColorRole.Text, QColor("#c9d1d9"))
    app.setPalette(palette)
    
    window = ModernICQClient()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Download raw | Create new paste

© BitByByte, 2026.
Downgrade Counter