diff --git a/cogs/debug.py b/cogs/debug.py index 7dcead2..2741761 100644 --- a/cogs/debug.py +++ b/cogs/debug.py @@ -1,4 +1,5 @@ from dataclass.custom_embed import CustomEmbed +from datetime import timedelta from nextcord import Color, Interaction, slash_command from nextcord.ext import application_checks from nextcord.ext.commands import Cog @@ -48,24 +49,38 @@ async def stats(self, itx: Interaction): nodes = self._bot.pool.nodes for node in nodes: stats = node.stats - - # Adapted from @ooliver1/mafic test bot - pages.append(CustomEmbed( - color=Color.purple(), - title=f':bar_chart:|Stats for node `{node.label}`', - description='No statistics available' if stats is None else STATS_FORMAT.format( - uptime=stats.uptime, - used=stats.memory.used / 1024 / 1024, - free=stats.memory.free / 1024 / 1024, - allocated=stats.memory.allocated / 1024 / 1024, - reservable=stats.memory.reservable / 1024 / 1024, - system_load=stats.cpu.system_load * 100, - lavalink_load=stats.cpu.lavalink_load * 100, - player_count=stats.player_count, - playing_player_count=stats.playing_player_count - ), - footer=f'{len(nodes)} total node(s)' - ).get()) + + if stats is not None: + # Properly convert uptime to timedelta + # Lavalink emits uptime in milliseconds, + # but Mafic passes it as seconds to timedelta. + # Here we correct it ourselves. + uptime = timedelta(milliseconds=stats.uptime.total_seconds()) + + # Adapted from @ooliver1/mafic test bot + pages.append(CustomEmbed( + color=Color.purple(), + title=f':bar_chart:|Stats for node `{node.label}`', + description='No statistics available' if stats is None else STATS_FORMAT.format( + uptime=uptime, + used=stats.memory.used / 1024 / 1024, + free=stats.memory.free / 1024 / 1024, + allocated=stats.memory.allocated / 1024 / 1024, + reservable=stats.memory.reservable / 1024 / 1024, + system_load=stats.cpu.system_load * 100, + lavalink_load=stats.cpu.lavalink_load * 100, + player_count=stats.player_count, + playing_player_count=stats.playing_player_count + ), + footer=f'{len(nodes)} total node(s)' + ).get()) + else: + pages.append(CustomEmbed( + color=Color.red(), + title=f':bar_chart:|Stats for node `{node.label}`', + description='No statistics available', + footer=f'{len(nodes)} total node(s)' + ).get()) # Run paginator paginator = Paginator(itx) diff --git a/database/__init__.py b/database/__init__.py index e69de29..596daf8 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -0,0 +1,81 @@ +from utils.logger import create_logger +from .migrations import run_migrations +import sqlite3 as sql + + +class Database: + """ + Class for handling connections to the bot's SQLite DB. + """ + + def __init__(self, db_filename: str): + self._con = sql.connect(db_filename) + self._cur = self._con.cursor() + self._logger = create_logger(self.__class__.__name__) + self._logger.info(f'Connected to database: {db_filename}') + + # Run migrations + run_migrations(self._logger, self._con) + + def init_guild(self, guild_id: int): + """ + Initialize a guild in the database if it hasn't been yet. + """ + self._cur.execute(f'INSERT OR IGNORE INTO player_settings (guild_id) VALUES ({guild_id})') + self._con.commit() + + def get_volume(self, guild_id: int) -> int: + """ + Get the volume for a guild. + """ + self._cur.execute(f'SELECT volume FROM player_settings WHERE guild_id = {guild_id}') + return self._cur.fetchone()[0] + + def set_volume(self, guild_id: int, volume: int): + """ + Set the volume for a guild. + """ + self._cur.execute(f'UPDATE player_settings SET volume = {volume} WHERE guild_id = {guild_id}') + self._con.commit() + + def get_loop(self, guild_id: int) -> bool: + """ + Get the loop setting for a guild. + """ + self._cur.execute(f'SELECT loop FROM player_settings WHERE guild_id = {guild_id}') + return self._cur.fetchone()[0] == 1 + + def set_loop(self, guild_id: int, loop: bool): + """ + Set the loop setting for a guild. + """ + self._cur.execute(f'UPDATE player_settings SET loop = {int(loop)} WHERE guild_id = {guild_id}') + self._con.commit() + + def get_now_playing(self, guild_id: int) -> int: + """ + Get the last now playing message ID for a guild. + """ + self._cur.execute(f'SELECT last_np_msg FROM player_settings WHERE guild_id = {guild_id}') + return self._cur.fetchone()[0] + + def set_now_playing(self, guild_id: int, msg_id: int): + """ + Set the last now playing message ID for a guild. + """ + self._cur.execute(f'UPDATE player_settings SET last_np_msg = {msg_id} WHERE guild_id = {guild_id}') + self._con.commit() + + def get_session_id(self, node_id: str) -> str: + """ + Get the session ID for a Lavalink node. + """ + self._cur.execute(f'SELECT session_id FROM lavalink WHERE node_id = "{node_id}"') + return self._cur.fetchone()[0] + + def set_session_id(self, node_id: str, session_id: str): + """ + Set the session ID for a Lavalink node. + """ + self._cur.execute(f'INSERT OR REPLACE INTO lavalink (node_id, session_id) VALUES ("{node_id}", "{session_id}")') + self._con.commit() diff --git a/database/database.py b/database/database.py deleted file mode 100644 index 596daf8..0000000 --- a/database/database.py +++ /dev/null @@ -1,81 +0,0 @@ -from utils.logger import create_logger -from .migrations import run_migrations -import sqlite3 as sql - - -class Database: - """ - Class for handling connections to the bot's SQLite DB. - """ - - def __init__(self, db_filename: str): - self._con = sql.connect(db_filename) - self._cur = self._con.cursor() - self._logger = create_logger(self.__class__.__name__) - self._logger.info(f'Connected to database: {db_filename}') - - # Run migrations - run_migrations(self._logger, self._con) - - def init_guild(self, guild_id: int): - """ - Initialize a guild in the database if it hasn't been yet. - """ - self._cur.execute(f'INSERT OR IGNORE INTO player_settings (guild_id) VALUES ({guild_id})') - self._con.commit() - - def get_volume(self, guild_id: int) -> int: - """ - Get the volume for a guild. - """ - self._cur.execute(f'SELECT volume FROM player_settings WHERE guild_id = {guild_id}') - return self._cur.fetchone()[0] - - def set_volume(self, guild_id: int, volume: int): - """ - Set the volume for a guild. - """ - self._cur.execute(f'UPDATE player_settings SET volume = {volume} WHERE guild_id = {guild_id}') - self._con.commit() - - def get_loop(self, guild_id: int) -> bool: - """ - Get the loop setting for a guild. - """ - self._cur.execute(f'SELECT loop FROM player_settings WHERE guild_id = {guild_id}') - return self._cur.fetchone()[0] == 1 - - def set_loop(self, guild_id: int, loop: bool): - """ - Set the loop setting for a guild. - """ - self._cur.execute(f'UPDATE player_settings SET loop = {int(loop)} WHERE guild_id = {guild_id}') - self._con.commit() - - def get_now_playing(self, guild_id: int) -> int: - """ - Get the last now playing message ID for a guild. - """ - self._cur.execute(f'SELECT last_np_msg FROM player_settings WHERE guild_id = {guild_id}') - return self._cur.fetchone()[0] - - def set_now_playing(self, guild_id: int, msg_id: int): - """ - Set the last now playing message ID for a guild. - """ - self._cur.execute(f'UPDATE player_settings SET last_np_msg = {msg_id} WHERE guild_id = {guild_id}') - self._con.commit() - - def get_session_id(self, node_id: str) -> str: - """ - Get the session ID for a Lavalink node. - """ - self._cur.execute(f'SELECT session_id FROM lavalink WHERE node_id = "{node_id}"') - return self._cur.fetchone()[0] - - def set_session_id(self, node_id: str, session_id: str): - """ - Set the session ID for a Lavalink node. - """ - self._cur.execute(f'INSERT OR REPLACE INTO lavalink (node_id, session_id) VALUES ("{node_id}", "{session_id}")') - self._con.commit() diff --git a/database/migrations/__init__.py b/database/migrations/__init__.py index 8ffaa00..9f536af 100644 --- a/database/migrations/__init__.py +++ b/database/migrations/__init__.py @@ -12,7 +12,7 @@ def run_migrations(logger: 'Logger', con: 'Connection'): :param con: The Connection instance to the SQLite database. """ - for file in listdir(path.dirname(__file__)): + for file in sorted(listdir(path.dirname(__file__))): if file != path.basename(__file__) and file.endswith('.py'): logger.info(f'Running migration: {file}') migration = import_module(f'database.migrations.{file[:-3]}') diff --git a/utils/blanco.py b/utils/blanco.py index 05a233b..a0d028c 100644 --- a/utils/blanco.py +++ b/utils/blanco.py @@ -1,9 +1,9 @@ +from database import Database from mafic import NodePool, VoiceRegion from nextcord import Activity, ActivityType, Interaction, PartialMessageable from nextcord.ext.commands import Bot from nextcord.ext.tasks import loop from typing import Any, Dict, Optional, TYPE_CHECKING -from database.database import Database from utils.jockey_helpers import create_error_embed from utils.logger import create_logger from utils.spotify_client import Spotify @@ -78,7 +78,7 @@ async def on_ready(self): self._logger.info(f'Logged in as {self.user}') self.load_extension('cogs') if self.debug: - self._logger.info('Debug mode enabled') + self._logger.warn('Debug mode enabled') await self.change_presence( activity=Activity(name='/play (debug)', type=ActivityType.listening) ) @@ -102,8 +102,11 @@ async def on_node_ready(self, node: 'Node'): self._db.set_session_id(node.label, node.session_id) async def on_track_start(self, event: 'TrackStartEvent[Jockey]'): - # Send now playing embed - await self.send_now_playing(event) + if event.player.playing: + # Send now playing embed + await self.send_now_playing(event) + else: + self._logger.warn(f'Got track_start event for idle player in {event.player.guild.name}') async def on_track_end(self, event: 'TrackEndEvent[Jockey]'): # Play next track in queue diff --git a/utils/lavalink_client.py b/utils/lavalink_client.py index cca759a..96d3973 100644 --- a/utils/lavalink_client.py +++ b/utils/lavalink_client.py @@ -7,13 +7,14 @@ from mafic import Node, Track -blacklist = ( +BLACKLIST = ( '3d' '8d', 'cover', 'instrumental', 'karaoke', 'live', + 'loop', 'mashup', 'minus one', 'performance', @@ -102,7 +103,7 @@ async def get_youtube_matches(node: 'Node', query: str, desired_duration_ms: Opt # if the original query did not ask for it valid = True if automatic: - for word in blacklist: + for word in BLACKLIST: if word in result.title.lower() and not word in query.lower(): valid = False break