removed all direct database access. readable async functions. docstrings. organisation
This commit is contained in:
@@ -1,16 +1,14 @@
|
|||||||
from http import server
|
"""
|
||||||
import sqlite3
|
Queue management for Groovy-Zilean music bot
|
||||||
import random
|
Now using centralized database manager for cleaner code
|
||||||
import time
|
"""
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
from .translate import search_song
|
from .translate import search_song
|
||||||
import config
|
from .db_manager import 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 = {
|
||||||
@@ -105,342 +103,224 @@ def get_effect_options(effect_name):
|
|||||||
return effects.get(effect_name, effects['none'])
|
return effects.get(effect_name, effects['none'])
|
||||||
|
|
||||||
|
|
||||||
# Creates the tables if they don't exist
|
# ===================================
|
||||||
|
# Initialization
|
||||||
|
# ===================================
|
||||||
|
|
||||||
def initialize_tables():
|
def initialize_tables():
|
||||||
# Connect to the database
|
"""Initialize database tables"""
|
||||||
conn = sqlite3.connect(db_path)
|
db.initialize_tables()
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Create servers table if it doesn't exist
|
|
||||||
cursor.execute('''CREATE TABLE IF NOT EXISTS servers (
|
|
||||||
server_id TEXT PRIMARY KEY,
|
|
||||||
is_playing INTEGER DEFAULT 0,
|
|
||||||
song_name TEXT,
|
|
||||||
song_url TEXT,
|
|
||||||
song_thumbnail TEXT,
|
|
||||||
loop_mode TEXT DEFAULT 'off',
|
|
||||||
volume INTEGER DEFAULT 100,
|
|
||||||
effect TEXT DEFAULT 'none',
|
|
||||||
song_start_time REAL DEFAULT 0,
|
|
||||||
song_duration INTEGER DEFAULT 0
|
|
||||||
);''')
|
|
||||||
|
|
||||||
# Set all to not playing
|
# ===================================
|
||||||
cursor.execute("UPDATE servers SET is_playing = 0;")
|
# Queue Management
|
||||||
|
# ===================================
|
||||||
|
|
||||||
# Add new columns if they don't exist (for existing databases)
|
async def add_song(server_id, details, queued_by, position=None):
|
||||||
# Migrations for existing databases
|
"""
|
||||||
columns = [
|
Add a song to the queue
|
||||||
("loop_mode", "TEXT DEFAULT 'off'"),
|
|
||||||
("volume", "INTEGER DEFAULT 100"),
|
|
||||||
("effect", "TEXT DEFAULT 'none'"),
|
|
||||||
("song_start_time", "REAL DEFAULT 0"),
|
|
||||||
("song_duration", "INTEGER DEFAULT 0"),
|
|
||||||
("song_thumbnail", "TEXT DEFAULT ''"),
|
|
||||||
("song_url", "TEXT DEFAULT ''") # NEW
|
|
||||||
]
|
|
||||||
|
|
||||||
for col_name, col_type in columns:
|
Args:
|
||||||
try:
|
server_id: Discord server ID
|
||||||
cursor.execute(f"ALTER TABLE servers ADD COLUMN {col_name} {col_type};")
|
details: Dictionary with song info (url, title, thumbnail, duration) or string
|
||||||
except sqlite3.OperationalError:
|
queued_by: Username who queued the song
|
||||||
pass
|
position: Optional position in queue (None = end of queue)
|
||||||
|
|
||||||
cursor.execute('''CREATE TABLE IF NOT EXISTS songs (
|
|
||||||
server_id TEXT NOT NULL,
|
|
||||||
song_link TEXT,
|
|
||||||
queued_by TEXT,
|
|
||||||
position INTEGER NOT NULL,
|
|
||||||
title TEXT,
|
|
||||||
thumbnail TEXT,
|
|
||||||
duration INTEGER,
|
|
||||||
PRIMARY KEY (position),
|
|
||||||
FOREIGN KEY (server_id) REFERENCES servers(server_id)
|
|
||||||
);''')
|
|
||||||
|
|
||||||
cursor.execute("DELETE FROM songs;")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
# Queue a song in the db
|
|
||||||
async def add_song(server_id, details, queued_by):
|
|
||||||
# Connect to db
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
|
|
||||||
max_order_num = await get_max(server_id, cursor) + 1
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Position in queue
|
||||||
|
"""
|
||||||
if isinstance(details, str):
|
if isinstance(details, str):
|
||||||
# Fallback for raw strings
|
# Fallback for raw strings (legacy support)
|
||||||
cursor.execute("""INSERT INTO songs VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
pos = db.add_song(
|
||||||
(server_id, "Not grabbed", queued_by, max_order_num, details, "Unknown", 0))
|
server_id=str(server_id),
|
||||||
|
song_link="Not grabbed",
|
||||||
|
queued_by=queued_by,
|
||||||
|
title=details,
|
||||||
|
thumbnail="Unknown",
|
||||||
|
duration=0,
|
||||||
|
position=position
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Save exact duration and thumbnail from the start
|
# Standard dictionary format
|
||||||
cursor.execute("""INSERT INTO songs VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
pos = db.add_song(
|
||||||
(server_id, details['url'], queued_by, max_order_num, details['title'], details['thumbnail'], details['duration']))
|
server_id=str(server_id),
|
||||||
|
song_link=details['url'],
|
||||||
|
queued_by=queued_by,
|
||||||
|
title=details['title'],
|
||||||
|
thumbnail=details.get('thumbnail', ''),
|
||||||
|
duration=details.get('duration', 0),
|
||||||
|
position=position
|
||||||
|
)
|
||||||
|
|
||||||
conn.commit()
|
return pos
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return max_order_num
|
|
||||||
|
|
||||||
|
|
||||||
# Pop song from server (respects loop mode)
|
|
||||||
async def pop(server_id, ignore=False, skip_mode=False):
|
async def pop(server_id, ignore=False, skip_mode=False):
|
||||||
"""
|
"""
|
||||||
Pop next song from queue
|
Pop next song from queue
|
||||||
ignore: Skip the song without returning URL
|
|
||||||
skip_mode: True when called from skip command (affects loop song behavior)
|
Args:
|
||||||
|
server_id: Discord server ID
|
||||||
|
ignore: Skip the song without returning URL
|
||||||
|
skip_mode: True when called from skip command (affects loop song behavior)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Song URL or None
|
||||||
"""
|
"""
|
||||||
# Connect to db
|
result = db.get_next_song(str(server_id))
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# JUST INCASE!
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
|
|
||||||
# Fetch info: link(1), title(4), thumbnail(5), duration(6)
|
|
||||||
cursor.execute('''SELECT * FROM songs WHERE server_id = ? ORDER BY position LIMIT 1;''', (server_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
elif ignore:
|
|
||||||
await mark_song_as_finished(server_id, result[3])
|
|
||||||
return None
|
|
||||||
elif result[1] == "Not grabbed":
|
|
||||||
# Lazy load logic
|
|
||||||
song_list = await search_song(result[4])
|
|
||||||
if not song_list:
|
|
||||||
return None
|
|
||||||
song = song_list[0]
|
|
||||||
|
|
||||||
await set_current_song(server_id, song['title'], song.get('thumbnail', ''), song.get('duration', 0))
|
# result format: (server_id, song_link, queued_by, position, title, thumbnail, duration)
|
||||||
|
server_id_str, song_link, queued_by, position, title, thumbnail, duration = result
|
||||||
|
|
||||||
|
if ignore:
|
||||||
|
db.remove_song(str(server_id), position)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Handle lazy-loaded songs (not yet fetched from YouTube)
|
||||||
|
if song_link == "Not grabbed":
|
||||||
|
song_list = await search_song(title)
|
||||||
|
if not song_list:
|
||||||
|
db.remove_song(str(server_id), position)
|
||||||
|
return None
|
||||||
|
|
||||||
|
song = song_list[0]
|
||||||
|
await set_current_song(
|
||||||
|
server_id,
|
||||||
|
song['title'],
|
||||||
|
song['url'],
|
||||||
|
song.get('thumbnail', ''),
|
||||||
|
song.get('duration', 0)
|
||||||
|
)
|
||||||
|
|
||||||
# Check loop mode before removing
|
# Check loop mode before removing
|
||||||
loop_mode = await get_loop_mode(server_id)
|
loop_mode = await get_loop_mode(server_id)
|
||||||
if loop_mode != 'song': # Only remove if not looping song
|
if loop_mode != 'song':
|
||||||
await mark_song_as_finished(server_id, result[3])
|
db.remove_song(str(server_id), position)
|
||||||
|
|
||||||
return song['url']
|
return song['url']
|
||||||
|
|
||||||
# Pre-grabbed logic (Standard)
|
# Standard pre-fetched song
|
||||||
# result[1] is url, result[5] is thumbnail, result[6] is duration
|
await set_current_song(server_id, title, song_link, thumbnail, duration)
|
||||||
await set_current_song(server_id, result[4], result[1], result[5], result[6])
|
|
||||||
|
|
||||||
# Check loop mode before removing
|
# Check loop mode before removing
|
||||||
loop_mode = await get_loop_mode(server_id)
|
loop_mode = await get_loop_mode(server_id)
|
||||||
if loop_mode != 'song': # Only remove if not looping song
|
if loop_mode != 'song':
|
||||||
await mark_song_as_finished(server_id, result[3])
|
db.remove_song(str(server_id), position)
|
||||||
|
|
||||||
return result[1]
|
return song_link
|
||||||
|
|
||||||
# Add server to db if first time queuing
|
|
||||||
async def add_server(server_id, cursor, conn):
|
|
||||||
cursor.execute('SELECT COUNT(*) FROM servers WHERE server_id = ?', (server_id,))
|
|
||||||
if cursor.fetchone()[0] == 0:
|
|
||||||
cursor.execute('''INSERT INTO servers (server_id, loop_mode, volume, effect, song_thumbnail, song_url)
|
|
||||||
VALUES (?, 'off', 100, 'none', '', '')''', (server_id,))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
|
|
||||||
# set song as played and update indexes
|
|
||||||
async def mark_song_as_finished(server_id, order_num):
|
|
||||||
# Connect to the database
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Update the song as finished
|
|
||||||
cursor.execute('''DELETE FROM songs
|
|
||||||
WHERE server_id = ? AND position = ?''',
|
|
||||||
(server_id, order_num))
|
|
||||||
|
|
||||||
# Close connection
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
# set the current playing song of the server
|
|
||||||
async def set_current_song(server_id, title, url, thumbnail="", duration=0):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
# Ensure duration is an integer
|
|
||||||
try:
|
|
||||||
duration = int(duration)
|
|
||||||
except:
|
|
||||||
duration = 0
|
|
||||||
|
|
||||||
cursor.execute(''' UPDATE servers
|
|
||||||
SET song_name = ?, song_url = ?, song_thumbnail = ?, song_start_time = ?, song_duration = ?
|
|
||||||
WHERE server_id = ?''',
|
|
||||||
(title, url, thumbnail, start_time, duration, server_id))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
# Returns dictionary with title and thumbnail
|
|
||||||
async def get_current_song(server_id):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
cursor.execute(''' SELECT song_name, song_thumbnail, song_url FROM servers WHERE server_id = ? LIMIT 1;''', (server_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
return {'title': result[0], 'thumbnail': result[1], 'url': result[2]}
|
|
||||||
return {'title': "Nothing", 'thumbnail': None, 'url': ''}
|
|
||||||
|
|
||||||
async def get_current_progress(server_id):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute('''SELECT song_start_time, song_duration, is_playing FROM servers WHERE server_id = ? LIMIT 1;''', (server_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
conn.close() # Close quickly
|
|
||||||
|
|
||||||
if not result or result[2] == 0:
|
|
||||||
return 0, 0, 0.0
|
|
||||||
|
|
||||||
start_time, duration, _ = result
|
|
||||||
|
|
||||||
if duration is None or duration == 0:
|
|
||||||
return 0, 0, 0.0
|
|
||||||
|
|
||||||
elapsed = int(time.time() - start_time)
|
|
||||||
elapsed = min(elapsed, duration)
|
|
||||||
percentage = (elapsed / duration) * 100 if duration > 0 else 0
|
|
||||||
|
|
||||||
return elapsed, duration, percentage
|
|
||||||
|
|
||||||
async def get_max(server_id, cursor):
|
|
||||||
cursor.execute("SELECT MAX(position) FROM songs WHERE server_id = ?", (server_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
return result[0] if result[0] is not None else -1
|
|
||||||
|
|
||||||
async def update_server(server_id, playing):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
val = 1 if playing else 0
|
|
||||||
cursor.execute("UPDATE servers SET is_playing = ? WHERE server_id = ?", (val, server_id))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
async def is_server_playing(server_id):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("SELECT is_playing FROM servers WHERE server_id = ?", (server_id,))
|
|
||||||
res = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
return True if res[0] == 1 else False
|
|
||||||
|
|
||||||
async def clear(server_id):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
await update_server(server_id, False)
|
|
||||||
cursor.execute("DELETE FROM songs WHERE server_id = ?", (server_id,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
async def grab_songs(server_id):
|
async def grab_songs(server_id):
|
||||||
conn = sqlite3.connect(db_path)
|
"""
|
||||||
cursor = conn.cursor()
|
Get current queue
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("SELECT title, duration, queued_by FROM songs WHERE server_id = ? ORDER BY position LIMIT 10", (server_id,))
|
|
||||||
songs = cursor.fetchall()
|
|
||||||
max_pos = await get_max(server_id, cursor)
|
|
||||||
conn.close()
|
|
||||||
return max_pos, songs
|
|
||||||
|
|
||||||
# --- Effects/Loop/Shuffle/Volume (Simplified Paste) ---
|
Returns:
|
||||||
async def get_loop_mode(server_id):
|
Tuple of (max_position, list_of_songs)
|
||||||
conn = sqlite3.connect(db_path)
|
"""
|
||||||
cursor = conn.cursor()
|
return db.get_queue(str(server_id), limit=10)
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("SELECT loop_mode FROM servers WHERE server_id = ?", (server_id,))
|
|
||||||
res = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
return res[0] if res else 'off'
|
|
||||||
|
|
||||||
async def set_loop_mode(server_id, mode):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("UPDATE servers SET loop_mode = ? WHERE server_id = ?", (mode, server_id))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
async def get_volume(server_id):
|
async def clear(server_id):
|
||||||
conn = sqlite3.connect(db_path)
|
"""Clear the queue for a server"""
|
||||||
cursor = conn.cursor()
|
db.clear_queue(str(server_id))
|
||||||
await add_server(server_id, cursor, conn)
|
await update_server(server_id, False)
|
||||||
cursor.execute("SELECT volume FROM servers WHERE server_id = ?", (server_id,))
|
|
||||||
res = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
return res[0] if res else 100
|
|
||||||
|
|
||||||
async def set_volume(server_id, vol):
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("UPDATE servers SET volume = ? WHERE server_id = ?", (vol, server_id))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
return vol
|
|
||||||
|
|
||||||
async def shuffle_queue(server_id):
|
async def shuffle_queue(server_id):
|
||||||
conn = sqlite3.connect(db_path)
|
"""Shuffle the queue randomly"""
|
||||||
cursor = conn.cursor()
|
return db.shuffle_queue(str(server_id))
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("SELECT position, song_link, queued_by, title, thumbnail, duration FROM songs WHERE server_id = ? ORDER BY position", (server_id,))
|
|
||||||
songs = cursor.fetchall()
|
# ===================================
|
||||||
if len(songs) <= 1:
|
# Server State Management
|
||||||
conn.close()
|
# ===================================
|
||||||
return False
|
|
||||||
random.shuffle(songs)
|
async def update_server(server_id, playing):
|
||||||
cursor.execute("DELETE FROM songs WHERE server_id = ?", (server_id,))
|
"""Update server playing status"""
|
||||||
for i, s in enumerate(songs):
|
db.set_server_playing(str(server_id), playing)
|
||||||
cursor.execute("INSERT INTO songs VALUES (?, ?, ?, ?, ?, ?, ?)", (server_id, s[1], s[2], i, s[3], s[4], s[5]))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
async def is_server_playing(server_id):
|
||||||
return True
|
"""Check if server is currently playing"""
|
||||||
|
return db.is_server_playing(str(server_id))
|
||||||
|
|
||||||
|
|
||||||
|
async def set_current_song(server_id, title, url, thumbnail="", duration=0):
|
||||||
|
"""Set the currently playing song"""
|
||||||
|
db.set_current_song(
|
||||||
|
str(server_id),
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
thumbnail,
|
||||||
|
duration,
|
||||||
|
time.time() # start_time
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_song(server_id):
|
||||||
|
"""Get current song info"""
|
||||||
|
return db.get_current_song(str(server_id))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_progress(server_id):
|
||||||
|
"""Get playback progress (elapsed, duration, percentage)"""
|
||||||
|
return db.get_current_progress(str(server_id))
|
||||||
|
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Settings Management
|
||||||
|
# ===================================
|
||||||
|
|
||||||
|
async def get_loop_mode(server_id):
|
||||||
|
"""Get loop mode: 'off', 'song', or 'queue'"""
|
||||||
|
return db.get_loop_mode(str(server_id))
|
||||||
|
|
||||||
|
|
||||||
|
async def set_loop_mode(server_id, mode):
|
||||||
|
"""Set loop mode: 'off', 'song', or 'queue'"""
|
||||||
|
db.set_loop_mode(str(server_id), mode)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_volume(server_id):
|
||||||
|
"""Get volume (0-200)"""
|
||||||
|
return db.get_volume(str(server_id))
|
||||||
|
|
||||||
|
|
||||||
|
async def set_volume(server_id, vol):
|
||||||
|
"""Set volume (0-200)"""
|
||||||
|
return db.set_volume(str(server_id), vol)
|
||||||
|
|
||||||
|
|
||||||
async def get_effect(server_id):
|
async def get_effect(server_id):
|
||||||
conn = sqlite3.connect(db_path)
|
"""Get current audio effect"""
|
||||||
cursor = conn.cursor()
|
return db.get_effect(str(server_id))
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("SELECT effect FROM servers WHERE server_id = ?", (server_id,))
|
|
||||||
res = cursor.fetchone()
|
|
||||||
conn.close()
|
|
||||||
return res[0] if res else 'none'
|
|
||||||
|
|
||||||
async def set_effect(server_id, fx):
|
async def set_effect(server_id, fx):
|
||||||
conn = sqlite3.connect(db_path)
|
"""Set audio effect"""
|
||||||
cursor = conn.cursor()
|
db.set_effect(str(server_id), fx)
|
||||||
await add_server(server_id, cursor, conn)
|
|
||||||
cursor.execute("UPDATE servers SET effect = ? WHERE server_id = ?", (fx, server_id))
|
|
||||||
conn.commit()
|
# ===================================
|
||||||
conn.close()
|
# Effect Metadata
|
||||||
|
# ===================================
|
||||||
|
|
||||||
def list_all_effects():
|
def list_all_effects():
|
||||||
|
"""List all available audio effects"""
|
||||||
return [
|
return [
|
||||||
'none', 'bassboost', 'nightcore', 'slowed', 'earrape', 'deepfry', 'distortion',
|
'none', 'bassboost', 'nightcore', 'slowed', 'earrape', 'deepfry', 'distortion',
|
||||||
'reverse', 'chipmunk', 'demonic', 'underwater', 'robot',
|
'reverse', 'chipmunk', 'demonic', 'underwater', 'robot',
|
||||||
'8d', 'vibrato', 'tremolo', 'echo', 'phone', 'megaphone'
|
'8d', 'vibrato', 'tremolo', 'echo', 'phone', 'megaphone'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_effect_emoji(effect_name):
|
def get_effect_emoji(effect_name):
|
||||||
# Short list of emoji mappings
|
"""Get emoji for effect"""
|
||||||
emojis = {
|
emojis = {
|
||||||
'none': '✨', # Changed to generic Sparkles
|
'none': '✨',
|
||||||
'bassboost': '💥',
|
'bassboost': '💥',
|
||||||
'nightcore': '⚡',
|
'nightcore': '⚡',
|
||||||
'slowed': '🐢',
|
'slowed': '🐢',
|
||||||
@@ -461,7 +341,9 @@ def get_effect_emoji(effect_name):
|
|||||||
}
|
}
|
||||||
return emojis.get(effect_name, '✨')
|
return emojis.get(effect_name, '✨')
|
||||||
|
|
||||||
|
|
||||||
def get_effect_description(effect_name):
|
def get_effect_description(effect_name):
|
||||||
|
"""Get description for effect"""
|
||||||
descriptions = {
|
descriptions = {
|
||||||
'none': 'Normal audio',
|
'none': 'Normal audio',
|
||||||
'bassboost': 'MAXIMUM BASS 🔊',
|
'bassboost': 'MAXIMUM BASS 🔊',
|
||||||
@@ -484,43 +366,60 @@ def get_effect_description(effect_name):
|
|||||||
}
|
}
|
||||||
return descriptions.get(effect_name, 'Unknown effect')
|
return descriptions.get(effect_name, 'Unknown effect')
|
||||||
|
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Playback
|
||||||
|
# ===================================
|
||||||
|
|
||||||
async def play(ctx):
|
async def play(ctx):
|
||||||
|
"""
|
||||||
|
Main playback loop - plays songs from queue
|
||||||
|
"""
|
||||||
server_id = ctx.guild.id
|
server_id = ctx.guild.id
|
||||||
voice_client = ctx.voice_client
|
voice_client = ctx.voice_client
|
||||||
|
|
||||||
if voice_client is None:
|
if voice_client is None:
|
||||||
await update_server(server_id, False)
|
await update_server(server_id, False)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Wait for current song to finish
|
||||||
while voice_client.is_playing():
|
while voice_client.is_playing():
|
||||||
await asyncio.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
|
|
||||||
|
# Get next song
|
||||||
url = await pop(server_id)
|
url = await pop(server_id)
|
||||||
if url is None:
|
if url is None:
|
||||||
await update_server(server_id, False)
|
await update_server(server_id, False)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Scale volume down to prevent earrape
|
# Get volume and effect settings
|
||||||
# User sees 0-200%, but internally we scale by 0.25
|
vol = await get_volume(server_id) / 100.0 * 0.25 # Scale down by 0.25
|
||||||
# So user's 100% = 0.25 actual volume (25%)
|
|
||||||
vol = await get_volume(server_id) / 100.0 * 0.25
|
|
||||||
fx = await get_effect(server_id)
|
fx = await get_effect(server_id)
|
||||||
opts = get_effect_options(fx)
|
opts = get_effect_options(fx)
|
||||||
|
|
||||||
|
# Create audio source
|
||||||
src = discord.FFmpegPCMAudio(url, **opts)
|
src = discord.FFmpegPCMAudio(url, **opts)
|
||||||
src = discord.PCMVolumeTransformer(src, volume=vol)
|
src = discord.PCMVolumeTransformer(src, volume=vol)
|
||||||
|
|
||||||
|
# After callback - play next song
|
||||||
def after(e):
|
def after(e):
|
||||||
if e: print(e)
|
if e:
|
||||||
if voice_client and not voice_client.is_connected(): return
|
print(f"Playback error: {e}")
|
||||||
|
if voice_client and not voice_client.is_connected():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Schedule next song
|
||||||
coro = play(ctx)
|
coro = play(ctx)
|
||||||
fut = asyncio.run_coroutine_threadsafe(coro, ctx.bot.loop)
|
fut = asyncio.run_coroutine_threadsafe(coro, ctx.bot.loop)
|
||||||
try: fut.result()
|
try:
|
||||||
except: pass
|
fut.result()
|
||||||
|
except Exception as ex:
|
||||||
|
print(f"Error in after callback: {ex}")
|
||||||
|
|
||||||
voice_client.play(src, after=after)
|
voice_client.play(src, after=after)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Play error: {e}")
|
print(f"Play error: {e}")
|
||||||
|
# Try next song on error
|
||||||
await play(ctx)
|
await play(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user