updated tech stack for newer versions of python, removed pyaudio. refactored configuration into .env file and integration inside logic, removed repeat config reads

This commit is contained in:
2025-11-29 18:21:51 +00:00
parent fecb4947e1
commit b3d618b337
8 changed files with 490 additions and 142 deletions

67
.env_example Normal file
View File

@@ -0,0 +1,67 @@
# Groovy-Zilean Configuration
# Copy this file to .env and fill in your values
# NEVER commit .env to git!
# ===================================
# Environment Selection
# ===================================
# Set to "dev" for development bot, "live" for production
ENVIRONMENT=dev
# ===================================
# Discord Bot Tokens
# ===================================
DISCORD_TOKEN_DEV=
DISCORD_TOKEN_LIVE=
# Bot settings
DISCORD_PREFIX=
# ===================================
# Spotify Integration
# ===================================
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
# ===================================
# Database
# ===================================
# For now (SQLite)
DB_PATH=./data/music.db
# For future (PostgreSQL)
# DB_HOST=localhost
# DB_PORT=5432
# DB_NAME=groovy_zilean
# DB_USER=groovy
# DB_PASSWORD=your_db_password
# ===================================
# Bot Status/Presence
# ===================================
# Types: playing, listening, watching, streaming, competing
STATUS_TYPE=listening
STATUS_TEXT==help | /help
# STATUS_URL= # Only needed for streaming type
# ===================================
# Color Scheme (hex colors)
# ===================================
COLOR_PRIMARY=#7289DA
COLOR_SUCCESS=#43B581
COLOR_ERROR=#F04747
COLOR_WARNING=#FAA61A
# ===================================
# Web Dashboard (Future)
# ===================================
# DISCORD_CLIENT_ID=your_oauth_client_id
# DISCORD_CLIENT_SECRET=your_oauth_secret
# DISCORD_REDIRECT_URI=http://localhost:8000/callback
# WEB_SECRET_KEY=random_secret_for_sessions
# ===================================
# Logging
# ===================================
LOG_LEVEL=INFO
# Options: DEBUG, INFO, WARNING, ERROR, CRITICAL

2
.gitignore vendored
View File

@@ -160,4 +160,4 @@ cython_debug/
#.idea/ #.idea/
# My stuff # My stuff
data/ data/*.db

201
SETUP.md Normal file
View File

@@ -0,0 +1,201 @@
# Groovy-Zilean Setup Guide
Quick start guide for getting groovy-zilean running locally or in production.
---
## Prerequisites
- **Python 3.11+** (3.9 deprecated by yt-dlp)
- **FFmpeg** installed on your system
- Discord Bot Token ([Discord Developer Portal](https://discord.com/developers/applications))
- Spotify API Credentials ([Spotify Developer Dashboard](https://developer.spotify.com/dashboard))
---
## 1. Clone & Setup Environment
```bash
# Clone the repository
git clone <your-repo-url>
cd groovy-zilean
# Create virtual environment (keeps dependencies isolated)
python3.12 -m venv venv
# Activate virtual environment
source venv/bin/activate # Linux/Mac
# OR
venv\Scripts\activate # Windows
# Install dependencies
pip install --upgrade pip
pip install -r requirements.txt
```
---
## 2. Configuration
### Create `.env` file
```bash
# Copy the example file
cp .env.example .env
# Edit with your favorite editor
nano .env
# OR
vim .env
# OR
code .env # VS Code
```
### Fill in Required Values
At minimum, you need:
```env
# Choose environment: "dev" or "live"
ENVIRONMENT=dev
# Discord bot tokens (get from Discord Developer Portal)
DISCORD_TOKEN_DEV=your_dev_bot_token_here
DISCORD_TOKEN_LIVE=your_live_bot_token_here
# Spotify credentials (get from Spotify Developer Dashboard)
SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_secret
```
**Optional but recommended:**
```env
# Bot settings
DISCORD_PREFIX==
STATUS_TYPE=listening
STATUS_TEXT=Zilean's Theme
# Colors (hex format)
COLOR_PRIMARY=#7289DA
COLOR_SUCCESS=#43B581
COLOR_ERROR=#F04747
COLOR_WARNING=#FAA61A
```
---
## 3. Create Data Directory
```bash
# Create directory for database
mkdir -p data
# The database file (music.db) will be created automatically on first run
```
---
## 4. Run the Bot
### Development Mode
```bash
# Make sure .env has ENVIRONMENT=dev
source venv/bin/activate # If not already activated
python main.py
```
### Production Mode
```bash
# Change .env to ENVIRONMENT=live
source venv/bin/activate
python main.py
```
---
## 5. Switching Between Dev and Live
**Super easy!** Just change one line in `.env`:
```bash
# For development bot
ENVIRONMENT=dev
# For production bot
ENVIRONMENT=live
```
The bot will automatically use the correct token!
---
## Troubleshooting
### "Configuration Error: DISCORD_TOKEN_DEV not found"
- Make sure you copied `.env.example` to `.env`
- Check that `.env` has the token values filled in
- Token should NOT have quotes around it
### "No module named 'dotenv'"
```bash
pip install python-dotenv
```
### "FFmpeg not found"
```bash
# Debian/Ubuntu
sudo apt install ffmpeg
# macOS
brew install ffmpeg
# Arch Linux
sudo pacman -S ffmpeg
```
### Python version issues
yt-dlp requires Python 3.10+. Check your version:
```bash
python --version
```
If too old, install newer Python and recreate venv:
```bash
python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
---
## Project Structure
```
groovy-zilean/
├── main.py # Entry point (run this!)
├── bot.py # Bot class definition
├── config.py # Configuration management
├── .env # YOUR secrets (never commit!)
├── .env.example # Template (safe to commit)
├── requirements.txt # Python dependencies
├── cogs/
│ └── music/ # Music functionality
│ ├── main.py # Commands
│ ├── queue.py # Queue management
│ ├── util.py # Utilities
│ └── translate.py # URL/search handling
└── data/
└── music.db # SQLite database (auto-created)
```
---
## Next Steps
- Check out `PRODUCTION_ROADMAP.md` for the full development plan
- See `README.md` for feature list and usage
- Join your test server and try commands!
**Happy coding!** 🎵⏱️

View File

@@ -12,28 +12,7 @@ from cogs.music.help import music_help
import spotipy import spotipy
from spotipy.oauth2 import SpotifyClientCredentials from spotipy.oauth2 import SpotifyClientCredentials
import config # Use centralized config
# Fix this pls
import json
#from .. import config
# Read data from JSON file in ./data/config.json
def read_data():
with open("./data/config.json", "r") as file:
return json.load(file)
raise Exception("Could not load config data")
def get_spotify_creds():
data = read_data()
data = data.get("spotify")
SCID = data.get("SCID")
secret = data.get("SECRET")
return SCID, secret
@@ -51,10 +30,13 @@ class music(commands.Cog):
help_command.cog = self help_command.cog = self
self.help_command = help_command self.help_command = help_command
SCID, secret = get_spotify_creds() # Get Spotify credentials from centralized config
spotify_id, spotify_secret = config.get_spotify_creds()
# Authentication - without user # Authentication - without user
client_credentials_manager = SpotifyClientCredentials(client_id=SCID, client_credentials_manager = SpotifyClientCredentials(
client_secret=secret) client_id=spotify_id,
client_secret=spotify_secret
)
self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

View File

@@ -7,8 +7,10 @@ import discord
import asyncio import asyncio
from .translate import search_song from .translate import search_song
import config
db_path = "./data/music.db" # Get database path from centralized config
db_path = config.get_db_path()
# Base FFmpeg options (will be modified by effects) # Base FFmpeg options (will be modified by effects)
BASE_FFMPEG_OPTS = { BASE_FFMPEG_OPTS = {

255
config.py
View File

@@ -1,115 +1,198 @@
# config.py # config.py
# This file should parse all configurations within the bot # Modern configuration management using environment variables
import os
import discord import discord
from discord import Color from discord import Color
import json from dotenv import load_dotenv
from typing import Optional
# Read data from JSON file in ./data/config.json # Load environment variables from .env file
def read_data(): load_dotenv()
with open("./data/config.json", "r") as file:
return json.load(file)
raise Exception("Could not load config data") # ===================================
# Environment Detection
# ===================================
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev").lower()
IS_PRODUCTION = ENVIRONMENT == "live"
IS_DEVELOPMENT = ENVIRONMENT == "dev"
# ===================================
# Discord Configuration
# ===================================
def get_discord_token() -> str:
"""Get the appropriate Discord token based on environment"""
if IS_PRODUCTION:
token = os.getenv("DISCORD_TOKEN_LIVE")
if not token:
raise ValueError("DISCORD_TOKEN_LIVE not found in environment!")
return token
else:
token = os.getenv("DISCORD_TOKEN_DEV")
if not token:
raise ValueError("DISCORD_TOKEN_DEV not found in environment!")
return token
def get_spotify_creds(): def get_prefix() -> str:
data = read_data() """Get command prefix (default: =)"""
data = data.get("spotify") return os.getenv("DISCORD_PREFIX", "=")
SCID = data.get("SCID")
secret = data.get("SECRET")
return SCID, secret
# Reading prefix # ===================================
def get_prefix(): # Spotify Configuration
data = read_data() # ===================================
def get_spotify_creds() -> tuple[str, str]:
"""Get Spotify API credentials"""
client_id = os.getenv("SPOTIFY_CLIENT_ID")
client_secret = os.getenv("SPOTIFY_CLIENT_SECRET")
prefix = data.get('prefix') if not client_id or not client_secret:
if prefix: raise ValueError("Spotify credentials not found in environment!")
return prefix
raise Exception("Missing config data: prefix") return client_id, client_secret
# Fetch the bot secret token # ===================================
def get_login(bot): # Database Configuration
data = read_data() # ===================================
if data is False or data.get(f"{bot}bot") is False: def get_db_path() -> str:
raise Exception(f"Missing config data: {bot}bot") """Get SQLite database path"""
return os.getenv("DB_PATH", "./data/music.db")
data = data.get(f"{bot}bot")
return data.get("secret")
# Read the status and text data # Future PostgreSQL config
def get_status(): def get_postgres_url() -> Optional[str]:
data = read_data() """Get PostgreSQL connection URL (for future migration)"""
host = os.getenv("DB_HOST")
port = os.getenv("DB_PORT", "5432")
name = os.getenv("DB_NAME")
user = os.getenv("DB_USER")
password = os.getenv("DB_PASSWORD")
if data is False or data.get('status') is False: if all([host, name, user, password]):
raise Exception("Missing config data: status") return f"postgresql://{user}:{password}@{host}:{port}/{name}"
return None
# Find type
data = data.get('status')
return translate_status(
data.get('type'),
data.get('text'),
data.get('link')
)
# Get colors from colorscheme
def get_color(color):
data = read_data()
if data is False or data.get('status') is False:
raise Exception("Missing config data: color")
# Grab color
string_value = data.get("colorscheme").get(color)
hex_value = Color.from_str(string_value)
return hex_value
# Taking JSON variables and converting them into a presence # ===================================
# Use None url incase not provided # Bot Status/Presence
def translate_status(status_type, status_text, status_url=None): # ===================================
if status_type == "playing": def get_status() -> discord.Activity:
"""Get bot status/presence"""
status_type = os.getenv("STATUS_TYPE", "listening").lower()
status_text = os.getenv("STATUS_TEXT", "Zilean's Theme")
status_url = os.getenv("STATUS_URL")
return translate_status(status_type, status_text, status_url)
def translate_status(
status_type: str,
status_text: str,
status_url: Optional[str] = None
) -> discord.Activity:
"""Convert status type string to Discord Activity"""
status_map = {
"playing": discord.ActivityType.playing,
"streaming": discord.ActivityType.streaming,
"listening": discord.ActivityType.listening,
"watching": discord.ActivityType.watching,
"competing": discord.ActivityType.competing,
}
activity_type = status_map.get(status_type)
if not activity_type:
raise ValueError(f"Invalid status type: {status_type}")
# Streaming requires URL
if status_type == "streaming":
if not status_url:
raise ValueError("Streaming status requires STATUS_URL")
return discord.Activity( return discord.Activity(
type=discord.ActivityType.playing, type=activity_type,
name=status_text
)
elif status_type == "streaming":
return discord.Activity(
type=discord.ActivityType.streaming,
name=status_text, name=status_text,
url=status_url url=status_url
) )
elif status_type == "listening": return discord.Activity(type=activity_type, name=status_text)
return discord.Activity(
type=discord.ActivityType.listening,
name=status_text
)
elif status_type == "watching": # ===================================
return discord.Activity( # Color Scheme
type=discord.ActivityType.watching, # ===================================
name=status_text def get_color(color_name: str) -> Color:
) """Get color from environment (hex format)"""
color_map = {
"primary": os.getenv("COLOR_PRIMARY", "#7289DA"),
"success": os.getenv("COLOR_SUCCESS", "#43B581"),
"error": os.getenv("COLOR_ERROR", "#F04747"),
"warning": os.getenv("COLOR_WARNING", "#FAA61A"),
}
elif status_type == "competing": hex_value = color_map.get(color_name.lower())
return discord.Activity( if not hex_value:
type=discord.ActivityType.competing, # Default to Discord blurple
name=status_text hex_value = "#7289DA"
)
#TODO return Color.from_str(hex_value)
# Implement custom status type
else:
raise Exception(f"Invalid status type: {status_type}") # ===================================
# Logging Configuration
# ===================================
def get_log_level() -> str:
"""Get logging level from environment"""
return os.getenv("LOG_LEVEL", "INFO").upper()
# ===================================
# Legacy Support (for backward compatibility)
# ===================================
def get_login(bot: str) -> str:
"""Legacy function - maps to new get_discord_token()"""
# Ignore the 'bot' parameter, use ENVIRONMENT instead
return get_discord_token()
# ===================================
# Validation
# ===================================
def validate_config():
"""Validate that all required config is present"""
errors = []
# Check Discord token
try:
get_discord_token()
except ValueError as e:
errors.append(str(e))
# Check Spotify creds
try:
get_spotify_creds()
except ValueError as e:
errors.append(str(e))
if errors:
error_msg = "\n".join(errors)
raise ValueError(f"Configuration errors:\n{error_msg}")
print(f"✅ Configuration validated (Environment: {ENVIRONMENT})")
# ===================================
# Startup Info
# ===================================
def print_config_info():
"""Print configuration summary (without secrets!)"""
print("=" * 50)
print("🎵 Groovy-Zilean Configuration")
print("=" * 50)
print(f"Environment: {ENVIRONMENT.upper()}")
print(f"Prefix: {get_prefix()}")
print(f"Database: {get_db_path()}")
print(f"Log Level: {get_log_level()}")
print(f"Spotify: {'Configured ✅' if os.getenv('SPOTIFY_CLIENT_ID') else 'Not configured ❌'}")
print("=" * 50)

13
main.py
View File

@@ -3,6 +3,16 @@ from bot import Groovy
import config import config
import help import help
# Validate configuration before starting
try:
config.validate_config()
config.print_config_info()
except ValueError as e:
print(f"❌ Configuration Error:\n{e}")
print("\n💡 Tip: Copy .env.example to .env and fill in your values")
exit(1)
# Initialize bot with validated config
client = Groovy(command_prefix=config.get_prefix(), intents=discord.Intents.all()) client = Groovy(command_prefix=config.get_prefix(), intents=discord.Intents.all())
@client.event @client.event
@@ -25,4 +35,5 @@ async def on_voice_state_update(member, before, after):
except Exception as e: except Exception as e:
print(f"Error auto-disconnecting: {e}") print(f"Error auto-disconnecting: {e}")
client.run(config.get_login("live")) # Run bot with environment-appropriate token
client.run(config.get_discord_token())

View File

@@ -1,12 +1,14 @@
# Core bot framework # Core bot framework
discord.py==2.6.4 discord.py>=2.6.4
aiohttp==3.8.4 aiohttp>=3.9.0
PyNaCl==1.5.0 PyNaCl>=1.5.0
spotipy==2.23.0 spotipy>=2.23.0
# Configuration management
python-dotenv>=1.0.0
# YouTube extractor # YouTube extractor
yt-dlp>=2025.10.14 yt-dlp>=2025.10.14
# System dependencies # Audio metadata (if needed by yt-dlp)
PyAudio==0.2.13 mutagen>=1.47.0
mutagen==1.46.0