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 #67

Posted by: dsconnect_realease
Posted on: 2025-10-28 20:37:55
Age: 27 days ago
Views: 31
# chat_voice_client_with_custom_themes.py
import socket
import threading
import time
import queue
import os
import json

import numpy as np
import sounddevice as sd
import opuslib
import requests

import ttkbootstrap as tb
from ttkbootstrap.constants import *
from tkinter import simpledialog, messagebox, scrolledtext, filedialog, ttk
import tkinter as tk

# ---------------- Config ----------------
CONFIG_FILE = "client_config.json"

client_socket = None
stop_event = threading.Event()
ignore_list = []

# Voice (Opus + sounddevice)
VOICE_SERVER_IP = "hoho.ws"
VOICE_SERVER_PORT = 50007
RATE = 16000
CHANNELS = 1
FRAME_SIZE = 960
QUEUE_MAXSIZE = 50

voice_sock = None
encoder = opuslib.Encoder(RATE, CHANNELS, application="voip")
decoder = opuslib.Decoder(RATE, CHANNELS)
audio_queue = queue.Queue(maxsize=QUEUE_MAXSIZE)
send_audio = False
sd_stream = None

# HTTP upload server
UPLOAD_SERVER_URL = ""  # Настраивается через меню

# User themes
user_themes = {}
current_theme_name = "cyborg"

# ---------------- Config persistence ----------------
def save_config():
    cfg = {
        "voice_server_ip": VOICE_SERVER_IP,
        "voice_server_port": VOICE_SERVER_PORT,
        "upload_server_url": UPLOAD_SERVER_URL,
        "ignore_list": ignore_list,
        "user_themes": user_themes,
        "current_theme_name": current_theme_name
    }
    with open(CONFIG_FILE, "w", encoding="utf-8") as f:
        json.dump(cfg, f, indent=2)

def load_config():
    global VOICE_SERVER_IP, VOICE_SERVER_PORT, UPLOAD_SERVER_URL
    global ignore_list, user_themes, current_theme_name
    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                cfg = json.load(f)
            VOICE_SERVER_IP = cfg.get("voice_server_ip", VOICE_SERVER_IP)
            VOICE_SERVER_PORT = cfg.get("voice_server_port", VOICE_SERVER_PORT)
            UPLOAD_SERVER_URL = cfg.get("upload_server_url", UPLOAD_SERVER_URL)
            ignore_list = cfg.get("ignore_list", ignore_list)
            user_themes = cfg.get("user_themes", user_themes)
            current_theme_name = cfg.get("current_theme_name", current_theme_name)
        except Exception as e:
            print("Ошибка загрузки конфигурации:", e)

load_config()

# ---------------- Chat functions ----------------
def connect_to_server():
    global client_socket
    ip = simpledialog.askstring("Подключение", "Введите IP сервера:")
    port = simpledialog.askinteger("Подключение", "Введите порт:")
    if not ip or not port:
        return
    try:
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((ip, port))
        threading.Thread(target=receive_messages, daemon=True).start()
        threading.Thread(target=send_keepalive, daemon=True).start()
        chat_box.insert(tk.END, f"[+] Подключено к {ip}:{port}\n")
        chat_box.see(tk.END)
    except Exception as e:
        messagebox.showerror("Ошибка подключения", str(e))

def receive_messages():
    global client_socket
    while not stop_event.is_set():
        try:
            data = client_socket.recv(4096)
            if not data:
                break
            message = data.decode('utf-8', errors='ignore').strip()
            if message == "*Ping!*":
                continue
            if any(word in message for word in ignore_list):
                continue
            chat_box.insert(tk.END, message + "\n")
            chat_box.see(tk.END)
        except Exception:
            break

def send_keepalive():
    while not stop_event.is_set():
        try:
            if client_socket:
                client_socket.send(b"/")
        except:
            break
        time.sleep(5)

def send_message(event=None):
    if not client_socket:
        chat_box.insert(tk.END, "[!] Не подключено к серверу\n")
        chat_box.see(tk.END)
        return
    msg = msg_entry.get()
    if msg:
        try:
            client_socket.send(msg.encode('utf-8'))
            msg_entry.delete(0, tk.END)
        except Exception:
            chat_box.insert(tk.END, "[!] Ошибка отправки\n")
            chat_box.see(tk.END)

# ---------------- Menu actions ----------------
def menu_login():
    user = simpledialog.askstring("Логин", "Введите логин:")
    password = simpledialog.askstring("Логин", "Введите пароль:", show="*")
    if user and password and client_socket:
        client_socket.send(f"/login {user} {password}".encode('utf-8'))

def menu_register():
    user = simpledialog.askstring("Регистрация", "Введите логин:")
    password = simpledialog.askstring("Регистрация", "Введите пароль:", show="*")
    if user and password and client_socket:
        client_socket.send(f"/register {user} {password}".encode('utf-8'))

def menu_pm():
    recipient = simpledialog.askstring("Приватное сообщение", "Кому отправить:")
    text = simpledialog.askstring("Приватное сообщение", "Текст сообщения:")
    if recipient and text and client_socket:
        client_socket.send(f"/pm {recipient} {text}".encode('utf-8'))

def menu_join():
    channel = simpledialog.askstring("Вход на канал", "Введите название канала:")
    if channel and client_socket:
        client_socket.send(f"/join_server {channel}".encode('utf-8'))

def menu_ignore():
    global ignore_list
    ignore_text = simpledialog.askstring("Игнор-лист", "Введите слова через запятую:")
    if ignore_text:
        ignore_list = [w.strip() for w in ignore_text.split(",") if w.strip()]
        chat_box.insert(tk.END, f"[+] Игнор обновлён: {ignore_list}\n")
        chat_box.see(tk.END)
        save_config()

def menu_theme(theme_name: str):
    global current_theme_name
    if theme_name in user_themes:
        theme_config = user_themes[theme_name]
        root.style.configure("TEntry", foreground=theme_config.get("fg", "white"),
                             background=theme_config.get("bg", "#1e1e1e"))
        root.style.configure("TLabel", foreground=theme_config.get("fg", "white"),
                             background=theme_config.get("bg", "#1e1e1e"))
        chat_box.config(bg=theme_config.get("bg", "#1e1e1e"),
                        fg=theme_config.get("fg", "white"))
    else:
        root.style.theme_use(theme_name)
    current_theme_name = theme_name
    save_config()

def add_user_theme():
    theme_name = simpledialog.askstring("Новая тема", "Введите имя темы:")
    theme_text = simpledialog.askstring("Новая тема", "Введите описание темы в формате JSON, например:\n{\"bg\":\"#202020\",\"fg\":\"#FFCC00\"}")
    if theme_name and theme_text:
        try:
            config = json.loads(theme_text)
            user_themes[theme_name] = config
            menu_theme(theme_name)
            save_config()
            chat_box.insert(tk.END, f"[+] Пользовательская тема '{theme_name}' добавлена и применена\n")
            chat_box.see(tk.END)
        except Exception as e:
            messagebox.showerror("Ошибка темы", f"Неверный формат темы: {e}")

# ---------------- Voice functions ----------------
def audio_callback(indata, frames, time_info, status):
    if send_audio:
        try:
            audio_queue.put_nowait(indata[:,0].copy())
        except queue.Full:
            pass

def audio_send_thread():
    global voice_sock
    while True:
        try:
            frame = audio_queue.get(timeout=0.1)
            if len(frame) < FRAME_SIZE:
                frame = np.pad(frame, (0, FRAME_SIZE - len(frame)), 'constant')
            pcm16 = (frame * 32767).astype(np.int16)
            packet = encoder.encode(pcm16.tobytes(), FRAME_SIZE)
            voice_sock.sendall(len(packet).to_bytes(4, 'big') + packet)
        except queue.Empty:
            continue
        except Exception as e:
            print("Send error:", e)
            break

def audio_receive_thread():
    global voice_sock
    try:
        with sd.OutputStream(samplerate=RATE, channels=CHANNELS, blocksize=FRAME_SIZE) as out_stream:
            buffer = b''
            while True:
                try:
                    data = voice_sock.recv(4096)
                    if not data:
                        break
                    buffer += data
                    while len(buffer) >= 4:
                        length = int.from_bytes(buffer[:4], 'big')
                        if len(buffer) < 4 + length:
                            break
                        packet = buffer[4:4+length]
                        buffer = buffer[4+length:]
                        try:
                            pcm = decoder.decode(packet, FRAME_SIZE)
                            audio = np.frombuffer(pcm, dtype=np.int16).astype(np.float32)/32768
                            out_stream.write(audio)
                        except opuslib.OpusError:
                            continue
                except Exception as e:
                    print("Receive error:", e)
                    break
    except Exception as e:
        print("Output stream error:", e)

def switch_device(device_name):
    global sd_stream
    if sd_stream:
        try:
            sd_stream.stop(); sd_stream.close()
        except:
            pass
    try:
        sd_stream = sd.InputStream(
            device=device_name,
            channels=CHANNELS,
            samplerate=RATE,
            blocksize=FRAME_SIZE,
            dtype='float32',
            latency='low',
            callback=audio_callback
        )
        sd_stream.start()
    except Exception as e:
        print(f"Cannot open microphone '{device_name}': {e}")

def toggle_send():
    global send_audio
    send_audio = var.get()

def select_device(event):
    threading.Thread(
        target=switch_device,
        args=(input_devices[devices_combo.current()]["name"],),
        daemon=True
    ).start()

def connect_voice_server():
    global voice_sock
    try:
        voice_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        voice_sock.connect((VOICE_SERVER_IP, VOICE_SERVER_PORT))
        threading.Thread(target=audio_send_thread, daemon=True).start()
        threading.Thread(target=audio_receive_thread, daemon=True).start()
        chat_box.insert(tk.END, f"[+] Подключено к голосовому серверу {VOICE_SERVER_IP}:{VOICE_SERVER_PORT}\n")
        chat_box.see(tk.END)
    except Exception as e:
        messagebox.showerror("Голос", f"Ошибка подключения к голосовому серверу: {e}")

def set_voice_server():
    global VOICE_SERVER_IP, VOICE_SERVER_PORT
    ip = simpledialog.askstring("Голосовой сервер", "Введите IP сервера:", initialvalue=VOICE_SERVER_IP)
    port = simpledialog.askinteger("Голосовой сервер", "Введите порт:", initialvalue=VOICE_SERVER_PORT)
    if ip and port:
        VOICE_SERVER_IP = ip
        VOICE_SERVER_PORT = port
        chat_box.insert(tk.END, f"[+] Голосовой сервер изменён на {ip}:{port}\n")
        chat_box.see(tk.END)
        save_config()

# ---------------- HTTP Upload ----------------
def set_upload_server():
    global UPLOAD_SERVER_URL
    new_url = simpledialog.askstring("HTTP Upload сервер",
                                     "Введите базовый адрес (например http://example.com:42439):",
                                     initialvalue=UPLOAD_SERVER_URL or "http://")
    if new_url:
        UPLOAD_SERVER_URL = new_url.rstrip('/')
        chat_box.insert(tk.END, f"[+] Сервер загрузки изменён на {UPLOAD_SERVER_URL}\n")
        chat_box.see(tk.END)
        save_config()

def upload_file():
    if not UPLOAD_SERVER_URL:
        messagebox.showwarning("Upload", "Сначала настройте адрес HTTP Upload сервера в меню.")
        return

    file_path = filedialog.askopenfilename(title="Выберите файл для отправки")
    if not file_path:
        return

    chat_box.insert(tk.END, f"[⏫] Загружаю файл: {os.path.basename(file_path)}\n")
    chat_box.see(tk.END)

    def worker(path):
        try:
            with open(path, "rb") as f:
                r = requests.post(f"{UPLOAD_SERVER_URL}/upload", files={"file": f}, timeout=60)
            if r.status_code == 200:
                data = r.json()
                url = data.get("url")
                if url:
                    msg_entry.delete(0, tk.END)
                    msg_entry.insert(0, url)
                    chat_box.insert(tk.END, f"[✅] Файл загружен, URL вставлен в поле ввода\n")
                else:
                    chat_box.insert(tk.END, "[!] Сервер вернул ответ без URL\n")
            else:
                chat_box.insert(tk.END, f"[!] Ошибка HTTP {r.status_code}\n")
        except Exception as e:
            chat_box.insert(tk.END, f"[!] Ошибка загрузки: {e}\n")
        chat_box.see(tk.END)

    threading.Thread(target=worker, args=(file_path,), daemon=True).start()

# ---------------- GUI ----------------
root = tb.Window(themename=current_theme_name)
root.title("Chat Client (Voice + Upload + Themes)")
root.geometry("920x700")

menubar = tk.Menu(root)
root.config(menu=menubar)

# Подключение
connection_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Подключение", menu=connection_menu)
connection_menu.add_command(label="Подключиться к серверу", command=connect_to_server)

# Действия
actions_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Действия", menu=actions_menu)
actions_menu.add_command(label="Логин", command=menu_login)
actions_menu.add_command(label="Регистрация", command=menu_register)
actions_menu.add_command(label="Приватное сообщение", command=menu_pm)
actions_menu.add_command(label="Войти на канал", command=menu_join)
actions_menu.add_command(label="Игнор-лист", command=menu_ignore)

# Темы
theme_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Темы", menu=theme_menu)
for theme in ["cyborg","darkly","flatly","superhero","morph","solar"]:
    theme_menu.add_command(label=theme, command=lambda t=theme: menu_theme(t))
theme_menu.add_command(label="Добавить пользовательскую тему", command=add_user_theme)

# Голосовой сервер
voice_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Голосовой сервер", menu=voice_menu)
voice_menu.add_command(label="Настроить адрес", command=set_voice_server)
voice_menu.add_command(label="Подключиться", command=connect_voice_server)

# HTTP Upload сервер
upload_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="HTTP Upload сервер", menu=upload_menu)
upload_menu.add_command(label="Настроить адрес", command=set_upload_server)

# Chat box
chat_box = scrolledtext.ScrolledText(root, wrap="word", width=120, height=30,
                                     bg="#1e1e1e", fg="white",
                                     insertbackground="white", font=("Consolas", 11))
chat_box.pack(padx=10, pady=10, fill="both", expand=True)

# Message entry & buttons
frame_bottom = tb.Frame(root)
frame_bottom.pack(fill="x", padx=10, pady=5)

msg_entry = tb.Entry(frame_bottom, width=80)
msg_entry.pack(side="left", padx=5, fill="x", expand=True)
msg_entry.bind("<Return>", send_message)

send_btn = tb.Button(frame_bottom, text="Отправить", bootstyle=SUCCESS, command=send_message)
send_btn.pack(side="left", padx=5)

file_btn = tb.Button(frame_bottom, text="📎", bootstyle="secondary-outline", command=upload_file)
file_btn.pack(side="left", padx=5)

# Voice controls
voice_frame = tb.Frame(root)
voice_frame.pack(padx=10, pady=5, fill="x")

var = tk.BooleanVar()
ttk.Checkbutton(voice_frame, text="Говорить", variable=var, command=toggle_send).pack(side="left", padx=5)
ttk.Label(voice_frame, text="Выберите микрофон:").pack(side="left", padx=5)

devices = sd.query_devices()
input_devices = [d for d in devices if d["max_input_channels"] > 0]
device_names = [d["name"] for d in input_devices] if input_devices else []

devices_combo = ttk.Combobox(voice_frame, values=device_names, width=50)
if device_names:
    devices_combo.current(0)
devices_combo.bind("<<ComboboxSelected>>", select_device)
devices_combo.pack(side="left", padx=5)

# Select first device automatically
if input_devices:
    threading.Thread(target=switch_device, args=(input_devices[0]["name"],), daemon=True).start()

# ---------------- Closing ----------------
def on_closing():
    global send_audio, stop_event
    send_audio = False
    stop_event.set()
    try:
        if client_socket:
            client_socket.close()
    except: pass
    try:
        if voice_sock:
            voice_sock.close()
    except: pass
    try:
        if sd_stream:
            sd_stream.stop(); sd_stream.close()
    except: pass
    save_config()
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

Download raw | Create new paste

© BitByByte, 2025.
Downgrade Counter