diff --git a/config.ini b/config.ini index d89c5b7..fc67302 100644 --- a/config.ini +++ b/config.ini @@ -2,11 +2,13 @@ bot_secret = 6454033742:AAFj2rUoVb2jJ_Lew4eiIecdr7s7xbrqeNU enabled = 1 -[Youtube] -video_id = SsC29jqiNto +[YouTube] +video_id = 3xLyYdp0UFg +enabled = 1 [Twitch] -channel = wanderbraun +channel = coulthardf1 +enabled = 1 [Alerts] app_id = 12554 diff --git a/huy.py b/huy.py index b7e68a8..98f67df 100644 --- a/huy.py +++ b/huy.py @@ -29,7 +29,7 @@ import twitch # Reading Configs config = configparser.ConfigParser() -config.read("E:/Games/cgi-bin/config.ini") +config.read("config.ini") alerts = DonationAlertsAPI( diff --git a/huySeek.py b/huySeek.py index 4806314..201801a 100644 --- a/huySeek.py +++ b/huySeek.py @@ -6,373 +6,281 @@ import os import sys import threading import asyncio +import socket +import emoji from datetime import datetime, timedelta import time +import tzlocal from zoneinfo import ZoneInfo from http.server import BaseHTTPRequestHandler, HTTPServer -import pytchat -import telegram -from donationalerts import DonationAlertsAPI, Scopes from urllib import parse -import emoji -sys.path.append('twitchchatirc') -import twitch -# ==================== -# Конфигурация -# ==================== -class Config: - def __init__(self): - self.config = configparser.ConfigParser() - self.config.read("config.ini") - - # Настройки - self.timezone = ZoneInfo('Asia/Yekaterinburg') - self.poll_interval = 2 # seconds - self.max_comments_per_source = 15 - self.max_total_comments = 150 - self.hello_message_interval = 600 # seconds - - # API keys - self.alerts_app_id = self.config['Alerts']['app_id'] - self.alerts_api_key = self.config['Alerts']['api_key'] - self.telegram_enabled = self.config['Telegram']['enabled'] - self.telegram_bot_secret = self.config['Telegram']['bot_secret'] - self.youtube_video_id = self.config['Youtube']['video_id'] - self.twitch_channel = self.config['Twitch']['channel'] +# Чтение конфигурации +config = configparser.ConfigParser() +config.read("config.ini") -# ==================== -# Модели данных -# ==================== -class Comment: - def __init__(self, comment_id, comment_type, sender, message, date, amount=None): - self.id = comment_id - self.type = comment_type - self.sender = sender - self.message = message - self.date = date - self.amount = amount - - def to_dict(self): - return { - 'id': self.id, - 'type': self.type, - 'sender': self.sender, - 'message': emoji.emojize(self.message), - 'date': self.date, - 'amount': self.amount - } +# Инициализация модулей на основе конфигурации +chat_connectors = {} +chat_comments = {} -# ==================== -# Сервисы -# ==================== -class DonationAlertsService: - def __init__(self, config): - self.config = config - self.api = DonationAlertsAPI( - config.alerts_app_id, - config.alerts_api_key, - "http://127.0.0.1:8008/login", - [Scopes.USER_SHOW] - ) - self.access_token = None - - def get_donations(self): - if not self.access_token: - print('Waiting for Alerts auth...') - return [] - - try: - donations = self.api.donations.get(self.access_token.access_token) - return self._process_donations(donations.items) - except Exception as e: - print(f'Alerts error: {str(e)}') - return [] - - def _process_donations(self, items): - result = [] - for item in items: - if not item.is_shown: - continue - - sender = item.username or 'Аноним' - message = item.message or '---' - amount = self._format_amount(item.amount, item.currency) - date = self._parse_donation_date(item.shown_at) - - result.append( - Comment( - comment_id=item.id, - comment_type='donate', - sender=sender, - message=message, - date=date, - amount=amount - ) - ) - return result - - def _format_amount(self, amount, currency): - if amount != int(amount): - return f"{format(amount, '.2f')} {currency}" - return f"{int(amount)} {currency}" - - def _parse_donation_date(self, date_str): - dt = datetime.fromisoformat(date_str).astimezone(self.config.timezone) - # Фильтр донатов за последний месяц - if dt > datetime.now(self.config.timezone) - timedelta(days=30): - return dt - return None +# Telegram (опционально) +if config.getboolean('Telegram', 'enabled', fallback=False): + import telegram + bot = telegram.Bot(config['Telegram']['bot_secret']) + chat_connectors['telegram'] = bot + chat_comments['tg'] = [] -class TelegramService: - def __init__(self, config): - self.config = config - self.bot = telegram.Bot(config.telegram_bot_secret) if config.telegram_enabled else None - - async def get_messages(self): - if not self.bot: - return [] - - try: - updates = await self.bot.get_updates( - allowed_updates=['message', 'edited_message'], - timeout=None - ) - return self._process_updates(updates) - except Exception as e: - print(f'Telegram error: {str(e)}') - return [] - - def _process_updates(self, updates): - messages = [] - for upd in updates: - msg = upd.edited_message if upd.edited_message else upd.message - - if not hasattr(msg, 'text') or not msg.text: - continue - - sender = self._get_sender_name(msg.from_user) - date = msg.date.replace(tzinfo=self.config.timezone) - - messages.append( - Comment( - comment_id=msg.message_id, - comment_type='tg', - sender=sender, - message=msg.text, - date=date - ) - ) - return messages - - def _get_sender_name(self, user): - if user.first_name and user.last_name: - return f"{user.first_name} {user.last_name}" - return user.first_name or "Админ" +# YouTube (опционально) +if config.getboolean('YouTube', 'enabled', fallback=False): + print('Youtube enabled') + import pytchat + video_id = config['YouTube']['video_id'] + chat = pytchat.create(video_id=video_id) + chat_connectors['youtube'] = chat + chat_comments['yt'] = [] -class YouTubeService: - def __init__(self, config): - self.config = config - self.chat = pytchat.create(video_id=config.youtube_video_id) - - def get_messages(self): - try: - items = self.chat.get() - if not hasattr(items, 'items'): - print('YT reconnecting...') - self.chat = pytchat.create(video_id=self.config.youtube_video_id) - return [] - - return self._process_messages(items.items) - except Exception as e: - print(f'YouTube error: {str(e)}') - return [] - - def _process_messages(self, items): - messages = [] - for item in items: - date = datetime.fromisoformat(item.datetime).replace(tzinfo=self.config.timezone) - messages.append( - Comment( - comment_id=item.id, - comment_type='yt', - sender=item.author.name, - message=item.message, - date=date - ) - ) - return messages +# Twitch (опционально) +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 + chat_comments['tw'] = [] -class TwitchService: - def __init__(self, config): - self.config = config - self.socket = twitch.TwitchChatIRC(config.twitch_channel) - - def get_messages(self): - # Предполагаем, что сообщения добавляются в self.socket.all_messages - # в отдельном потоке (как в оригинальном коде) - return getattr(self.socket, 'all_messages', []) - -# ==================== -# Ядро приложения -# ==================== -class ChatAggregator: - def __init__(self): - self.config = Config() - self.services = { - 'alerts': DonationAlertsService(self.config), - 'telegram': TelegramService(self.config), - # 'youtube': YouTubeService(self.config), - 'twitch': TwitchService(self.config) - } - self.comments = [] - self.lock = threading.Lock() - self.hello_message = Comment( - comment_id=random.randint(100000, 999999), - comment_type='hello', - sender='Eikichi-bot', - message='🔥 Спасибо всем на стриме за интерес к переводу и поддержку! 🔥', - date=datetime.now(self.config.timezone) - ) - - def run(self): - # Запуск сервера в отдельном потоке - server_thread = threading.Thread(target=self._run_server) - server_thread.daemon = True - server_thread.start() - - # Запуск Twitch в отдельном потоке - twitch_thread = threading.Thread(target=self.services['twitch'].socket.listen) - twitch_thread.daemon = True - twitch_thread.start() - - print('System started. Press Ctrl+Shift+Alt+C to exit.') - - # Главный цикл - while True: - if keyboard.is_pressed('Ctrl+Shift+Alt+C'): - sys.exit(0) - - if int(time.time()) % self.config.poll_interval == 0: - self.update_comments() - time.sleep(1) - - def update_comments(self): - # Сбор сообщений со всех сервисов - new_comments = [] - - # DonationAlerts - new_comments.extend(self.services['alerts'].get_donations()) - - # YouTube - # new_comments.extend(self.services['youtube'].get_messages()) - - # Telegram - if self.config.telegram_enabled: - try: - telegram_comments = asyncio.run(self.services['telegram'].get_messages()) - new_comments.extend(telegram_comments) - except Exception as e: - print(f'Telegram error: {e}') - - # Twitch - new_comments.extend(self.services['twitch'].get_messages()) - - # Добавление приветственного сообщения - if int(time.time()) % self.config.hello_message_interval == 0: - new_comments.append(self.hello_message) - - # Обновление основного списка комментариев - with self.lock: - # Добавление новых уникальных комментариев - existing_ids = {c.id for c in self.comments} - self.comments.extend( - c for c in new_comments - if c.id not in existing_ids - ) - - # Сортировка по дате - self.comments.sort(key=lambda x: x.date) - - # Ограничение общего количества - self.comments = self.comments[-self.config.max_total_comments:] - - print(f"{datetime.now().strftime('%H:%M:%S')} Updated. Total comments: {len(self.comments)}") - - def get_comments_json(self): - with self.lock: - return json.dumps( - [c.to_dict() for c in self.comments], - cls=DateTimeEncoder, - ensure_ascii=False - ) - - def _run_server(self): - class Handler(BaseHTTPRequestHandler): - def __init__(self, *args, **kwargs): - self.aggregator = kwargs.pop('aggregator') - super().__init__(*args, **kwargs) - - def _set_headers(self): - self.send_response(200) - self.send_header('Content-type', 'application/json; charset=utf-8') - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - - def do_GET(self): - if self.path == '/alert_auth': - self.send_response(301) - self.send_header('Location', self.aggregator.services['alerts'].api.authorize.login()) - self.end_headers() - elif self.path.startswith('/login'): - self._handle_alerts_login() - else: - self._set_headers() - self.wfile.write(self.aggregator.get_comments_json().encode('utf-8')) - - def _handle_alerts_login(self): - url = dict(parse.parse_qsl(parse.urlsplit(self.path).query)) - code = url.get('code') - if code: - try: - self.aggregator.services['alerts'].access_token = ( - self.aggregator.services['alerts'].api.authorize.get_access_token(code) - ) - print("DonationAlerts authorized successfully!") - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - self.wfile.write(b'Authorization successful! You can close this page.') - except Exception as e: - print(f'Alerts auth error: {str(e)}') - self.send_error(500, 'Authorization failed') - else: - self.send_error(400, 'Missing code parameter') - - def log_message(self, format, *args): - return - - server_address = ('127.0.0.1', 8008) - httpd = HTTPServer(server_address, lambda *args: Handler(*args, aggregator=self)) - print(f'Starting HTTP server on port 8008...') - httpd.serve_forever() - -# ==================== -# Вспомогательные классы -# ==================== +# Функции для работы с датами (оставлены как есть) class DateTimeEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, datetime): return o.isoformat() - return super().default(o) + if isinstance(o, bytes): + return list(o) + return json.JSONEncoder.default(self, o) -# ==================== -# Точка входа -# ==================== -if __name__ == '__main__': - print('\n--- Telegram/YouTube/Twitch Chat Aggregator ---\n') - print('For DonationAlerts authorization visit: http://127.0.0.1:8008/alert_auth') - print('Press Ctrl+Shift+Alt+C to exit\n') +LOCAL_TIMEZONE = tzlocal.get_localzone_name() +tz = ZoneInfo('Asia/Yekaterinburg') + +all_comments = [] +all_old_comments = [] +is_changed = False +overallcount = 0 + +# Получение комментариев из Telegram +async def get_telegram_comments(): + global chat_comments + chat_comments['tg'] = [] + + if 'telegram' not in chat_connectors: + return + + async with bot: + try: + updates = await bot.get_updates( + allowed_updates=['message', 'edited_message'], + timeout=None + ) + except telegram.error.TimedOut: + print('TG connection timeout') + time.sleep(5) + return + except Exception as e: + print(f'TG connection error: {e}') + return + + for upd in updates: + msg = upd.message + if upd.edited_message: + msg = upd.edited_message + + if not hasattr(msg, 'text') or not msg.text: + continue + + sendr = msg.from_user.first_name + if msg.from_user.last_name: + sendr = sendr + ' ' + msg.from_user.last_name + + 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) + +# Получение комментариев из YouTube +def get_youtube_comments(): + global chat_comments - aggregator = ChatAggregator() - aggregator.run() \ No newline at end of file + if 'youtube' not in chat_connectors: + return + + chat = chat_connectors['youtube'] + itms = chat.get() + + if not hasattr(itms, 'items'): + print('YT has no items attribute! (Empty?). Reconnecting...') + time.sleep(5) + video_id = config['YouTube']['video_id'] + chat_connectors['youtube'] = pytchat.create(video_id=video_id) + return + + for c in itms.items: + dt = datetime.fromisoformat(c.datetime).replace(tzinfo=tz) + comm = { + 'id': c.id, + 'type': 'yt', + 'date': dt, + 'sendr': c.author.name, + 'msg': emoji.emojize(c.message) + } + chat_comments['yt'].append(comm) + +# Получение комментариев из Twitch +def get_twitch_comments(): + global chat_comments + + if 'twitch' not in chat_connectors: + return + + chat_comments['tw'] = chat_connectors['twitch'].all_messages + +def sort_by_date(e): + return e['date'] + +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) + + # Сортируем по дате и ограничиваем количество + all_comments.sort(key=sort_by_date) + all_comments = all_comments[-150:] + +def make_json_object(): + global all_comments + global chat_comments + global overallcount + + # Получаем комментарии из всех включенных источников + if 'youtube' in chat_connectors: + get_youtube_comments() + + if 'twitch' in chat_connectors: + get_twitch_comments() + + if 'telegram' in chat_connectors: + try: + asyncio.run(get_telegram_comments()) + except Exception as e: + print(f'TG error: {e}') + time.sleep(2) + + # Ограничиваем буферы для каждого источника + for source in ['yt', 'tg', 'tw']: + if source in chat_comments and len(chat_comments[source]) > 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) + + # Обновляем общий список комментариев + update_all_comments() + + overallcount += 1 + print(f"{datetime.now().strftime('%H:%M:%S')} Проверка чатов... {len(all_comments)} элементов ({overallcount})") + +def print_all_comments(): + global all_comments + print('-' * 40) + print(all_comments) + print('-' * 40) + +# HTTP сервер +class ChatServer(BaseHTTPRequestHandler): + def _set_headers(self): + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + + def do_HEAD(self): + self._set_headers() + + def log_message(self, format, *args): + # Отключаем логи сервера + return + + def do_GET(self): + if self.path == '/': + self._set_headers() + self.wfile.write(json.dumps(all_comments, cls=DateTimeEncoder).encode('utf-8')) + else: + self.send_error(404, "Not Found") + +def run_server(port=8008): + server_address = ('', port) + httpd = HTTPServer(server_address, ChatServer) + print(f'Starting HTTP сервер на http://127.0.0.1:{port} ...') + print() + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + sys.exit(1) + +# Основная часть программы +print() +print('--- Парсер чатов Telegram/YouTube/Twitch --- Sergey Shemet (C) 2025 ---') +print() + +# Запускаем Twitch в отдельном потоке если включен +if 'twitch' in chat_connectors: + twitch_thread = threading.Thread(target=twitch_socket.listen, name='twitchirc') + twitch_thread.daemon = True + twitch_thread.start() + +# Запускаем HTTP сервер в отдельном потоке +server_thread = threading.Thread(target=run_server, name='HTTPServer') +server_thread.daemon = True +server_thread.start() + +print('Запуск подсистем чатов...') +print('...Удерживайте Ctrl+Alt+Shift+C для выхода и C для очистки консоли...') + +poll_time = 2 # Интервал проверки в секундах + +while True: + # Обработка горячих клавиш + if keyboard.is_pressed('Ctrl+Shift+Alt+c'): + sys.exit(1) + if keyboard.is_pressed('Ctrl+Shift+Alt+z'): + print_all_comments() + if keyboard.is_pressed('c'): + os.system("cls") + + # Периодическая проверка чатов + this_second = int(time.time()) + if this_second % poll_time == 0: + make_json_object() + time.sleep(1) \ No newline at end of file diff --git a/index.html b/index.html index 6ab8e1f..2906ecf 100644 --- a/index.html +++ b/index.html @@ -1,80 +1,143 @@ - SSH UltraChat + template +.highlight-message { + color: #ffb6c1 !important; /* Нежно-светло-розовый */ + font-weight: 500; +} + + + +.yt { + background-image: url('data:image/svg+xml,'); + background-color: #E53935A0; +} + +.tg { + background-image: url('data:image/svg+xml,'); + background-color: #2196F3A0; +} + +.tw { + background-color: #6034b2A0 ; + background-image: url("data:image/svg+xml,"); +} +/* */ +.donate { + background-image: url('back-coin.jpg'); + /* background-size: 500px; */ + color: black !important; +} + +.hello { + background-color: #00bb77; + background-image: url("data:image/svg+xml,"); +} + +.msgline { + padding: 5px; + width: 75%; + color: #fff; +} + + + -
+ +
+ +
+
+ diff --git a/midi/kimi_no_kioku_simple.mid b/midi/kimi_no_kioku_simple.mid new file mode 100644 index 0000000..a36407b Binary files /dev/null and b/midi/kimi_no_kioku_simple.mid differ diff --git a/midi/midi.py b/midi/midi.py new file mode 100644 index 0000000..40f889f --- /dev/null +++ b/midi/midi.py @@ -0,0 +1,69 @@ +from midiutil import MIDIFile + +# Параметры +tempo = 112 # BPM оригинала +volume = 100 + +# Создаём MIDI файл: 1 трек +midi = MIDIFile(1) +track = 0 +channel = 0 # Пианино +time = 0 + +midi.addTempo(track, time, tempo) + +# Функция для добавления нот (note, duration в долях такта, volume) +def add_notes(notes, duration=1, vol=volume): + global time + for pitch in notes if isinstance(notes, list) else [notes]: + midi.addNote(track, channel, pitch, time, duration, vol) + time += duration + +# Простая версия Verse (Куплет) - 4 повторения прогрессии +# Аккорды: Bdim7 (бас Ab), Gm6, F#m7, Bm7 +# Мелодия упрощённая, фокус на мотиве A A B B (A4=69, B4=71) + +# Один цикл куплета (4 такта) +for _ in range(4): # x4 + # 1. Bdim7 с басом Ab3 (44) + add_notes(44, 1) # бас Ab3 + add_notes([71, 74, 77, 80], 0.5) # B4 D5 F5 Ab5 (пример voicing) + add_notes([69, 69, 71, 71], 0.5) # мотив A A B B в мелодии + + # 2. Gm6 + add_notes(43, 1) # бас G3=43 + add_notes([67, 70, 74, 76], 1) # G Bb D E + + # 3. F#m7 + add_notes(42, 1) # бас F#3=42 + add_notes([66, 69, 73, 76], 1) # F# A C# E + + # 4. Bm7 + add_notes(47, 1) # бас B3=47 + add_notes([71, 74, 78, 81], 1) # B D F# A (выше) + +# Chorus (Припев) - упрощённо 4 цикла +# Аккорды: GM7 - F#m7 - Em7 - Dmaj7 +for _ in range(4): + # GM7 + add_notes(55, 1) # бас G3 + add_notes([71, 74, 78, 81], 1) # G B D F# + + # F#m7 + add_notes(54, 1) # F#3 + add_notes([69, 73, 76, 78], 1) + + # Em7 + add_notes(52, 1) # E3 + add_notes([64, 67, 71, 74], 1) + + # Dmaj7 с развитием мелодии + add_notes(50, 1) # D3 + add_notes([69, 71, 73, 74], 0.5) # A B C# D (развитие мотива) + add_notes([74], 0.5) # финал + +# Записываем в файл +with open("kimi_no_kioku_simple.mid", "wb") as output_file: + midi.writeFile(output_file) + +print("MIDI файл успешно создан: kimi_no_kioku_simple.mid") \ No newline at end of file diff --git a/script.js b/script.js index e4a18d2..87d2294 100644 --- a/script.js +++ b/script.js @@ -1,131 +1,147 @@ -let messages = [ - "I wondered why the baseball was getting bigger. Then it hit me.", - "Police were called to a day care, where a three-year-old was resisting a rest.", - "Did you hear about the guy whose whole left side was cut off? He’s all right now.", - "The roundest knight at King Arthur’s round table was Sir Cumference.", - "To write with a broken pencil is pointless.", - "When fish are in schools they sometimes take debate.", - "The short fortune teller who escaped from prison was a small medium at large.", - "A thief who stole a calendar… got twelve months.", - "A thief fell and broke his leg in wet cement. He became a hardened criminal.", - "Thieves who steal corn from a garden could be charged with stalking.", - "When the smog lifts in Los Angeles , U. C. L. A.", - "The math professor went crazy with the blackboard. He did a number on it.", - "The professor discovered that his theory of earthquakes was on shaky ground.", - "The dead batteries were given out free of charge.", - "If you take a laptop computer for a run you could jog your memory.", - "A dentist and a manicurist fought tooth and nail.", - "A bicycle can’t stand alone; it is two tired.", - "A will is a dead giveaway.", - "Time flies like an arrow; fruit flies like a banana.", - "A backward poet writes inverse.", - "In a democracy it’s your vote that counts; in feudalism, it’s your Count that votes.", - "A chicken crossing the road: poultry in motion.", - "If you don’t pay your exorcist you can get repossessed.", - "With her marriage she got a new name and a dress.", - "Show me a piano falling down a mine shaft and I’ll show you A-flat miner.", - "When a clock is hungry it goes back four seconds.", - "The guy who fell onto an upholstery machine was fully recovered.", - "A grenade fell onto a kitchen floor in France and resulted in Linoleum Blownapart.", - "You are stuck with your debt if you can’t budge it.", - "Local Area Network in Australia : The LAN down under.", - "He broke into song because he couldn’t find the key.", - "A calendar’s days are numbered." - ]; - - let nicks = [ - "Edvard Force", - "Диванный Воин", - "Yoshka's Cat", - "XAOSHammer", - "Kino Konformist" ]; - const chatwin = document.getElementById("chatwin"); +const anchor = document.getElementById("anchor"); -function randomMessage() { - return messages[(Math.random() * messages.length) | 0]; +// Массив ключевых слов для выделения (без учета регистра) +const specialKeywords = ['sergshemet', 'sergeyshemet', 'sshemet', 'сергей', 'серёга', 'админ']; + +// Функция для проверки содержит ли сообщение ключевые слова +function containsSpecialKeywords(text) { + const lowerText = text.toLowerCase(); + return specialKeywords.some(keyword => lowerText.includes(keyword.toLowerCase())); } -function randomNick() { - return nicks[(Math.random() * nicks.length) | 0]; -} - -function randomChat() { - return Math.random() > 0.5 ? "tg" : "yt"; +// Создание новой строки чата из 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 { + existingMsg.classList.remove("highlight-message"); + } + 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() { - chatwin.scrollTop = chatwin.scrollHeight; + // Используем setTimeout чтобы прокрутка происходила после добавления DOM элемента + setTimeout(() => { + window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); + }, 10); } -function removeOldMessages() { - const children = Array.from(chatwin.children); - while (children.length > 15) { - chatwin.removeChild(children[0]); +// Удаление старых сообщений +function removeOldMessages(maxMessages = 200) { + const messageRows = document.getElementsByClassName("chatRow"); + const rowsToRemove = messageRows.length - maxMessages; + + if (rowsToRemove > 0) { + for (let i = 0; i < rowsToRemove; i++) { + if (messageRows[0]) { + messageRows[0].remove(); + } + } } } -function createChatLine() { - const nm = document.createElement("div"); - nm.className = "nameline " + randomChat(); - nm.textContent = randomNick(); - - const msg = document.createElement("div"); - msg.className = "msgline"; - msg.textContent = randomMessage(); - - const rw = document.createElement("div"); - rw.className = "chatRow"; - rw.appendChild(nm); - rw.appendChild(msg); - - chatwin.appendChild(rw); - scrollToBottom(); - removeOldMessages(); -} - -function createNewLine(json) { - json.forEach(element => { - const existing = document.getElementById(element.id); - if (existing) { - existing.querySelector('.msgline').textContent = element.msg; - return; - } - - const nm = document.createElement("div"); - nm.className = "nameline " + element.type; - nm.textContent = element.sendr; - - if (element.type === "donate") { - nm.innerHTML += `

${element.amount}

`; - } - - const msg = document.createElement("div"); - msg.className = "msgline"; - msg.textContent = element.msg; - msg.id = element.id; - - if (element.type === "donate") { - msg.style.backgroundColor = "#0000FF20"; - } - - const rw = document.createElement("div"); - rw.className = "chatRow"; - rw.appendChild(nm); - rw.appendChild(msg); - - chatwin.appendChild(rw); - scrollToBottom(); - removeOldMessages(); - }); -} - +// Запрос новых сообщений с сервера function requestNewLines() { fetch("http://localhost:8008/") - .then(response => response.json()) - .then(data => createNewLine(data)) - .catch(error => console.error('Ошибка:', error)); + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + createNewLine(data); + }) + .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 = '
Ошибка
Не удалось подключиться к серверу чата
'; + chatwin.insertBefore(errorDiv, anchor); + scrollToBottom(); + } + }); } -setInterval(requestNewLines, 1000); -// setInterval(createChatLine, 3000); \ No newline at end of file +// Инициализация чата +function initChat() { + // Прокручиваем сразу вниз при загрузке + scrollToBottom(); + + // Запрашиваем сообщения сразу при загрузке + requestNewLines(); + + // Запускаем периодический опрос + setInterval(requestNewLines, 1000); +} + +// Запускаем чат когда DOM загружен +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initChat); +} else { + initChat(); +} \ No newline at end of file