Загрузка данных


Отличный план. Давай сделаем полное пошаговое руководство с нуля. Я вынес имя канала и текст твоего водяного знака в удобные переменные в самом начале скрипта, чтобы их можно было менять за пару секунд.
Шаг 1. Подготовка и установка зависимостей
Если у тебя или у стримера еще не установлен Python:
 * Скачай установщик с официального сайта python.org.
 * При установке обязательно поставь галочку «Add Python to PATH» (это нужно, чтобы команды работали в консоли).
После установки открывай командную строку (cmd) или терминал (в VS Code / PyCharm) и устанавливай нужные библиотеки одной командой:
pip install websockets twitchio pyinstaller

Шаг 2. Написание кода
Создай пустую папку для проекта, а внутри неё файл main.py. Скопируй в него следующий код. В самом верху я выделил блок «НАСТРОЙКИ» — там ты можешь указать ник стримера и текст для водяного знака.
import asyncio
import json
import http.server
import socketserver
import threading
import websockets
from twitchio.ext import commands

# ==========================================
#               НАСТРОЙКИ
# ==========================================
CHANNEL_NAME = "имя_канала" # Логин стримера на Twitch (пиши строчными буквами)
WATERMARK_TEXT = "MODERATED BY ТВОЕ ИМЯ" # Твой водяной знак
HTTP_PORT = 8080
WS_PORT = 8765
# ==========================================

CONNECTED_CLIENTS = set()

# HTML-шаблон оверлея (%%WATERMARK%% автоматически заменится на твой текст)
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <style>
    body {
      margin: 0; padding: 20px;
      background: transparent;
      font-family: 'Segoe UI', Roboto, sans-serif;
      overflow: hidden; color: #fff;
    }
    #chat-container {
      display: flex; flex-direction: column;
      justify-content: flex-end; height: 92vh; max-width: 420px;
    }
    .message-card {
      background: rgba(15, 17, 26, 0.85);
      border-left: 4px solid #6366f1;
      border-radius: 8px; padding: 10px 14px; margin-top: 8px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.4);
      animation: slideIn 0.3s ease-out forwards;
    }
    .author { font-weight: bold; margin-bottom: 2px; font-size: 14px; }
    .text { font-size: 15px; word-break: break-word; }
    #watermark {
      position: fixed; top: 50%; left: 50%;
      transform: translate(-50%, -50%) scale(0);
      background: linear-gradient(135deg, #4f46e5, #7c3aed);
      padding: 16px 32px; border-radius: 12px;
      font-size: 24px; font-weight: 800; letter-spacing: 2px;
      box-shadow: 0 8px 24px rgba(0,0,0,0.6); opacity: 0; pointer-events: none;
    }
    @keyframes slideIn {
      from { opacity: 0; transform: translateX(-30px); }
      to { opacity: 1; transform: translateX(0); }
    }
    @keyframes watermarkIntro {
      0% { transform: translate(-50%, -50%) scale(0); opacity: 0; }
      50% { transform: translate(-50%, -50%) scale(1.1); opacity: 1; }
      70% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
      100% { transform: translate(-50%, -50%) scale(0.8); opacity: 0; }
    }
  </style>
</head>
<body>
  <div id="watermark">%%WATERMARK%%</div>
  <div id="chat-container"></div>
  <script>
    const ws = new WebSocket("ws://localhost:8765");
    const chatContainer = document.getElementById("chat-container");
    const watermark = document.getElementById("watermark");

    // Анимация знака при подключении к OBS
    ws.onopen = () => {
      watermark.style.animation = "watermarkIntro 3s ease forwards";
      setTimeout(() => { watermark.style.animation = ""; }, 3000);
    };

    // Получение сообщений
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === "chat_message") {
        addMessage(data.author, data.color, data.content);
      }
    };

    function addMessage(author, color, content) {
      const msg = document.createElement("div");
      msg.className = "message-card";
      msg.style.borderLeftColor = color || "#6366f1";
      msg.innerHTML = `<div class="author" style="color:${color || '#a5b4fc'}">${author}</div><div class="text">${content}</div>`;
      chatContainer.appendChild(msg);
      
      // Удаление сообщения через 15 секунд
      setTimeout(() => {
        msg.style.opacity = '0';
        msg.style.transition = 'opacity 0.5s ease';
        setTimeout(() => msg.remove(), 500);
      }, 15000);
    }
  </script>
</body>
</html>"""

# Подставляем твой водяной знак в HTML
FINAL_HTML = HTML_TEMPLATE.replace("%%WATERMARK%%", WATERMARK_TEXT)

# --- HTTP СЕРВЕР ---
class CustomHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write(FINAL_HTML.encode('utf-8'))

def start_http():
    with socketserver.TCPServer(("", HTTP_PORT), CustomHandler) as httpd:
        httpd.serve_forever()

# --- TWITCH BOT И WEBSOCKET ---
class Bot(commands.Bot):
    def __init__(self):
        # Анонимный вход в чат (без авторизации самого стримера)
        super().__init__(token='oauth:justinfan12345', prefix='!', initial_channels=[CHANNEL_NAME])

    async def event_ready(self):
        print(f"[ГОТОВО] Подключено к чату: {CHANNEL_NAME}")
        print(f"[OBS] Ссылка для браузера: http://localhost:{HTTP_PORT}")
        print("-" * 40)

    async def event_message(self, message):
        if message.echo: return
        data = {
            "type": "chat_message",
            "author": message.author.name,
            "color": message.author.color or "#a5b4fc",
            "content": message.content
        }
        if CONNECTED_CLIENTS:
            payload = json.dumps(data)
            await asyncio.gather(*[c.send(payload) for c in CONNECTED_CLIENTS])

async def ws_handler(websocket):
    CONNECTED_CLIENTS.add(websocket)
    try:
        async for _ in websocket: pass
    finally:
        CONNECTED_CLIENTS.remove(websocket)

async def main():
    threading.Thread(target=start_http, daemon=True).start()
    bot = Bot()
    ws_server = await websockets.serve(ws_handler, "localhost", WS_PORT)
    await asyncio.gather(bot.start(), ws_server.wait_closed())

if __name__ == "__main__":
    print("Запуск системы чата...")
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        pass

Шаг 3. Компиляция в .exe файл
Чтобы стримеру не нужно было устанавливать Python, мы сделаем из скрипта самостоятельную программу.
 * Открой консоль в той же папке, где лежит твой main.py.
 * Введи команду:
   pyinstaller --onefile --noconsole --icon=NONE main.py

   Параметр --noconsole скрывает черное окно командной строки при запуске, чтобы программа работала в фоне и не мешала стримеру.
 * После завершения сборки зайди в появившуюся папку dist. Там будет лежать твой готовый main.exe.
 * Можешь переименовать его, например, в StreamChat.exe и скинуть стримеру. Больше ему ничего не нужно!
Шаг 4. Настройка в OBS Studio
Инструкция для стримера (делается один раз):
 * Запустить полученный .exe файл (ничего визуально не произойдет, он работает в фоне).
 * В OBS в нужной сцене нажать «+» в источниках и выбрать Браузер (Browser).
 * В открывшемся окне снять галочку «Локальный файл».
 * В поле URL вписать: http://localhost:8080
 * Задать размеры. Рекомендуемые: Ширина 400, Высота 800.
 * Важно: Поставить галочку на пункт «Обновлять браузер, когда сцена становится активной» (Refresh browser when scene becomes active). Благодаря этому твой водяной знак будет красиво появляться каждый раз, когда он переключается на эту сцену!
Получилось ли у тебя собрать .exe файл, или нужна помощь с какой-то конкретной ошибкой при компиляции?