diff --git a/config.ini b/config.ini index fc67302..9a4b766 100644 --- a/config.ini +++ b/config.ini @@ -3,8 +3,8 @@ bot_secret = 6454033742:AAFj2rUoVb2jJ_Lew4eiIecdr7s7xbrqeNU enabled = 1 [YouTube] -video_id = 3xLyYdp0UFg -enabled = 1 +video_id = y2fwatM1oOg +enabled = 0 [Twitch] channel = coulthardf1 diff --git a/huySeek.py b/huySeek.py index 201801a..e02cd58 100644 --- a/huySeek.py +++ b/huySeek.py @@ -14,6 +14,7 @@ import tzlocal from zoneinfo import ZoneInfo from http.server import BaseHTTPRequestHandler, HTTPServer from urllib import parse +from datetime import timezone # Чтение конфигурации config = configparser.ConfigParser() @@ -23,14 +24,14 @@ config.read("config.ini") chat_connectors = {} chat_comments = {} -# Telegram (опционально) +# Telegram if config.getboolean('Telegram', 'enabled', fallback=False): import telegram bot = telegram.Bot(config['Telegram']['bot_secret']) chat_connectors['telegram'] = bot chat_comments['tg'] = [] -# YouTube (опционально) +# YouTube if config.getboolean('YouTube', 'enabled', fallback=False): print('Youtube enabled') import pytchat @@ -39,16 +40,18 @@ if config.getboolean('YouTube', 'enabled', fallback=False): chat_connectors['youtube'] = chat chat_comments['yt'] = [] -# Twitch (опционально) +# Twitch +TWITCH_CHANNEL = None if config.getboolean('Twitch', 'enabled', fallback=False): sys.path.append('twitchchatirc') import twitch twitch_channel = config['Twitch']['channel'] twitch_socket = twitch.TwitchChatIRC(twitch_channel) chat_connectors['twitch'] = twitch_socket + TWITCH_CHANNEL = config['Twitch']['channel'] chat_comments['tw'] = [] -# Функции для работы с датами (оставлены как есть) +# Функции для работы с датами class DateTimeEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, datetime): @@ -64,10 +67,12 @@ all_comments = [] all_old_comments = [] is_changed = False overallcount = 0 +last_tg_update_id = 0 # Получение комментариев из Telegram async def get_telegram_comments(): global chat_comments + global last_tg_update_id chat_comments['tg'] = [] if 'telegram' not in chat_connectors: @@ -76,9 +81,10 @@ async def get_telegram_comments(): async with bot: try: updates = await bot.get_updates( + offset=last_tg_update_id + 1, allowed_updates=['message', 'edited_message'], timeout=None - ) + ) except telegram.error.TimedOut: print('TG connection timeout') time.sleep(5) @@ -86,6 +92,11 @@ async def get_telegram_comments(): except Exception as e: print(f'TG connection error: {e}') return + + if not updates: + return + + last_tg_update_id = updates[-1].update_id for upd in updates: msg = upd.message @@ -102,15 +113,21 @@ async def get_telegram_comments(): if sendr == "Group": sendr = 'Админ' - netdatetime = msg.date.replace(tzinfo=tz) - comm = { - 'id': msg.message_id, - 'type': 'tg', - 'date': netdatetime, - 'sendr': sendr, - 'msg': msg.text - } - chat_comments['tg'].append(comm) + try: + netdatetime = msg.date.replace(tzinfo=timezone.utc).astimezone(tz) + + comm = { + 'id': msg.message_id, + 'type': 'tg', + 'date': netdatetime, + 'sendr': sendr, + 'msg': msg.text + } + chat_comments['tg'].append(comm) + except Exception as e: + print(f"Ошибка преобразования времени: {e}") + + # Получение комментариев из YouTube def get_youtube_comments(): @@ -156,16 +173,35 @@ def update_all_comments(): global all_comments global chat_comments - # Добавляем новые комментарии из всех источников - for source in ['tg', 'yt', 'tw']: - if source in chat_comments: - for comment in chat_comments[source]: - if comment not in all_comments: - all_comments.append(comment) + # Временный список для новых комментариев + new_comments = [] - # Сортируем по дате и ограничиваем количество + # Собираем новые комментарии из всех источников + for source in ['tg', 'yt', 'tw']: + if source in chat_comments and chat_comments[source]: + # Добавляем все новые комментарии из этого источника + new_comments.extend(chat_comments[source]) + # Очищаем буфер этого источника + chat_comments[source] = [] + + # Добавляем новые комментарии к существующим + all_comments.extend(new_comments) + + # Сортируем по дате all_comments.sort(key=sort_by_date) - all_comments = all_comments[-150:] + + # Удаляем дубликаты по ID (если они есть) + seen_ids = set() + unique_comments = [] + + for comment in all_comments: + comment_id = (comment['id'], comment['type']) # ID уникален в рамках типа + if comment_id not in seen_ids: + seen_ids.add(comment_id) + unique_comments.append(comment) + + # Ограничиваем количество и сохраняем + all_comments = unique_comments[-150:] def make_json_object(): global all_comments @@ -189,19 +225,31 @@ def make_json_object(): # Ограничиваем буферы для каждого источника for source in ['yt', 'tg', 'tw']: if source in chat_comments and len(chat_comments[source]) > 15: + # Сортируем ВСЕ источники по дате (старые -> новые) + chat_comments[source].sort(key=lambda x: x['date']) + # Берём последние 15 (самые новые) chat_comments[source] = chat_comments[source][-15:] # Добавляем приветственное сообщение каждые 10 минут - if int(time.time()) % 600 == 0: - dt = datetime.now(ZoneInfo('UTC')).astimezone(tz) - hello_msg = { - 'id': random.randint(100000, 999999), - 'type': 'hello', - 'date': dt, - 'sendr': 'Eikichi-bot', - 'msg': '🔥 Спасибо всем на стриме за интерес к переводу и поддержку! 🔥' - } - all_comments.append(hello_msg) + current_second = int(time.time()) + if current_second % 600 <= 5: + # Проверяем, есть ли приветственное сообщение от Eikichi-bot в последних 20 сообщениях + has_recent_hello = any( + comment.get('sendr') == 'Eikichi-bot' and comment.get('type') == 'hello' + for comment in all_comments[-20:] # Последние 20 сообщений + ) + + if not has_recent_hello: + dt = datetime.now(ZoneInfo('UTC')).astimezone(tz) + hello_msg = { + 'id': random.randint(100000, 999999), + 'type': 'hello', + 'date': dt, + 'sendr': 'Eikichi-bot', + 'msg': '🔥 Спасибо всем на стриме за интерес к переводу и поддержку! 🔥' + } + all_comments.append(hello_msg) + print(f"{datetime.now().strftime('%H:%M:%S')} Добавлено приветственное сообщение") # Обновляем общий список комментариев update_all_comments() @@ -211,9 +259,11 @@ def make_json_object(): def print_all_comments(): global all_comments + global chat_comments print('-' * 40) print(all_comments) print('-' * 40) + print(chat_comments) # HTTP сервер class ChatServer(BaseHTTPRequestHandler): @@ -267,6 +317,9 @@ server_thread.start() print('Запуск подсистем чатов...') print('...Удерживайте Ctrl+Alt+Shift+C для выхода и C для очистки консоли...') +# print('Загрузка 7TV эмодзи...') +# emotes_cache = get_7tv_emotes() if TWITCH_CHANNEL else {} + poll_time = 2 # Интервал проверки в секундах diff --git a/index.html b/index.html index 2906ecf..d4a2af1 100644 --- a/index.html +++ b/index.html @@ -30,7 +30,7 @@ body { /* color: white; */ height: 100%; min-width: 150px; - max-width: 400px; + max-width: 450px; display: flex; flex-direction: column; @@ -43,6 +43,8 @@ body { flex-direction: row; margin-bottom: 1px; animation: fadeInUp 0.3s ease-out; + + min-height: 0; } @keyframes fadeInUp { @@ -67,6 +69,13 @@ body { font-weight: bold; flex-direction: column; vertical-align: auto; + + flex: 0 0 25%; /* Фиксируем ширину - не растягивается и не сжимается */ + min-width: 0; /* Важно для работы переноса текста в flex-контейнере */ + max-width: 25%; /* Ограничиваем максимальную ширину */ + word-wrap: break-word; /* Перенос длинных слов */ + overflow-wrap: break-word; /* Современная версия */ + word-break: break-word; /* Разрыв слов если нужно */ } .highlight-message { @@ -75,9 +84,19 @@ body { } +.emote-7tv { + height: 1.4em; + width: auto; + vertical-align: middle; + display: inline-block; + margin: 0 2px; + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} .yt { - background-image: url('data:image/svg+xml,'); + background-image: url('data:image/svg+xml,'); background-color: #E53935A0; } @@ -106,6 +125,11 @@ body { padding: 5px; width: 75%; color: #fff; + + flex: 1; /* Занимает всё оставшееся пространство */ + min-width: 0; /* КРИТИЧЕСКИ ВАЖНО для работы текста в flex */ + word-wrap: break-word; + overflow-wrap: break-word; } diff --git a/script.js b/script.js index 87d2294..85cd4d8 100644 --- a/script.js +++ b/script.js @@ -1,28 +1,17 @@ const chatwin = document.getElementById("chatwin"); const anchor = document.getElementById("anchor"); - -// Массив ключевых слов для выделения (без учета регистра) -const specialKeywords = ['sergshemet', 'sergeyshemet', 'sshemet', 'сергей', 'серёга', 'админ']; - -// Функция для проверки содержит ли сообщение ключевые слова +const specialKeywords = ['sergshemet', 'sergeyshemet', 'sshemet', 'серг', 'серег', 'серёг', 'админ']; function containsSpecialKeywords(text) { const lowerText = text.toLowerCase(); return specialKeywords.some(keyword => lowerText.includes(keyword.toLowerCase())); } - -// Создание новой строки чата из JSON данных function createNewLine(json) { - // Сортируем сообщения по дате (самые старые первыми) json.sort((a, b) => new Date(a.date) - new Date(b.date)); json.forEach(element => { - // Проверяем, существует ли уже элемент с таким ID let existingMsg = document.getElementById(element["id"]); if (existingMsg) { - // Обновляем текст существующего сообщения existingMsg.innerHTML = element["msg"]; - - // Проверяем на ключевые слова для подсветки if (containsSpecialKeywords(element["msg"])) { existingMsg.classList.add("highlight-message"); } else { @@ -30,62 +19,39 @@ function createNewLine(json) { } return; } - - // Создаем блок имени const nameBlock = document.createElement("div"); nameBlock.className = "nameline " + element["type"]; - - // Для донатов добавляем сумму if (element["type"] == "donate") { nameBlock.innerHTML = element["sendr"] + '

' + element['amount'] + "

"; } else { nameBlock.innerHTML = element["sendr"]; } - - // Создаем блок сообщения const msgBlock = document.createElement("div"); msgBlock.className = "msgline"; msgBlock.innerHTML = element["msg"]; msgBlock.id = element["id"]; - - // Добавляем подсветку если есть ключевые слова if (containsSpecialKeywords(element["msg"])) { msgBlock.classList.add("highlight-message"); } - - // Стиль для донатов if (element["type"] == "donate") { msgBlock.style.backgroundColor = "#0000FF20"; } - - // Создаем строку чата const row = document.createElement("div"); row.className = "chatRow"; row.setAttribute("name", element["id"]); row.appendChild(nameBlock); row.appendChild(msgBlock); - - // ВСЕГДА вставляем новое сообщение перед якорем (внизу) chatwin.insertBefore(row, anchor); - - // Прокручиваем сразу к новому сообщению scrollToBottom(); }); - - // Ограничиваем количество сообщений (200) removeOldMessages(200); } - -// Функция прокрутки в самый низ function scrollToBottom() { - // Используем setTimeout чтобы прокрутка происходила после добавления DOM элемента setTimeout(() => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }, 10); } - -// Удаление старых сообщений function removeOldMessages(maxMessages = 200) { const messageRows = document.getElementsByClassName("chatRow"); const rowsToRemove = messageRows.length - maxMessages; @@ -98,8 +64,6 @@ function removeOldMessages(maxMessages = 200) { } } } - -// Запрос новых сообщений с сервера function requestNewLines() { fetch("http://localhost:8008/") .then(response => { @@ -113,33 +77,22 @@ function requestNewLines() { }) .catch(error => { console.error('Error fetching chat messages:', error); - - // Показываем сообщение об ошибке только если его нет const errorRow = document.getElementById("error-message"); if (!errorRow) { const errorDiv = document.createElement("div"); errorDiv.id = "error-message"; errorDiv.className = "chatRow error"; - errorDiv.innerHTML = '
Ошибка
Не удалось подключиться к серверу чата
'; + errorDiv.innerHTML = '
Maya
Тацуя, демоны отключили сервер чата!
'; chatwin.insertBefore(errorDiv, anchor); scrollToBottom(); } }); } - -// Инициализация чата function initChat() { - // Прокручиваем сразу вниз при загрузке scrollToBottom(); - - // Запрашиваем сообщения сразу при загрузке requestNewLines(); - - // Запускаем периодический опрос setInterval(requestNewLines, 1000); } - -// Запускаем чат когда DOM загружен if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initChat); } else {