Files
TwitchAlerts/twitch_events.py
T
2025-04-14 17:38:21 -04:00

168 lines
6.0 KiB
Python

import requests
import config
import twitch_auth
from config import BROADCASTER_ID
import websocket
import threading
import config
import re
import pygame
import json
# Initialize pygame for sound effects
pygame.mixer.init()
CLAP_SOUND = pygame.mixer.Sound("clap.wav")
# This variable will be set by app.py
handle_chat_message = None
def delete_existing_subscriptions():
"""Deletes all existing Twitch EventSub subscriptions to prevent duplicates."""
access_token = twitch_auth.get_app_access_token()
url = "https://api.twitch.tv/helix/eventsub/subscriptions"
headers = {
"Client-ID": config.CLIENT_ID,
"Authorization": f"Bearer {access_token}"
}
response = requests.get(url, headers=headers).json()
for sub in response.get("data", []):
delete_url = f"{url}?id={sub['id']}"
requests.delete(delete_url, headers=headers)
print(f"🗑 Deleted subscription: {sub['id']}")
def subscribe_to_twitch_events():
"""Subscribes to Twitch EventSub webhooks for follow, sub, cheer, raid, and channel points."""
delete_existing_subscriptions()
app_access_token = twitch_auth.get_app_access_token()
user_access_token = config.USER_ACCESS_TOKEN
if not user_access_token:
print("❌ Error: User access token is missing. Please re-authenticate.")
return
url = "https://api.twitch.tv/helix/eventsub/subscriptions"
def subscribe(event_type, version, condition, token):
headers = {
"Client-ID": config.CLIENT_ID,
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
data = {
"type": event_type,
"version": version,
"condition": condition,
"transport": {
"method": "webhook",
"callback": config.WEBHOOK_CALLBACK,
"secret": config.WEBHOOK_SECRET
}
}
response = requests.post(url, json=data, headers=headers)
print(f"✅ Subscribed to {event_type}: {response.json()}")
subscribe("channel.follow", "2", {"broadcaster_user_id": BROADCASTER_ID, "moderator_user_id": BROADCASTER_ID}, app_access_token)
subscribe("channel.subscribe", "1", {"broadcaster_user_id": BROADCASTER_ID}, app_access_token)
subscribe("channel.cheer", "1", {"broadcaster_user_id": BROADCASTER_ID}, app_access_token)
subscribe("channel.raid", "1", {"to_broadcaster_user_id": BROADCASTER_ID}, app_access_token)
subscribe("channel.channel_points_custom_reward_redemption.add", "1", {"broadcaster_user_id": BROADCASTER_ID}, app_access_token)
def parse_irc_tags(tag_string):
"""Parse IRC tags from the message prefix."""
if not tag_string:
return {}
# Remove the @ prefix
if tag_string.startswith('@'):
tag_string = tag_string[1:]
tags = {}
for tag in tag_string.split(';'):
if '=' in tag:
key, value = tag.split('=', 1)
tags[key] = value if value else None
return tags
def on_message(ws, message):
"""Handles incoming messages from Twitch chat and responds to PING."""
if message.startswith("PING"):
ws.send("PONG :tmi.twitch.tv")
return
message_lines = message.split("\r\n")
for line in message_lines:
if not line.strip():
continue
if "NOTICE" in line or "JOIN" in line or "PART" in line:
print(f"📢 System Alert: {line}")
if "PRIVMSG" in line:
try:
# Parse more complex IRC message format with tags
tags = {}
if line.startswith('@'):
tags_part, rest = line.split(' ', 1)
tags = parse_irc_tags(tags_part)
line = rest
# Extract username and message
match = re.search(r":(\w+)!\w+@\w+\.tmi\.twitch\.tv PRIVMSG #\w+ :(.+)", line)
if match:
user, message_content = match.groups()
# Handle clap emote
if "procptClap" in message_content:
print(f"👏 {user} clapped!")
CLAP_SOUND.play()
# If handle_chat_message function is set (by app.py), use it
if handle_chat_message:
# Create a copy of data to pass to the main thread
username = user
content = message_content
tags_copy = tags.copy() if tags else {}
# Use threading to avoid blocking the WebSocket thread
threading.Thread(
target=lambda: handle_chat_message(username, content, tags_copy),
daemon=True
).start()
print(f"💬 {user}: {message_content}")
except Exception as e:
print(f"Error parsing chat message: {e}")
def on_open(ws):
"""Handles connection opening to Twitch Chat."""
print("✅ Connected to Twitch Chat")
# Use the token directly from config
ws.send(f"PASS {config.BOT_OAUTH_TOKEN}")
ws.send(f"NICK {config.TWITCH_BOT_NICKNAME}")
ws.send(f"JOIN #{config.TWITCH_CHANNEL}")
# Request capabilities for additional message data
ws.send("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership")
print(f"🔗 Joined #{config.TWITCH_CHANNEL} as {config.TWITCH_BOT_NICKNAME}")
def connect_twitch_chat():
"""Creates a WebSocket connection to Twitch Chat."""
ws = websocket.WebSocketApp(
config.TWITCH_CHAT_URL,
on_message=on_message,
on_open=on_open
)
ws.run_forever()
def start_twitch_chat():
chat_thread = threading.Thread(target=connect_twitch_chat, daemon=True)
chat_thread.start()
if __name__ == "__main__":
subscribe_to_twitch_events()