186 lines
5.7 KiB
Python
186 lines
5.7 KiB
Python
import requests
|
|
import config
|
|
import urllib.parse
|
|
import os
|
|
import webbrowser
|
|
from config import CLIENT_ID, CLIENT_SECRET, BOT_CLIENT_ID, BOT_CLIENT_SECRET, REDIRECT_URI
|
|
|
|
SCOPES = [
|
|
"channel:read:subscriptions",
|
|
"bits:read",
|
|
"moderator:read:followers",
|
|
"channel:read:redemptions",
|
|
"channel:manage:redemptions",
|
|
"channel:manage:raids",
|
|
"channel:manage:broadcast",
|
|
"user:read:email",
|
|
"chat:read",
|
|
"chat:edit",
|
|
]
|
|
|
|
BOT_SCOPES = ["chat:read", "chat:edit"] # Bot-only permissions
|
|
TOKEN_FILE = "bot_token.txt" # Stores bot token locally
|
|
|
|
|
|
def _request_token(payload, client_id, client_secret):
|
|
"""Internal function to request an OAuth token from Twitch."""
|
|
url = "https://id.twitch.tv/oauth2/token"
|
|
payload.update({
|
|
"client_id": client_id,
|
|
"client_secret": client_secret
|
|
})
|
|
|
|
response = requests.post(url, data=payload).json()
|
|
|
|
if "access_token" in response:
|
|
return response
|
|
else:
|
|
print(f"❌ Error getting token: {response}")
|
|
return None
|
|
|
|
|
|
### 🔹 MAIN ACCOUNT (BROADCASTER) AUTH ###
|
|
def get_oauth_url():
|
|
"""Generates OAuth URL for broadcaster authentication (event subscriptions, rewards, etc.)."""
|
|
scope_str = " ".join(SCOPES)
|
|
encoded_scope = urllib.parse.quote(scope_str)
|
|
|
|
url = (
|
|
f"https://id.twitch.tv/oauth2/authorize"
|
|
f"?client_id={CLIENT_ID}"
|
|
f"&redirect_uri={urllib.parse.quote(REDIRECT_URI)}"
|
|
f"&response_type=code"
|
|
f"&scope={encoded_scope}"
|
|
)
|
|
|
|
print(f"🔗 Opening browser for broadcaster authentication: {url}")
|
|
webbrowser.open(url)
|
|
return url
|
|
|
|
|
|
def get_user_access_token(auth_code):
|
|
"""Gets a user access token for the broadcaster account."""
|
|
print(f"🔄 Exchanging authorization code for token: {auth_code}")
|
|
|
|
token_data = _request_token({
|
|
"code": auth_code,
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": REDIRECT_URI
|
|
}, CLIENT_ID, CLIENT_SECRET)
|
|
|
|
if token_data:
|
|
config.USER_ACCESS_TOKEN = token_data["access_token"]
|
|
config.REFRESH_TOKEN = token_data.get("refresh_token", "")
|
|
|
|
print(f"✅ Broadcaster access token retrieved successfully: {config.USER_ACCESS_TOKEN[:10]}...")
|
|
return config.USER_ACCESS_TOKEN
|
|
else:
|
|
print(f"❌ Failed to exchange authorization code. Response: {token_data}")
|
|
return None
|
|
|
|
def refresh_user_access_token():
|
|
"""Refreshes the user access token when it expires."""
|
|
if not config.REFRESH_TOKEN:
|
|
print("❌ No refresh token available. Broadcaster must reauthenticate.")
|
|
return None
|
|
|
|
token_data = _request_token({
|
|
"grant_type": "refresh_token",
|
|
"refresh_token": config.REFRESH_TOKEN
|
|
}, CLIENT_ID, CLIENT_SECRET)
|
|
|
|
if token_data:
|
|
config.USER_ACCESS_TOKEN = token_data["access_token"]
|
|
config.REFRESH_TOKEN = token_data.get("refresh_token", "")
|
|
print("🔄 Broadcaster access token refreshed.")
|
|
|
|
return config.USER_ACCESS_TOKEN
|
|
|
|
|
|
def get_app_access_token():
|
|
"""Gets an app access token for general API use (not user-specific)."""
|
|
token_data = _request_token({"grant_type": "client_credentials"}, CLIENT_ID, CLIENT_SECRET)
|
|
return token_data.get("access_token") if token_data else None
|
|
|
|
|
|
### 🔹 BOT ACCOUNT AUTH ###
|
|
def get_bot_oauth_url():
|
|
"""Generates OAuth URL for bot authentication (chat only)."""
|
|
scope_str = " ".join(BOT_SCOPES)
|
|
encoded_scope = urllib.parse.quote(scope_str)
|
|
|
|
url = (
|
|
f"https://id.twitch.tv/oauth2/authorize"
|
|
f"?client_id={BOT_CLIENT_ID}"
|
|
f"&redirect_uri={urllib.parse.quote(REDIRECT_URI)}"
|
|
f"&response_type=code"
|
|
f"&scope={encoded_scope}"
|
|
)
|
|
|
|
print(f"🔗 Opening browser for bot authentication: {url}")
|
|
webbrowser.open(url)
|
|
return url
|
|
|
|
|
|
def load_bot_token():
|
|
"""Loads the bot token from a local file."""
|
|
if os.path.exists(TOKEN_FILE):
|
|
with open(TOKEN_FILE, "r") as f:
|
|
return f.read().strip()
|
|
return None
|
|
|
|
|
|
def save_bot_token(token):
|
|
"""Saves the bot token to a local file."""
|
|
with open(TOKEN_FILE, "w") as f:
|
|
f.write(token)
|
|
|
|
|
|
def get_bot_access_token(auth_code=None):
|
|
"""Gets a bot access token for chat authentication."""
|
|
if auth_code:
|
|
token_data = _request_token({
|
|
"code": auth_code,
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": REDIRECT_URI
|
|
}, BOT_CLIENT_ID, BOT_CLIENT_SECRET)
|
|
else:
|
|
print("❌ No bot authorization code provided. Re-authentication required.")
|
|
get_bot_oauth_url()
|
|
return None
|
|
|
|
if token_data:
|
|
config.BOT_OAUTH_TOKEN = token_data["access_token"]
|
|
save_bot_token(config.BOT_OAUTH_TOKEN)
|
|
print(f"✅ Bot access token retrieved successfully.")
|
|
return config.BOT_OAUTH_TOKEN
|
|
|
|
return None
|
|
|
|
|
|
def ensure_bot_token():
|
|
"""Ensures the bot has a valid token before starting chat."""
|
|
bot_token = load_bot_token()
|
|
|
|
if bot_token:
|
|
# Validate the token
|
|
headers = {
|
|
"Authorization": f"Bearer {bot_token}"
|
|
}
|
|
response = requests.get("https://id.twitch.tv/oauth2/validate", headers=headers)
|
|
|
|
if response.status_code == 200:
|
|
# Token is valid, format it correctly for IRC
|
|
if bot_token.startswith("oauth:"):
|
|
config.BOT_OAUTH_TOKEN = bot_token
|
|
else:
|
|
config.BOT_OAUTH_TOKEN = f"oauth:{bot_token}"
|
|
print("✅ Using saved bot token.")
|
|
return
|
|
else:
|
|
print("🔄 Existing bot token is invalid. Getting a new one.")
|
|
|
|
print("🔄 No valid bot token found. Redirecting to Twitch for bot authentication.")
|
|
get_bot_oauth_url()
|
|
|