working TG YT TW
This commit is contained in:
@@ -2,11 +2,13 @@
|
|||||||
bot_secret = 6454033742:AAFj2rUoVb2jJ_Lew4eiIecdr7s7xbrqeNU
|
bot_secret = 6454033742:AAFj2rUoVb2jJ_Lew4eiIecdr7s7xbrqeNU
|
||||||
enabled = 1
|
enabled = 1
|
||||||
|
|
||||||
[Youtube]
|
[YouTube]
|
||||||
video_id = SsC29jqiNto
|
video_id = 3xLyYdp0UFg
|
||||||
|
enabled = 1
|
||||||
|
|
||||||
[Twitch]
|
[Twitch]
|
||||||
channel = wanderbraun
|
channel = coulthardf1
|
||||||
|
enabled = 1
|
||||||
|
|
||||||
[Alerts]
|
[Alerts]
|
||||||
app_id = 12554
|
app_id = 12554
|
||||||
|
|||||||
2
huy.py
2
huy.py
@@ -29,7 +29,7 @@ import twitch
|
|||||||
|
|
||||||
# Reading Configs
|
# Reading Configs
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read("E:/Games/cgi-bin/config.ini")
|
config.read("config.ini")
|
||||||
|
|
||||||
|
|
||||||
alerts = DonationAlertsAPI(
|
alerts = DonationAlertsAPI(
|
||||||
|
|||||||
620
huySeek.py
620
huySeek.py
@@ -6,373 +6,281 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import socket
|
||||||
|
import emoji
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import time
|
import time
|
||||||
|
import tzlocal
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
import pytchat
|
|
||||||
import telegram
|
|
||||||
from donationalerts import DonationAlertsAPI, Scopes
|
|
||||||
from urllib import parse
|
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')
|
sys.path.append('twitchchatirc')
|
||||||
import twitch
|
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):
|
class DateTimeEncoder(json.JSONEncoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, datetime):
|
if isinstance(o, datetime):
|
||||||
return o.isoformat()
|
return o.isoformat()
|
||||||
return super().default(o)
|
if isinstance(o, bytes):
|
||||||
|
return list(o)
|
||||||
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
# ====================
|
LOCAL_TIMEZONE = tzlocal.get_localzone_name()
|
||||||
# Точка входа
|
tz = ZoneInfo('Asia/Yekaterinburg')
|
||||||
# ====================
|
|
||||||
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')
|
|
||||||
|
|
||||||
aggregator = ChatAggregator()
|
all_comments = []
|
||||||
aggregator.run()
|
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)
|
||||||
103
index.html
103
index.html
@@ -1,36 +1,59 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>SSH UltraChat</title>
|
<title>template</title>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed">
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #494949;
|
background-color: #494949;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
align-content: flex-end;
|
||||||
|
|
||||||
|
|
||||||
font-family: "Roboto Condensed", sans-serif;
|
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 {
|
#chatwin {
|
||||||
height: 80vh;
|
/* color: white; */
|
||||||
max-width: 500px;
|
height: 100%;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
|
max-width: 400px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chatRow {
|
.chatRow {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 1px;
|
||||||
width: 100%;
|
animation: fadeInUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nameline {
|
.nameline {
|
||||||
@@ -38,23 +61,28 @@
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
flex-direction: column;
|
||||||
|
vertical-align: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msgline {
|
.highlight-message {
|
||||||
padding: 5px;
|
color: #ffb6c1 !important; /* Нежно-светло-розовый */
|
||||||
width: 75%;
|
font-weight: 500;
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Сохраняем оригинальные стили фона */
|
|
||||||
|
|
||||||
.yt {
|
.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-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;
|
background-color: #E53935A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tg {
|
.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;
|
background-color: #2196F3A0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +90,10 @@
|
|||||||
background-color: #6034b2A0 ;
|
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>");
|
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 {
|
.donate {
|
||||||
background-image: url('back-coin.jpg');
|
background-image: url('back-coin.jpg');
|
||||||
|
/* background-size: 500px; */
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +101,43 @@
|
|||||||
background-color: #00bb77;
|
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>");
|
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>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<script src="script.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
BIN
midi/kimi_no_kioku_simple.mid
Normal file
BIN
midi/kimi_no_kioku_simple.mid
Normal file
Binary file not shown.
69
midi/midi.py
Normal file
69
midi/midi.py
Normal 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
234
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 chatwin = document.getElementById("chatwin");
|
||||||
|
const anchor = document.getElementById("anchor");
|
||||||
|
|
||||||
function randomMessage() {
|
// Массив ключевых слов для выделения (без учета регистра)
|
||||||
return messages[(Math.random() * messages.length) | 0];
|
const specialKeywords = ['sergshemet', 'sergeyshemet', 'sshemet', 'сергей', 'серёга', 'админ'];
|
||||||
}
|
|
||||||
|
// Функция для проверки содержит ли сообщение ключевые слова
|
||||||
function randomNick() {
|
function containsSpecialKeywords(text) {
|
||||||
return nicks[(Math.random() * nicks.length) | 0];
|
const lowerText = text.toLowerCase();
|
||||||
}
|
return specialKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()));
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Создание новой строки чата из JSON данных
|
||||||
function createNewLine(json) {
|
function createNewLine(json) {
|
||||||
|
// Сортируем сообщения по дате (самые старые первыми)
|
||||||
|
json.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
|
|
||||||
json.forEach(element => {
|
json.forEach(element => {
|
||||||
const existing = document.getElementById(element.id);
|
// Проверяем, существует ли уже элемент с таким ID
|
||||||
if (existing) {
|
let existingMsg = document.getElementById(element["id"]);
|
||||||
existing.querySelector('.msgline').textContent = element.msg;
|
if (existingMsg) {
|
||||||
|
// Обновляем текст существующего сообщения
|
||||||
|
existingMsg.innerHTML = element["msg"];
|
||||||
|
|
||||||
|
// Проверяем на ключевые слова для подсветки
|
||||||
|
if (containsSpecialKeywords(element["msg"])) {
|
||||||
|
existingMsg.classList.add("highlight-message");
|
||||||
|
} else {
|
||||||
|
existingMsg.classList.remove("highlight-message");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nm = document.createElement("div");
|
// Создаем блок имени
|
||||||
nm.className = "nameline " + element.type;
|
const nameBlock = document.createElement("div");
|
||||||
nm.textContent = element.sendr;
|
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";
|
const msgBlock = document.createElement("div");
|
||||||
msg.textContent = element.msg;
|
msgBlock.className = "msgline";
|
||||||
msg.id = element.id;
|
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";
|
if (element["type"] == "donate") {
|
||||||
rw.appendChild(nm);
|
msgBlock.style.backgroundColor = "#0000FF20";
|
||||||
rw.appendChild(msg);
|
}
|
||||||
|
|
||||||
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();
|
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/")
|
function initChat() {
|
||||||
.then(response => response.json())
|
// Прокручиваем сразу вниз при загрузке
|
||||||
.then(data => createNewLine(data))
|
scrollToBottom();
|
||||||
.catch(error => console.error('Ошибка:', error));
|
|
||||||
|
// Запрашиваем сообщения сразу при загрузке
|
||||||
|
requestNewLines();
|
||||||
|
|
||||||
|
// Запускаем периодический опрос
|
||||||
|
setInterval(requestNewLines, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(requestNewLines, 1000);
|
// Запускаем чат когда DOM загружен
|
||||||
// setInterval(createChatLine, 3000);
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initChat);
|
||||||
|
} else {
|
||||||
|
initChat();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user