commit
1635c4ef7a
|
@ -5,10 +5,10 @@ import importlib
|
|||
import cmd_discord
|
||||
|
||||
class DiscordBot(commands.Bot):
|
||||
def __init__(self, config, logger):
|
||||
def __init__(self, config, log_func):
|
||||
super().__init__(command_prefix="!", intents=discord.Intents.all())
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.log = log_func # Use the logging function from bots.py
|
||||
self.load_commands()
|
||||
|
||||
def load_commands(self):
|
||||
|
@ -18,21 +18,15 @@ class DiscordBot(commands.Bot):
|
|||
try:
|
||||
importlib.reload(cmd_discord)
|
||||
cmd_discord.setup(self)
|
||||
self.logger.info("Discord commands loaded successfully.")
|
||||
self.log("Discord commands loaded successfully.", "INFO")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading Discord commands: {e}")
|
||||
self.log(f"Error loading Discord commands: {e}", "ERROR")
|
||||
|
||||
async def on_ready(self):
|
||||
self.logger.info(f"Discord bot is online as {self.user}")
|
||||
|
||||
# @commands.command()
|
||||
# async def reload(self, ctx):
|
||||
# """ Reload all Discord commands dynamically. """
|
||||
# self.load_commands()
|
||||
# await ctx.send("Commands reloaded!")
|
||||
self.log(f"Discord bot is online as {self.user}", "INFO")
|
||||
|
||||
async def run(self, token):
|
||||
try:
|
||||
await super().start(token)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Discord bot error: {e}")
|
||||
self.log(f"Discord bot error: {e}", "CRITICAL")
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
# bot_twitch.py
|
||||
import os
|
||||
import requests
|
||||
import logging
|
||||
import asyncio
|
||||
from twitchio.ext import commands
|
||||
import importlib
|
||||
import cmd_twitch
|
||||
|
||||
class TwitchBot(commands.Bot):
|
||||
def __init__(self, config, logger):
|
||||
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.logger = logger
|
||||
self.log = log_func # Use the logging function from bots.py
|
||||
self.config = config
|
||||
|
||||
# 1) Initialize the parent Bot FIRST
|
||||
|
@ -30,7 +30,7 @@ class TwitchBot(commands.Bot):
|
|||
Refreshes the Twitch access token using the stored refresh token.
|
||||
Retries up to 2 times before logging a fatal error.
|
||||
"""
|
||||
self.logger.info("Attempting to refresh Twitch token...")
|
||||
self.log("Attempting to refresh Twitch token...", "INFO")
|
||||
|
||||
url = "https://id.twitch.tv/oauth2/token"
|
||||
params = {
|
||||
|
@ -53,19 +53,18 @@ class TwitchBot(commands.Bot):
|
|||
os.environ["TWITCH_REFRESH_TOKEN"] = self.refresh_token
|
||||
self.update_env_file()
|
||||
|
||||
self.logger.info("Twitch token refreshed successfully.")
|
||||
self.log("Twitch token refreshed successfully.", "INFO")
|
||||
return # Success, exit function
|
||||
else:
|
||||
self.logger.warning(f"Twitch token refresh failed (Attempt {attempt+1}/3): {data}")
|
||||
self.log(f"Twitch token refresh failed (Attempt {attempt+1}/3): {data}", "WARNING")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Twitch token refresh error (Attempt {attempt+1}/3): {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
|
||||
from bots import log_error
|
||||
log_error("Twitch token refresh failed after 3 attempts.")
|
||||
self.log("Twitch token refresh failed after 3 attempts.", "FATAL")
|
||||
|
||||
def update_env_file(self):
|
||||
"""
|
||||
|
@ -84,10 +83,10 @@ class TwitchBot(commands.Bot):
|
|||
else:
|
||||
file.write(line)
|
||||
|
||||
self.logger.info("Updated .env file with new Twitch token.")
|
||||
self.log("Updated .env file with new Twitch token.", "INFO")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to update .env file: {e}")
|
||||
self.log(f"Failed to update .env file: {e}", "ERROR")
|
||||
|
||||
def load_commands(self):
|
||||
"""
|
||||
|
@ -96,20 +95,25 @@ class TwitchBot(commands.Bot):
|
|||
try:
|
||||
importlib.reload(cmd_twitch)
|
||||
cmd_twitch.setup(self)
|
||||
self.logger.info("Twitch commands loaded successfully.")
|
||||
self.log("Twitch commands loaded successfully.", "INFO")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading Twitch commands: {e}")
|
||||
self.log(f"Error loading Twitch commands: {e}", "ERROR")
|
||||
|
||||
async def run(self):
|
||||
"""
|
||||
Run the Twitch bot, refreshing tokens if needed.
|
||||
"""
|
||||
try:
|
||||
await self.connect() # Connect to Twitch
|
||||
await self.start() # Start the bot event loop
|
||||
while True:
|
||||
await self.refresh_access_token()
|
||||
await asyncio.sleep(10800) # 3 hours
|
||||
self.log(f"Twitch bot connecting...", "INFO")
|
||||
self.log(f"...Consider online if no further messages", "INFO")
|
||||
await self.start()
|
||||
#while True:
|
||||
# await self.refresh_access_token()
|
||||
# await asyncio.sleep(10800) # Refresh every 3 hours
|
||||
except Exception as e:
|
||||
from bots import log_error
|
||||
log_error(f"Twitch bot failed to start: {e}")
|
||||
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")
|
||||
|
|
104
bots.py
104
bots.py
|
@ -2,11 +2,9 @@
|
|||
import os
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
from collections import Counter
|
||||
import traceback
|
||||
from discord.ext import commands
|
||||
from dotenv import load_dotenv
|
||||
from bot_discord import DiscordBot
|
||||
|
@ -28,95 +26,51 @@ except json.JSONDecodeError as e:
|
|||
sys.exit(1)
|
||||
|
||||
# Global settings
|
||||
ERROR_LOG_FILE = "error_log.txt"
|
||||
DISCORD_USER_ID = os.getenv("DISCORD_OWNER_ID") # Your Discord user ID for DM alerts
|
||||
DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN") # Discord bot token
|
||||
ERROR_CACHE = [] # Stores pending errors
|
||||
LAST_ERROR_SENT = 0 # Timestamp of last Discord error message
|
||||
ERROR_COOLDOWN = 900 # 15 minutes (900 sec) cooldown between Discord reports
|
||||
ERROR_BATCH_TIMEOUT = 5 # 5 seconds batching delay before sending
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
stream=sys.stdout
|
||||
)
|
||||
logger = logging.getLogger("Main")
|
||||
bot_start_time = time.time()
|
||||
|
||||
###############################
|
||||
# Error Handling & Discord Alerts
|
||||
# Simple Logging System
|
||||
###############################
|
||||
|
||||
async def send_discord_error_report(bot):
|
||||
def log(message, level="INFO"):
|
||||
"""
|
||||
Sends cached errors as a Discord DM with cooldowns and batching.
|
||||
If it fails, logs to error_log.txt instead.
|
||||
A simple logging function with adjustable log levels.
|
||||
Logs messages in a structured format.
|
||||
|
||||
Available levels:
|
||||
DEBUG, INFO, WARNING, ERROR, CRITICAL, FATAL
|
||||
See 'config.json' for disabling/enabling logging levels
|
||||
"""
|
||||
global ERROR_CACHE, LAST_ERROR_SENT
|
||||
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"]
|
||||
if level not in log_levels:
|
||||
level = "INFO" # Default to INFO if an invalid level is provided
|
||||
|
||||
if not ERROR_CACHE:
|
||||
return # No errors to send
|
||||
if level in config_data["log_levels"]:
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
log_message = f"[{timestamp}] [{level}] {message}"
|
||||
|
||||
# Wait for batching timeout
|
||||
await asyncio.sleep(ERROR_BATCH_TIMEOUT)
|
||||
|
||||
# Compress duplicate errors
|
||||
error_counter = Counter(ERROR_CACHE)
|
||||
compressed_errors = [f"{err} (Occurred {count} times)" if count > 1 else err for err, count in error_counter.items()]
|
||||
error_message = "\n".join(compressed_errors)
|
||||
|
||||
# Ensure message fits in Discord limits
|
||||
if len(error_message) > 2000:
|
||||
error_message = error_message[:1990] + "\n... (truncated)"
|
||||
|
||||
# Update timestamp
|
||||
LAST_ERROR_SENT = time.time()
|
||||
|
||||
try:
|
||||
user = await bot.fetch_user(DISCORD_USER_ID)
|
||||
await user.send(f"**Bot Error Report**\n\n{error_message}")
|
||||
logger.info(f"[ERROR REPORT] Sent {len(ERROR_CACHE)} errors at {time.strftime('%H:%M:%S')} | Discord Send Status: Success")
|
||||
except Exception as e:
|
||||
# If Discord DM fails, log errors to a file
|
||||
with open(ERROR_LOG_FILE, "a") as f:
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
f.write(f"\n----- {len(ERROR_CACHE)} Errors Occurred at {timestamp} -----\n")
|
||||
for err in ERROR_CACHE:
|
||||
f.write(f"{err}\n")
|
||||
|
||||
logger.error(f"[ERROR REPORT] Failed to send Discord DM. Errors saved to {ERROR_LOG_FILE}")
|
||||
|
||||
# Clear cache after sending
|
||||
ERROR_CACHE = []
|
||||
|
||||
def log_error(error_msg):
|
||||
"""
|
||||
Handles error logging with cooldowns.
|
||||
If multiple errors occur in a short time, they are cached and sent in batch.
|
||||
"""
|
||||
global ERROR_CACHE, LAST_ERROR_SENT
|
||||
|
||||
ERROR_CACHE.append(error_msg)
|
||||
if time.time() - LAST_ERROR_SENT >= ERROR_COOLDOWN:
|
||||
try:
|
||||
discord_bot
|
||||
except NameError:
|
||||
discord_bot = DiscordBot(config_data, logger)
|
||||
asyncio.create_task(send_discord_error_report(discord_bot))
|
||||
print(log_message) # Print to terminal
|
||||
except Exception:
|
||||
pass # Prevent logging failures from crashing the bot
|
||||
|
||||
# Placeholder for future expansions (e.g., file logging, Discord alerts, etc.)
|
||||
|
||||
###############################
|
||||
# Main Event Loop
|
||||
###############################
|
||||
|
||||
async def main():
|
||||
bot_start_time = time.time()
|
||||
global discord_bot, twitch_bot
|
||||
|
||||
discord_bot = DiscordBot(config_data, logger)
|
||||
twitch_bot = TwitchBot(config_data, logger)
|
||||
log("Initializing bots...", "INFO")
|
||||
|
||||
discord_task = asyncio.create_task(discord_bot.run(DISCORD_BOT_TOKEN))
|
||||
discord_bot = DiscordBot(config_data, log)
|
||||
twitch_bot = TwitchBot(config_data, log)
|
||||
|
||||
log("Starting Discord and Twitch bots...", "INFO")
|
||||
|
||||
discord_task = asyncio.create_task(discord_bot.run(os.getenv("DISCORD_BOT_TOKEN")))
|
||||
twitch_task = asyncio.create_task(twitch_bot.run())
|
||||
|
||||
await asyncio.gather(discord_task, twitch_task)
|
||||
|
@ -126,4 +80,4 @@ if __name__ == "__main__":
|
|||
asyncio.run(main())
|
||||
except Exception as e:
|
||||
error_trace = traceback.format_exc()
|
||||
log_error(f"Fatal Error: {e}\n{error_trace}")
|
||||
log(f"Fatal Error: {e}\n{error_trace}", "FATAL")
|
||||
|
|
|
@ -27,9 +27,38 @@ def ping():
|
|||
|
||||
current_time = time.time()
|
||||
elapsed = current_time - bot_start_time
|
||||
uptime_str = format_uptime(elapsed)
|
||||
response = f"Pong! I've been awake for {uptime_str}. Send coffee!"
|
||||
return f"{response}"
|
||||
uptime_str, uptime_s = format_uptime(elapsed)
|
||||
# Define thresholds in ascending order
|
||||
# (threshold_in_seconds, message)
|
||||
# The message is a short "desperation" or "awake" text.
|
||||
time_ranges = [
|
||||
(3600, f"I've been awake for {uptime_str}. I just woke up, feeling great!"), # < 1 hour
|
||||
(10800, f"I've been awake for {uptime_str}. I'm still fairly fresh!"), # 3 hours
|
||||
(21600, f"I've been awake for {uptime_str}. I'm starting to get a bit weary..."), # 6 hours
|
||||
(43200, f"I've been awake for {uptime_str}. 12 hours?! Might be time for coffee."), # 12 hours
|
||||
(86400, f"I've been awake for {uptime_str}. A whole day without sleep... I'm okay?"), # 1 day
|
||||
(172800, f"I've been awake for {uptime_str}. Two days... I'd love a nap."), # 2 days
|
||||
(259200, f"I've been awake for {uptime_str}. Three days. Is sleep optional now?"), # 3 days
|
||||
(345600, f"I've been awake for {uptime_str}. Four days... I'm running on fumes."), # 4 days
|
||||
(432000, f"I've been awake for {uptime_str}. Five days. Please send more coffee."), # 5 days
|
||||
(518400, f"I've been awake for {uptime_str}. Six days. I've forgotten what dreams are."), # 6 days
|
||||
(604800, f"I've been awake for {uptime_str}. One week. I'm turning into a zombie."), # 7 days
|
||||
(1209600, f"I've been awake for {uptime_str}. Two weeks. Are you sure I can't rest?"), # 14 days
|
||||
(2592000, f"I've been awake for {uptime_str}. A month! The nightmares never end."), # 30 days
|
||||
(7776000, f"I've been awake for {uptime_str}. Three months. I'm mostly coffee now."), # 90 days
|
||||
(15552000,f"I've been awake for {uptime_str}. Six months. This is insane..."), # 180 days
|
||||
(23328000,f"I've been awake for {uptime_str}. Nine months. I might be unstoppable."), # 270 days
|
||||
(31536000,f"I've been awake for {uptime_str}. A year?! I'm a legend of insomnia..."), # 365 days
|
||||
]
|
||||
|
||||
# We'll iterate from smallest to largest threshold
|
||||
for threshold, msg in time_ranges:
|
||||
if uptime_s < threshold:
|
||||
return msg
|
||||
|
||||
# If none matched, it means uptime_s >= 31536000 (1 year+)
|
||||
return f"I've been awake for {uptime_str}. Over a year awake... I'm beyond mortal limits!"
|
||||
|
||||
|
||||
def greet(target_display_name: str, platform_name: str) -> str:
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"discord_guilds": [896713616089309184],
|
||||
"twitch_channels": ["OokamiKunTV", "ookamipup"],
|
||||
"command_modules": ["cmd_discord", "cmd_twitch", "cmd_common"]
|
||||
"command_modules": ["cmd_discord", "cmd_twitch", "cmd_common"],
|
||||
"log_levels": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"]
|
||||
}
|
||||
|
|
@ -1,11 +1,17 @@
|
|||
# Example .env file.
|
||||
# Rename this file to ".env" and fill in your credentials accordingly.
|
||||
|
||||
# Discord bot credentials
|
||||
DISCORD_BOT_TOKEN=[Discord bot/access token]
|
||||
DISCORD_OWNER_ID=[Discord owner user ID]
|
||||
|
||||
# Twitch bot credentials
|
||||
TWITCH_CLIENT_ID=[Twitch bot client ID]
|
||||
TWITCH_CLIENT_SECRET=[Twitch bot client secret]
|
||||
TWITCH_BOT_TOKEN=[Twitch bot/access token]
|
||||
TWITCH_REFRESH_TOKEN=[Twitch bot refresh token]
|
||||
|
||||
# MariaDB Database credentials
|
||||
DB_HOST=[Database host IP address]
|
||||
DB_USER=[Database username]
|
||||
DB_PASSWORD=[Database user password]
|
|
@ -21,14 +21,14 @@ def format_uptime(seconds: float) -> str:
|
|||
# Build a short string, only listing the largest units
|
||||
# If you want more detail, you can keep going
|
||||
if years > 0:
|
||||
return f"{years} year(s), {months} month(s)"
|
||||
return [f"{years} year(s), {months} month(s)", seconds]
|
||||
elif months > 0:
|
||||
return f"{months} month(s), {days} day(s)"
|
||||
return [f"{months} month(s), {days} day(s)", seconds]
|
||||
elif days > 0:
|
||||
return f"{days} day(s), {hours} hour(s)"
|
||||
return [f"{days} day(s), {hours} hour(s)", seconds]
|
||||
elif hours > 0:
|
||||
return f"{hours} hour(s), {minutes} minute(s)"
|
||||
return [f"{hours} hour(s), {minutes} minute(s)", seconds]
|
||||
elif minutes > 0:
|
||||
return f"{minutes} minute(s)"
|
||||
return [f"{minutes} minute(s)", seconds]
|
||||
else:
|
||||
return f"{seconds} second(s)"
|
||||
return [f"{seconds} second(s)", seconds]
|
|
@ -0,0 +1,152 @@
|
|||
# OokamiPup V2
|
||||
|
||||
## A combined Discord and Twitch bot written in Python, leveraging:
|
||||
|
||||
- discord.py (PyPI version discord.py or a maintained fork)
|
||||
- TwitchIO
|
||||
- aiohttp
|
||||
- python-dotenv
|
||||
- and other libraries listed in your requirements.txt.
|
||||
|
||||
## About the bot
|
||||
|
||||
- Monitors specified Twitch channels for messages and commands.
|
||||
- Connects to a specified Discord server to respond to commands and events.
|
||||
- Uses a .env file for storing sensitive credentials.
|
||||
- Uses a config.json for channel configuration, logging settings, etc.
|
||||
|
||||
## Features
|
||||
|
||||
- Discord Bot
|
||||
- Responds to commands from any channel or restricted channels (depending on your setup).
|
||||
- Automatically loads commands from cmd_discord.py.
|
||||
- Twitch Bot
|
||||
- Joins multiple Twitch channels specified in your config.json.
|
||||
- Automatically loads commands from cmd_twitch.py.
|
||||
- Simple Logging
|
||||
- Logs to the console with optional levels (DEBUG, INFO, WARNING, etc.).
|
||||
- Token Refresh
|
||||
- Automatically attempts to refresh your Twitch OAuth token if it becomes invalid.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8+ (or a compatible Python 3 version—ensure asyncio.run is supported).
|
||||
- A Twitch Developer application (for your Client ID and Client Secret).
|
||||
- A Discord application/bot token.
|
||||
- A working pip or pipenv/poetry for installing dependencies.
|
||||
|
||||
## Installation
|
||||
|
||||
Clone this repository (or download the source code):
|
||||
|
||||
1. `git clone https://git.ookamikun.tv/kami/OokamiPupV2.git` *(Clone repository)*
|
||||
2. `cd OokamiPupV2` *(Navigate into the project folder)*
|
||||
3. `pip install -r requirements.txt` *(Install project dependancies)*
|
||||
|
||||
Create your .env file in the project root (same folder as `bots.py`). It should look like:
|
||||
|
||||
```python
|
||||
DISCORD_BOT_TOKEN=YourDiscordBotToken
|
||||
TWITCH_CLIENT_ID=YourTwitchAppClientID
|
||||
TWITCH_CLIENT_SECRET=YourTwitchAppClientSecret
|
||||
TWITCH_BOT_TOKEN=YourTwitchOAuthToken
|
||||
TWITCH_REFRESH_TOKEN=YourTwitchRefreshToken
|
||||
```
|
||||
|
||||
Note: Your Twitch OAuth/Refresh tokens typically come from generating them through the Twitch Developer Console.
|
||||
|
||||
Set up your config.json with your preferred channels, logging levels, etc. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"twitch_channels": ["mychannel", "anotherchannel"],
|
||||
"log_levels": ["INFO", "WARNING", "ERROR", "CRITICAL", "FATAL"]
|
||||
}
|
||||
```
|
||||
|
||||
twitch_channels is a list of channel names for your bot to join on Twitch.
|
||||
log_levels controls what messages get logged to console (The lowest "DEBUG" level can be added if needed).
|
||||
|
||||
## Directory Overview
|
||||
|
||||
A quick rundown of the important files:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── bots.py # Main entry point: initializes/starts both Discord and Twitch bots
|
||||
├── bot_discord.py # Discord bot implementation (using discord.py)
|
||||
├── bot_twitch.py # Twitch bot implementation (using TwitchIO)
|
||||
├── cmd_common/ # Platform-independant command logic
|
||||
│ └── common_commands.py # Common low-level commands file
|
||||
├── cmd_discord.py # Command definitions for Discord
|
||||
├── cmd_twitch.py # Command definitions for Twitch
|
||||
├── config.json # Bot configuration (channels, logging levels, etc.)
|
||||
├── .env # Environment variables for tokens (DO NOT commit this)
|
||||
├── requirements.txt # Python dependencies
|
||||
└── README.md # You are here
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Edit your environment variables in the .env file (tokens, client IDs, secrets, etc.).
|
||||
2. Edit your config.json to list the Twitch channels and your logging preferences.
|
||||
3. Run the bot: `python bots.py`
|
||||
4. Check the console output:
|
||||
- You should see logs indicating that the Discord bot is online (Discord bot is online as ...).
|
||||
- You should also see logs about the Twitch bot joining channels (Twitch bot connected) — assuming everything is configured correctly.
|
||||
|
||||
## Testing Commands
|
||||
|
||||
**Discord:**
|
||||
|
||||
Open your Discord server, type your command prefix (e.g., !) + the command name (e.g., !ping).
|
||||
|
||||
**Twitch:**
|
||||
|
||||
In one of the channels specified in twitch_channels, type !ping (or whichever commands you have set up in cmd_twitch.py).
|
||||
|
||||
### If your commands do not respond
|
||||
|
||||
- Confirm the bot has correct roles/permissions in Discord (especially if it needs to read/send messages in specific channels).
|
||||
- Confirm your Twitch OAuth token is valid.
|
||||
- Check for logs or errors in the console.
|
||||
|
||||
## Adding or Editing Commands
|
||||
|
||||
**General Commands:**
|
||||
|
||||
low-level commands can be made in `cmd_common/common_commands.py`, alternatively separated into separate files in that folder.
|
||||
|
||||
These are command logic meant to support both platforms, eg. the `!ping` command.
|
||||
|
||||
**Discord:**
|
||||
|
||||
- Open cmd_discord.py and define new commands using the standard @bot.command() decorators from discord.ext.commands.
|
||||
- Make sure cmd_discord.setup(bot) registers them with your DiscordBot.
|
||||
|
||||
**Twitch:**
|
||||
|
||||
- Open cmd_twitch.py and define new commands using the @bot.command() decorators from twitchio.ext.commands.
|
||||
- Ensure cmd_twitch.setup(bot) properly registers them with TwitchBot.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No logs from the Twitch bot
|
||||
- Ensure that you’re calling await bot.start() (not bot.run() and await bot.start() together).
|
||||
- If you only see the Discord bot logs, verify that your .env variables are loaded and valid (TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET, TWITCH_BOT_TOKEN, TWITCH_REFRESH_TOKEN).
|
||||
- Confirm that you have internet connectivity and that the Twitch IRC service isn’t blocked by a firewall.
|
||||
|
||||
- Token refresh issues
|
||||
- If you see repeated warnings about “Invalid or unauthorized Access Token,” double-check your TWITCH_REFRESH_TOKEN and TWITCH_CLIENT_SECRET are correct.
|
||||
- The bot attempts to refresh automatically; if it still fails, manually re-generate tokens from the Twitch Developer Console.
|
||||
|
||||
- "Cannot write to closing transport"
|
||||
- Make sure you do not call connect() manually before start() in TwitchIO.
|
||||
|
||||
## Contributing
|
||||
|
||||
- Fork the repository
|
||||
- Create a new branch (git checkout -b feature/myfeature)
|
||||
- Make changes and commit (git commit -am 'Add new feature')
|
||||
- Push to the branch (git push origin feature/myfeature)
|
||||
- Create a new Pull Request in Git
|
|
@ -1,4 +1,13 @@
|
|||
discord.py
|
||||
twitchio
|
||||
python-dotenv
|
||||
PyMySQL
|
||||
# Core Dependencies
|
||||
python-dotenv==1.0.0 # Loads environment variables from .env
|
||||
requests==2.31.0 # HTTP requests for Twitch API (token refresh, etc.)
|
||||
|
||||
# Discord Bot Dependencies
|
||||
discord.py==2.3.2 # Main Discord bot library (async)
|
||||
PyNaCl==1.5.0 # Enables voice support (optional, required if using voice features)
|
||||
|
||||
# Twitch Bot Dependencies
|
||||
twitchio==2.7.1 # Twitch chat bot library (async)
|
||||
|
||||
# Utility & Logging
|
||||
aiohttp==3.9.1 # Async HTTP requests (dependency for discord.py & twitchio)
|
||||
|
|
Loading…
Reference in New Issue