172 lines
6.4 KiB
Python
172 lines
6.4 KiB
Python
# bot_twitch.py
|
|
import os
|
|
import requests
|
|
import asyncio
|
|
from twitchio.ext import commands
|
|
import importlib
|
|
import cmd_twitch
|
|
|
|
import modules
|
|
import modules.utility
|
|
|
|
class TwitchBot(commands.Bot):
|
|
def __init__(self, config, log_func):
|
|
self.client_id = os.getenv("TWITCH_CLIENT_ID")
|
|
self.client_secret = os.getenv("TWITCH_CLIENT_SECRET")
|
|
self.token = os.getenv("TWITCH_BOT_TOKEN")
|
|
self.refresh_token = os.getenv("TWITCH_REFRESH_TOKEN")
|
|
self.log = log_func # Use the logging function from bots.py
|
|
self.config = config
|
|
self.db_conn = None # We'll set this
|
|
self.help_data = None # We'll set this later
|
|
|
|
# 1) Initialize the parent Bot FIRST
|
|
super().__init__(
|
|
token=self.token,
|
|
prefix="!",
|
|
initial_channels=config["twitch_channels"]
|
|
)
|
|
|
|
self.log("Twitch bot initiated")
|
|
|
|
cmd_class = str(type(self._commands)).split("'", 2)[1]
|
|
log_func(f"TwitchBot._commands type: {cmd_class}", "DEBUG")
|
|
|
|
# 2) Then load commands
|
|
self.load_commands()
|
|
|
|
def set_db_connection(self, db_conn):
|
|
"""
|
|
Store the DB connection so that commands can use it.
|
|
"""
|
|
self.db_conn = db_conn
|
|
|
|
async def event_message(self, message):
|
|
"""Logs and processes incoming Twitch messages."""
|
|
if message.echo:
|
|
return # Ignore bot's own messages
|
|
|
|
# Log the command if it's a command
|
|
if message.content.startswith("!"):
|
|
_cmd = message.content[1:] # Remove the leading "!"
|
|
_cmd_args = _cmd.split(" ")[1:]
|
|
_cmd = _cmd.split(" ", 1)[0]
|
|
self.log(f"Command '{_cmd}' (Twitch) initiated by {message.author.name} in #{message.channel.name}", "DEBUG")
|
|
if len(_cmd_args) > 1: self.log(f"!{_cmd} arguments: {_cmd_args}", "DEBUG")
|
|
|
|
# Process the message for command execution
|
|
await self.handle_commands(message)
|
|
|
|
async def event_ready(self):
|
|
self.log(f"Twitch bot is online as {self.nick}")
|
|
|
|
async def refresh_access_token(self):
|
|
"""
|
|
Refreshes the Twitch access token using the stored refresh token.
|
|
Retries up to 2 times before logging a fatal error.
|
|
"""
|
|
self.log("Attempting to refresh Twitch token...")
|
|
|
|
url = "https://id.twitch.tv/oauth2/token"
|
|
params = {
|
|
"client_id": self.client_id,
|
|
"client_secret": self.client_secret,
|
|
"refresh_token": self.refresh_token,
|
|
"grant_type": "refresh_token"
|
|
}
|
|
|
|
for attempt in range(3): # Attempt up to 3 times
|
|
try:
|
|
response = requests.post(url, params=params)
|
|
data = response.json()
|
|
|
|
if "access_token" in data:
|
|
self.token = data["access_token"]
|
|
self.refresh_token = data.get("refresh_token", self.refresh_token)
|
|
|
|
os.environ["TWITCH_BOT_TOKEN"] = self.token
|
|
os.environ["TWITCH_REFRESH_TOKEN"] = self.refresh_token
|
|
self.update_env_file()
|
|
|
|
self.log("Twitch token refreshed successfully.")
|
|
return # Success, exit function
|
|
else:
|
|
self.log(f"Twitch token refresh failed (Attempt {attempt+1}/3): {data}", "WARNING")
|
|
|
|
except Exception as e:
|
|
self.log(f"Twitch token refresh error (Attempt {attempt+1}/3): {e}", "ERROR")
|
|
|
|
await asyncio.sleep(10) # Wait before retrying
|
|
|
|
# If all attempts fail, log error
|
|
self.log("Twitch token refresh failed after 3 attempts.", "FATAL")
|
|
|
|
def update_env_file(self):
|
|
"""
|
|
Updates the .env file with the new Twitch token.
|
|
"""
|
|
try:
|
|
with open(".env", "r") as file:
|
|
lines = file.readlines()
|
|
|
|
with open(".env", "w") as file:
|
|
for line in lines:
|
|
if line.startswith("TWITCH_BOT_TOKEN="):
|
|
file.write(f"TWITCH_BOT_TOKEN={self.token}\n")
|
|
elif line.startswith("TWITCH_REFRESH_TOKEN="):
|
|
file.write(f"TWITCH_REFRESH_TOKEN={self.refresh_token}\n")
|
|
else:
|
|
file.write(line)
|
|
|
|
self.log("Updated .env file with new Twitch token.")
|
|
|
|
except Exception as e:
|
|
self.log(f"Failed to update .env file: {e}", "ERROR")
|
|
|
|
def load_commands(self):
|
|
"""
|
|
Load all commands from cmd_twitch.py
|
|
"""
|
|
try:
|
|
cmd_twitch.setup(self)
|
|
self.log("Twitch commands loaded successfully.")
|
|
|
|
# Now load the help info from dictionary/help_twitch.json
|
|
help_json_path = "dictionary/help_twitch.json"
|
|
modules.utility.initialize_help_data(
|
|
bot=self,
|
|
help_json_path=help_json_path,
|
|
is_discord=False, # Twitch
|
|
log_func=self.log
|
|
)
|
|
|
|
except Exception as e:
|
|
self.log(f"Error loading Twitch commands: {e}", "ERROR")
|
|
|
|
async def run(self):
|
|
"""
|
|
Run the Twitch bot, refreshing tokens if needed.
|
|
"""
|
|
retries = 0
|
|
while True:
|
|
if retries > 3:
|
|
self.log(f"Twitch bot failed to connect after {retries} attempts.", "CIRITCAL")
|
|
break # Break loop if repeatedly failing to connect to Twitch
|
|
try:
|
|
await self.start()
|
|
#while True:
|
|
# await self.refresh_access_token()
|
|
# await asyncio.sleep(10800) # Refresh every 3 hours
|
|
except Exception as e:
|
|
retries += 1
|
|
self.log(f"Twitch bot failed to start: {e}", "CRITICAL")
|
|
if "Invalid or unauthorized Access Token passed." in str(e):
|
|
try:
|
|
await self.refresh_access_token()
|
|
except Exception as e:
|
|
self.log(f"Unable to refresh Twitch token! Twitch bot will be offline!", "CRITICAL")
|
|
if "'NoneType' object has no attribute 'cancel'" in str(e):
|
|
self.log(f"The Twitch bot experienced an initialization glitch. Try starting again", "FATAL")
|
|
await asyncio.sleep(5) # Wait before retrying to authenticate
|
|
|