Skip to content

Commit

Permalink
Merge pull request #38 from jareddantis-bots/next
Browse files Browse the repository at this point in the history
Release 0.5.0
  • Loading branch information
jareddantis authored Sep 20, 2023
2 parents 475cf97 + c3a4a6a commit 4308f1a
Show file tree
Hide file tree
Showing 31 changed files with 1,590 additions and 441 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ blanco-bot

<img align="right" src="/server/static/images/logo.svg" width=200 alt="Blanco logo">

Blanco is a Discord music bot made with [Nextcord](https://nextcord.dev) that supports pulling music metadata from Spotify and scrobbling to Last.fm. Music playback is handled by the [Mafic](/~https://github.com/ooliver1/mafic) client for the [Lavalink](/~https://github.com/lavalink-devs/Lavalink) server.
Blanco is a Discord music bot made with [Nextcord](https://nextcord.dev). It supports pulling music metadata from Spotify and MusicBrainz, and it can also scrobble your listening history to Last.fm. Music playback is handled by the [Mafic](/~https://github.com/ooliver1/mafic) client for the [Lavalink](/~https://github.com/lavalink-devs/Lavalink) server.

The bot stores data in a local SQLite database. This database is populated automatically, and the data it will contain include authentication tokens, Lavalink session IDs, volume levels, and queue repeat preferences per guild.

[![GitHub Releases](https://img.shields.io/github/v/release/jareddantis-bots/blanco-bot)](/~https://github.com/jareddantis-bots/blanco-bot/releases/latest)
[![Docker Image CI](/~https://github.com/jareddantis/blanco-bot/actions/workflows/build.yml/badge.svg)](/~https://github.com/jareddantis/blanco-bot/actions/workflows/build.yml)
[![Docker Pulls](https://img.shields.io/docker/pulls/jareddantis/blanco-bot)](https://hub.docker.com/r/jareddantis/blanco-bot)

The bot stores data in a local SQLite database. This database is populated automatically on first run, and the data it will contain include authentication tokens, Lavalink session IDs, volume levels, and queue repeat preferences per guild.

See the [wiki](/~https://github.com/jareddantis-bots/blanco-bot/wiki#Command-reference) for a list of commands.

# Deployment
Expand Down
2 changes: 1 addition & 1 deletion cogs/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(self, bot: 'BlancoBot'):
Constructor for DebugCog.
"""
self._bot = bot
self._logger = create_logger(self.__class__.__name__, bot.debug)
self._logger = create_logger(self.__class__.__name__)
self._logger.info('Loaded DebugCog')

@slash_command(name='announce')
Expand Down
101 changes: 68 additions & 33 deletions cogs/player/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
from requests import HTTPError

from dataclass.custom_embed import CustomEmbed
from utils.constants import SPOTIFY_403_ERR_MSG
from utils.constants import RELEASE, SPOTIFY_403_ERR_MSG
from utils.embeds import create_error_embed, create_success_embed
from utils.exceptions import EndOfQueueError, JockeyError, JockeyException
from utils.exceptions import (EmptyQueueError, EndOfQueueError, JockeyError,
JockeyException, SpotifyNoResultsError)
from utils.logger import create_logger
from utils.paginator import Paginator
from utils.player_checks import check_mutual_voice
Expand Down Expand Up @@ -47,7 +48,7 @@ def __init__(self, bot: 'BlancoBot'):
Constructor for PlayerCog.
"""
self._bot = bot
self._logger = create_logger(self.__class__.__name__, bot.debug)
self._logger = create_logger(self.__class__.__name__)

# Initialize Lavalink client instance
if not bot.pool_initialized:
Expand All @@ -67,9 +68,9 @@ async def on_voice_state_update(self, member: Member, before: VoiceState, after:
if jockey is not None:
# Stop playing if we're left alone
if (hasattr(jockey.channel, 'members') and
len(jockey.channel.members) == 1 and
jockey.channel.members[0].id == member.guild.me.id and
after.channel is None): # type: ignore
len(jockey.channel.members) == 1 and # type: ignore
jockey.channel.members[0].id == member.guild.me.id and # type: ignore
after.channel is None):
return await self._disconnect(jockey=jockey, reason='You left me alone :(')

# Did we get server undeafened?
Expand Down Expand Up @@ -149,7 +150,8 @@ async def _disconnect(
# Send disconnection message
embed = CustomEmbed(
title=':wave:|Disconnected from voice',
description=reason
description=reason,
footer=f'Blanco release {RELEASE}'
).get()

# Try to send disconnection message
Expand All @@ -164,6 +166,9 @@ async def _disconnect(
except (Forbidden, HTTPException):
self._logger.error('Unable to send disconnect message in guild %d', jockey.guild.id)

# Dispatch disconnect event
self._bot.dispatch('jockey_disconnect', jockey)

@slash_command(name='jump')
@application_checks.check(check_mutual_voice)
async def jump(
Expand Down Expand Up @@ -196,8 +201,8 @@ async def loop(self, itx: Interaction):
Loops the current track.
"""
jockey = await self._get_jockey(itx)
if not jockey.is_looping:
jockey.is_looping = True
if not jockey.queue_manager.is_looping_one:
jockey.queue_manager.is_looping_one = True
return await itx.response.send_message(embed=create_success_embed('Looping current track'))

@slash_command(name='loopall')
Expand All @@ -207,8 +212,8 @@ async def loopall(self, itx: Interaction):
Loops the whole queue.
"""
jockey = await self._get_jockey(itx)
if not jockey.is_looping_all:
jockey.is_looping_all = True
if not jockey.queue_manager.is_looping_all:
jockey.queue_manager.is_looping_all = True
return await itx.response.send_message(embed=create_success_embed('Looping entire queue'))

@slash_command(name='nowplaying')
Expand Down Expand Up @@ -285,7 +290,7 @@ async def play(
except JockeyError as err:
# Disconnect if we're not playing anything
if not jockey.playing:
return await self._disconnect(itx=itx, reason=str(err))
return await self._disconnect(itx=itx, reason=f'Error: `{err}`')

return await itx.followup.send(embed=create_error_embed(str(err)))
except JockeyException as exc:
Expand All @@ -306,10 +311,11 @@ async def play(
f':sparkles: [Link Last.fm]({self._bot.config.base_url}) to scrobble as you listen'
)

return await itx.followup.send(embed=create_success_embed(
embed = create_success_embed(
title='Added to queue',
body='\n'.join(body)
))
body='\n'.join(body),
)
return await itx.followup.send(embed=embed.set_footer(text=f'Blanco release {RELEASE}'))

@slash_command(name='playlists')
async def playlist(self, itx: Interaction):
Expand Down Expand Up @@ -346,7 +352,7 @@ async def playlist(self, itx: Interaction):
), ephemeral=True)

# Create dropdown
view = SpotifyDropdownView(self._bot, playlists, itx.user.id)
view = SpotifyDropdownView(self._bot, playlists, itx.user.id, 'playlist')
await itx.followup.send(embed=create_success_embed(
title='Pick a playlist',
body='Select a playlist from the dropdown below.'
Expand Down Expand Up @@ -385,20 +391,16 @@ async def queue(self, itx: Interaction):

# Show loop status
embed_header = [f'{len(jockey.queue)} total']
if jockey.is_looping_all:
if jockey.queue_manager.is_looping_all:
embed_header.append(':repeat: Looping entire queue (`/unloopall` to disable)')

# Show shuffle status
queue = list(jockey.queue)
current = jockey.current_index
if jockey.is_shuffling:
queue = jockey.queue_manager.shuffled_queue
current = jockey.queue_manager.current_shuffled_index
if jockey.queue_manager.is_shuffling:
embed_header.append(
':twisted_rightwards_arrows: Shuffling queue (`/unshuffle` to disable)'
)
current = jockey.shuffle_indices.index(current)

# Get shuffled version of queue
queue = [jockey.queue[i] for i in jockey.shuffle_indices]

# Show queue in chunks of 10 per page
pages = []
Expand Down Expand Up @@ -468,7 +470,7 @@ async def remove(
return await itx.response.send_message(embed=create_error_embed(
message=f'Specify a number from 1 to {str(jockey.queue_size)}.'
), ephemeral=True)
if position - 1 == jockey.current_index:
if position - 1 == jockey.queue_manager.current_index:
return await itx.response.send_message(embed=create_error_embed(
message='You cannot remove the currently playing track.'
), ephemeral=True)
Expand All @@ -481,6 +483,39 @@ async def remove(
body=f'**{title}**\n{artist}'
))

@slash_command(name='search')
async def search(
self,
itx: Interaction,
search_type: str = SlashOption(
description='Search type',
required=True,
choices=['track', 'playlist', 'album', 'artist']
),
query: str = SlashOption(description='Query string', required=True)
):
"""
Search Spotify's catalog for tracks to play.
"""
if itx.user is None:
return
await itx.response.defer()

# Search catalog
try:
results = self._bot.spotify.search(query, search_type)
except SpotifyNoResultsError:
return await itx.followup.send(embed=create_error_embed(
message=f'No results found for `{query}`.'
), ephemeral=True)

# Create dropdown
view = SpotifyDropdownView(self._bot, results, itx.user.id, search_type)
await itx.followup.send(embed=create_success_embed(
title=f'Results for `{query}`',
body='Select a result to play from the dropdown below.'
), view=view, delete_after=60.0)

@slash_command(name='shuffle')
@application_checks.check(check_mutual_voice)
async def shuffle(self, itx: Interaction, quiet: bool = False):
Expand All @@ -494,8 +529,8 @@ async def shuffle(self, itx: Interaction, quiet: bool = False):
# Dispatch to jockey
jockey = await self._get_jockey(itx)
try:
await jockey.shuffle()
except EndOfQueueError as err:
jockey.queue_manager.shuffle()
except EmptyQueueError as err:
if not quiet:
await itx.followup.send(embed=create_error_embed(str(err.args[0])))
else:
Expand Down Expand Up @@ -538,8 +573,8 @@ async def unloop(self, itx: Interaction):
"""
# Dispatch to jockey
jockey = await self._get_jockey(itx)
if jockey.is_looping:
jockey.is_looping = False
if jockey.queue_manager.is_looping_one:
jockey.queue_manager.is_looping_one = False
return await itx.response.send_message(
embed=create_success_embed('Not looping current track')
)
Expand All @@ -552,8 +587,8 @@ async def unloopall(self, itx: Interaction):
"""
# Dispatch to jockey
jockey = await self._get_jockey(itx)
if jockey.is_looping_all:
jockey.is_looping_all = False
if jockey.queue_manager.is_looping_all:
jockey.queue_manager.is_looping_all = False
return await itx.response.send_message(
embed=create_success_embed('Not looping entire queue')
)
Expand Down Expand Up @@ -585,8 +620,8 @@ async def unshuffle(self, itx: Interaction, quiet: bool = False):

# Dispatch to jockey
jockey = await self._get_jockey(itx)
if jockey.is_shuffling:
jockey.shuffle_indices = []
if jockey.queue_manager.is_shuffling:
jockey.queue_manager.unshuffle()
if not quiet:
return await itx.followup.send(embed=create_success_embed('Unshuffled'))

Expand Down
Loading

0 comments on commit 4308f1a

Please sign in to comment.