Files
youtube-chat-webhook-v2/pytchat_listener.py

150 lines
5.9 KiB
Python

import pytchat
import sys
import os
import time
import re
import json
from datetime import datetime
from rich.console import Console
from rich.style import Style
from rich.text import Text
console = Console()
# Define the emoji pattern and coloring function
emoji_pattern = re.compile(
r'('
r' \U0001F600-\U0001F64F' # emoticons
r' \U0001F300-\U0001F5FF' # symbols & pictographs
r' \U0001F680-\U0001F6FF' # transport & map symbols
r' \U0001F1E0-\U0001F1FF' # flags (iOS)
r' \u2702-\u27B0' # Dingbats
r' \u24C2-\u2B55' # Enclosed characters
r' \U0001F900-\U0001F9FF' # Supplemental Symbols & Pictographs
r' \u200D' # ZWJ
r' \uFE0F' # VS-16
r')+', flags=re.UNICODE)
def colour_emoji(txt):
return emoji_pattern.sub(r'[magenta]\1[/magenta]', txt)
# --- User Color Management ---
USER_COLORS_FILE = "user_colors.json"
# A palette of distinct colors for usernames
COLOR_PALETTE = [
"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF", "#FF00FF",
"#FFA500", "#800080", "#008000", "#FFC0CB", "#808000", "#008080",
"#C0C0C0", "#800000", "#000080", "#FFD700", "#ADFF2F", "#FF69B4"
]
user_color_map = {}
def load_user_colors():
global user_color_map
if os.path.exists(USER_COLORS_FILE):
try:
with open(USER_COLORS_FILE, 'r') as f:
user_color_map = json.load(f)
except json.JSONDecodeError:
console.print(f"[red]Error loading {USER_COLORS_FILE}. File might be corrupted. Starting with empty colors.[/red]")
user_color_map = {}
def save_user_colors():
with open(USER_COLORS_FILE, 'w') as f:
json.dump(user_color_map, f, indent=4)
def get_user_color(author_id):
global user_color_map
if author_id not in user_color_map:
# Assign a new color from the palette
# Cycle through colors, or pick randomly if palette is exhausted
next_color_index = len(user_color_map) % len(COLOR_PALETTE)
user_color_map[author_id] = COLOR_PALETTE[next_color_index]
save_user_colors() # Save immediately after assigning a new color
return user_color_map[author_id]
# --- Main Script Logic ---
def main():
# Clear the terminal screen
os.system('clear')
video_id = input("Enter the YouTube Live Stream Video ID: ")
# Setup chat logging
log_dir = "chat_logs"
os.makedirs(log_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = os.path.join(log_dir, f"chat_{video_id}_{timestamp}.log")
log_file = open(log_filename, "w", encoding="utf-8")
console.print(f"[green]Chat will be logged to {log_filename}[/green]")
# Load existing user colors from user_colors.json
load_user_colors()
try:
livechat = pytchat.create(video_id=video_id)
console.print(f"[green]Listening to live chat for video ID: {video_id}[/green]")
console.print(f"[green]Video ID accepted: {video_id}[/green]")
# Wait for 5 seconds, then clear the screen
time.sleep(5)
os.system('clear')
message_count = 0
while livechat.is_alive():
for c in livechat.get().sync_items():
author_display_name = c.author.name
# pytchat provides author.channelId, which is perfect for unique identification
author_channel_id = c.author.channelId
message_text = c.message
# Process text-based emotes (e.g., :face-purple-sweating:)
message_text = re.sub(r'(:[a-zA-Z0-9_-]+:)', r'[blue]\1[/blue]', message_text)
# Apply emoji coloring using the new function
message_text = colour_emoji(message_text)
# Get persistent color for the user
user_color = get_user_color(author_channel_id)
username_style = Style(color=user_color, bold=True)
# Create a Text object for the username and apply style directly
username_text = Text(f"{author_display_name}: ", style=username_style)
# Create a Text object for the message, interpreting rich markup
# This allows rich markup (e.g., [bold]) within the message text itself to be rendered.
message_text_rich = Text.from_markup(message_text)
# Assemble the full message
full_message_text = Text.assemble(username_text, message_text_rich)
# Alternate background styles
background_style = Style(bgcolor="#2B2B2B") if message_count % 2 == 0 else Style(bgcolor="#3A3A3A")
# Apply the background style to the entire Text object
# This ensures the background color extends across the full width of the terminal
# for all lines of the message, even if it wraps.
full_message_text.stylize(background_style)
# Print the message, letting rich handle wrapping and filling the width
# The 'width' parameter ensures the background fills the terminal width.
# 'overflow="crop"' prevents text from wrapping beyond the terminal width.
console.print(full_message_text, width=console.width, overflow="crop")
log_file.write(f"{datetime.now().strftime("%H:%M:%S")} {full_message_text.plain}\n")
message_count += 1
except Exception as e:
console.print(f"[red]An error occurred: {e}[/red]")
finally:
log_file.close()
save_log = input(f"\nDo you want to save the chat log to {log_filename}? (y/n): ").lower()
if save_log != 'y' and save_log != 'yes':
os.remove(log_filename)
console.print(f"[red]Chat log not saved. {log_filename} deleted.[/red]")
else:
console.print(f"[green]Chat log saved to {log_filename}[/green]")
if __name__ == '__main__':
main()