working TG YT TW

This commit is contained in:
sShemet
2026-01-07 02:22:34 +05:00
parent 62263b713a
commit 7561e6f411
7 changed files with 592 additions and 534 deletions

View File

@@ -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

2
huy.py
View File

@@ -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(

View File

@@ -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
# Чтение конфигурации
config = configparser.ConfigParser()
config.read("config.ini")
# Инициализация модулей на основе конфигурации
chat_connectors = {}
chat_comments = {}
# 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 (опционально)
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'] = []
# 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 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']
# ====================
# Модели данных
# ====================
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
}
# ====================
# Сервисы
# ====================
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
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 "Админ"
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
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')
aggregator = ChatAggregator()
aggregator.run()
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
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)

View File

@@ -1,36 +1,59 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>SSH UltraChat</title>
<title>template</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed">
<style>
body {
margin: 0;
padding: 0;
background-color: #494949;
display: flex;
align-items: flex-start;
align-items: center;
justify-content: center;
height: 100vh;
align-content: flex-end;
font-family: "Roboto Condensed", sans-serif;
color: #fff;
font-size: 1em;
letter-spacing: 0.1px;
color: lighter;
text-rendering: optimizeLegibility;
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
-webkit-font-smoothing: antialiased;
}
#chatwin {
height: 80vh;
max-width: 500px;
/* color: white; */
height: 100%;
min-width: 150px;
max-width: 400px;
display: flex;
flex-direction: column;
padding: 10px;
position: relative;
}
.chatRow {
display: flex;
flex-direction: row;
margin-bottom: 5px;
width: 100%;
margin-bottom: 1px;
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.nameline {
@@ -38,23 +61,28 @@
padding: 5px;
width: 25%;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
flex-direction: column;
vertical-align: auto;
}
.msgline {
padding: 5px;
width: 75%;
color: #fff;
.highlight-message {
color: #ffb6c1 !important; /* Нежно-светло-розовый */
font-weight: 500;
}
/* Сохраняем оригинальные стили фона */
.yt {
background-image: url('data:image/svg+xml,<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg"><g fill="%239C92AC" fill-opacity="0.4" fill-rule="evenodd"><path d="M0 40L40 0H20L0 20M40 40V20L20 40"/></g></svg>');
background-color: #E53935A0;
}
.tg {
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd"><g fill="%239C92AC" fill-opacity="0.4"><path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>');
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd"><g fill="%239C92AC" fill-opacity="0.4"><path opacity=".5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>');
background-color: #2196F3A0;
}
@@ -62,9 +90,10 @@
background-color: #6034b2A0 ;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='60' viewBox='0 0 20 12'><g fill-rule='evenodd'><g id='charlie-brown' fill='%236441a5' fill-opacity='0.35'><path d='M9.8 12L0 2.2V.8l10 10 10-10v1.4L10.2 12h-.4zm-4 0L0 6.2V4.8L7.2 12H5.8zm8.4 0L20 6.2V4.8L12.8 12h1.4zM9.8 0l.2.2.2-.2h-.4zm-4 0L10 4.2 14.2 0h-1.4L10 2.8 7.2 0H5.8z'/></g></g></svg>");
}
/* <svg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'><rect fill='#6441A5' width='120' height='120'/><polygon fill='#5C3C98' fill-opacity='1' points='120 120 60 120 90 90 120 60 120 0 120 0 60 60 0 0 0 60 30 90 60 120 120 120 '/></svg> */
.donate {
background-image: url('back-coin.jpg');
/* background-size: 500px; */
color: black !important;
}
@@ -72,9 +101,43 @@
background-color: #00bb77;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='59' height='59' viewBox='0 0 120 120'><polygon fill='%23000' fill-opacity='0.11' points='120 0 120 60 90 30 60 0 0 0 0 0 60 60 0 120 60 120 90 90 120 60 120 0'/></svg>");
}
.msgline {
padding: 5px;
width: 75%;
color: #fff;
}
</style>
</head>
<body>
<div id="chatwin"></div>
<div id="chatwin">
<!--
<div idd="1" class="chatRow">
<div class="nameline tg">Edvard Force</div>
<div class="msgline">вот Тенчу Великому зашла , он в ней быстро разобрался и втащил за один стрим )))) ждем когда до нее доберется ))))</div>
</div>
<div idd="2" class="chatRow">
<div class="nameline tg" >Диванный Воин</div>
<div class="msgline">dc или если ты действительно решишь дать бан то мышкой два клика не сделаешь?</div>
</div>
<div idd="3" class="chatRow">
<div class="nameline tg">Yoshka's Cat</div>
<div class="msgline">великий ну эта часть ванпис лутьше чем вариорсы надо признать</div>
</div>
<div idd="4" class="chatRow">
<div class="nameline yt">XAOSHammer</div>
<div class="msgline">Великий а куда друг твой пропал спец по японскому языку ?))))</div>
</div>
<div idd="5" class="chatRow">
<div class="nameline tg">Kino Konformist</div>
<div class="msgline">вот Тенчу Великому зашла , он в ней быстро разобрался и втащил за один стрим )))) ждем когда до нее доберется ))))</div>
</div>
-->
<div id="anchor"></div>
</div>
<script src="script.js"></script>
</body>

Binary file not shown.

69
midi/midi.py Normal file
View File

@@ -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")

234
script.js
View File

@@ -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? Hes all right now.",
"The roundest knight at King Arthurs 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 cant 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 its your vote that counts; in feudalism, its your Count that votes.",
"A chicken crossing the road: poultry in motion.",
"If you dont 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 Ill 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 cant budge it.",
"Local Area Network in Australia : The LAN down under.",
"He broke into song because he couldnt find the key.",
"A calendars 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];
}
function randomNick() {
return nicks[(Math.random() * nicks.length) | 0];
}
function randomChat() {
return Math.random() > 0.5 ? "tg" : "yt";
}
function scrollToBottom() {
chatwin.scrollTop = chatwin.scrollHeight;
}
function removeOldMessages() {
const children = Array.from(chatwin.children);
while (children.length > 15) {
chatwin.removeChild(children[0]);
}
}
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();
// Массив ключевых слов для выделения (без учета регистра)
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 => {
const existing = document.getElementById(element.id);
if (existing) {
existing.querySelector('.msgline').textContent = element.msg;
// Проверяем, существует ли уже элемент с таким 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 nm = document.createElement("div");
nm.className = "nameline " + element.type;
nm.textContent = element.sendr;
// Создаем блок имени
const nameBlock = document.createElement("div");
nameBlock.className = "nameline " + element["type"];
if (element.type === "donate") {
nm.innerHTML += `<br><p style="color: red">${element.amount}</p>`;
// Для донатов добавляем сумму
if (element["type"] == "donate") {
nameBlock.innerHTML = element["sendr"] + '<br><p style="color: red">' + element['amount'] + "</p>";
} else {
nameBlock.innerHTML = element["sendr"];
}
const msg = document.createElement("div");
msg.className = "msgline";
msg.textContent = element.msg;
msg.id = element.id;
// Создаем блок сообщения
const msgBlock = document.createElement("div");
msgBlock.className = "msgline";
msgBlock.innerHTML = element["msg"];
msgBlock.id = element["id"];
if (element.type === "donate") {
msg.style.backgroundColor = "#0000FF20";
// Добавляем подсветку если есть ключевые слова
if (containsSpecialKeywords(element["msg"])) {
msgBlock.classList.add("highlight-message");
}
const rw = document.createElement("div");
rw.className = "chatRow";
rw.appendChild(nm);
rw.appendChild(msg);
// Стиль для донатов
if (element["type"] == "donate") {
msgBlock.style.backgroundColor = "#0000FF20";
}
chatwin.appendChild(rw);
// Создаем строку чата
const row = document.createElement("div");
row.className = "chatRow";
row.setAttribute("name", element["id"]);
row.appendChild(nameBlock);
row.appendChild(msgBlock);
// ВСЕГДА вставляем новое сообщение перед якорем (внизу)
chatwin.insertBefore(row, anchor);
// Прокручиваем сразу к новому сообщению
scrollToBottom();
removeOldMessages();
});
// Ограничиваем количество сообщений (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;
if (rowsToRemove > 0) {
for (let i = 0; i < rowsToRemove; i++) {
if (messageRows[0]) {
messageRows[0].remove();
}
}
}
}
// Запрос новых сообщений с сервера
function requestNewLines() {
fetch("http://localhost:8008/")
.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 = '<div class="nameline hello">Ошибка</div><div class="msgline">Не удалось подключиться к серверу чата</div>';
chatwin.insertBefore(errorDiv, anchor);
scrollToBottom();
}
});
}
function requestNewLines() {
fetch("http://localhost:8008/")
.then(response => response.json())
.then(data => createNewLine(data))
.catch(error => console.error('Ошибка:', error));
// Инициализация чата
function initChat() {
// Прокручиваем сразу вниз при загрузке
scrollToBottom();
// Запрашиваем сообщения сразу при загрузке
requestNewLines();
// Запускаем периодический опрос
setInterval(requestNewLines, 1000);
}
setInterval(requestNewLines, 1000);
// setInterval(createChatLine, 3000);
// Запускаем чат когда DOM загружен
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initChat);
} else {
initChat();
}