mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[V3 Audio] Add Spotify support (#2328)
* [V3 Audio] Add Spotify support * [V3 Audio] Update LICENSE * Appeasing the style gods * Extra word removal on LICENSE * Update for #2389 Thanks to TrustyJAID for the help. * Playlist command support for Spotify URLs or codes * Add exception for dc while loading Spotify tracks * Allow Spotify urls by default in audioset restrict Matches the behavior of Spotify codes already being allowed by default. * Update audio.py * .format() moving * Added a character to try to make Travis behave
This commit is contained in:
parent
94c3a2fedd
commit
050300040c
28
LICENSE
28
LICENSE
@ -672,3 +672,31 @@ may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
The Red-DiscordBot project contains subcomponents in audio.py that have a
|
||||
separate copyright notice and license terms. Your use of the source code for
|
||||
these subcomponents is subject to the terms and conditions of the following
|
||||
licenses.
|
||||
|
||||
This product bundles methods from https://github.com/Just-Some-Bots/MusicBot/
|
||||
blob/master/musicbot/spotify.py which are available under an MIT license.
|
||||
|
||||
Copyright (c) 2015-2018 Just-Some-Bots (https://github.com/Just-Some-Bots)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import base64
|
||||
import datetime
|
||||
import discord
|
||||
from fuzzywuzzy import process
|
||||
@ -32,8 +33,8 @@ from .manager import shutdown_lavalink_server, start_lavalink_server, maybe_down
|
||||
|
||||
_ = Translator("Audio", __file__)
|
||||
|
||||
__version__ = "0.0.8"
|
||||
__author__ = ["aikaterna", "billy/bollo/ati"]
|
||||
__version__ = "0.0.8b"
|
||||
__author__ = ["aikaterna"]
|
||||
|
||||
log = logging.getLogger("red.audio")
|
||||
|
||||
@ -84,6 +85,8 @@ class Audio(commands.Cog):
|
||||
self._connect_task = None
|
||||
self._disconnect_task = None
|
||||
self._cleaned_up = False
|
||||
self.spotify_token = None
|
||||
self.play_lock = {}
|
||||
|
||||
async def initialize(self):
|
||||
self._restart_connect()
|
||||
@ -331,6 +334,27 @@ class Audio(commands.Cog):
|
||||
await self.config.guild(ctx.guild).emptydc_timer.set(seconds)
|
||||
await self.config.guild(ctx.guild).emptydc_enabled.set(enabled)
|
||||
|
||||
@audioset.command()
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def jukebox(self, ctx, price: int):
|
||||
"""Set a price for queueing tracks for non-mods. 0 to disable."""
|
||||
if price < 0:
|
||||
return await self._embed_msg(ctx, _("Can't be less than zero."))
|
||||
if price == 0:
|
||||
jukebox = False
|
||||
await self._embed_msg(ctx, _("Jukebox mode disabled."))
|
||||
else:
|
||||
jukebox = True
|
||||
await self._embed_msg(
|
||||
ctx,
|
||||
_("Track queueing command price set to {price} {currency}.").format(
|
||||
price=price, currency=await bank.get_currency_name(ctx.guild)
|
||||
),
|
||||
)
|
||||
|
||||
await self.config.guild(ctx.guild).jukebox_price.set(price)
|
||||
await self.config.guild(ctx.guild).jukebox.set(jukebox)
|
||||
|
||||
@audioset.command()
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def maxlength(self, ctx, seconds):
|
||||
@ -354,35 +378,6 @@ class Audio(commands.Cog):
|
||||
|
||||
await self.config.guild(ctx.guild).maxlength.set(seconds)
|
||||
|
||||
@audioset.command()
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def role(self, ctx, role_name: discord.Role):
|
||||
"""Set the role to use for DJ mode."""
|
||||
await self.config.guild(ctx.guild).dj_role.set(role_name.id)
|
||||
dj_role_obj = ctx.guild.get_role(await self.config.guild(ctx.guild).dj_role())
|
||||
await self._embed_msg(ctx, _("DJ role set to: {role.name}.").format(role=dj_role_obj))
|
||||
|
||||
@audioset.command()
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def jukebox(self, ctx, price: int):
|
||||
"""Set a price for queueing tracks for non-mods. 0 to disable."""
|
||||
if price < 0:
|
||||
return await self._embed_msg(ctx, _("Can't be less than zero."))
|
||||
if price == 0:
|
||||
jukebox = False
|
||||
await self._embed_msg(ctx, _("Jukebox mode disabled."))
|
||||
else:
|
||||
jukebox = True
|
||||
await self._embed_msg(
|
||||
ctx,
|
||||
_("Track queueing command price set to {price} {currency}.").format(
|
||||
price=price, currency=await bank.get_currency_name(ctx.guild)
|
||||
),
|
||||
)
|
||||
|
||||
await self.config.guild(ctx.guild).jukebox_price.set(price)
|
||||
await self.config.guild(ctx.guild).jukebox.set(jukebox)
|
||||
|
||||
@audioset.command()
|
||||
@checks.mod_or_permissions(manage_messages=True)
|
||||
async def notify(self, ctx):
|
||||
@ -406,6 +401,14 @@ class Audio(commands.Cog):
|
||||
ctx, _("Commercial links only: {true_or_false}.").format(true_or_false=not restrict)
|
||||
)
|
||||
|
||||
@audioset.command()
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def role(self, ctx, role_name: discord.Role):
|
||||
"""Set the role to use for DJ mode."""
|
||||
await self.config.guild(ctx.guild).dj_role.set(role_name.id)
|
||||
dj_role_obj = ctx.guild.get_role(await self.config.guild(ctx.guild).dj_role())
|
||||
await self._embed_msg(ctx, _("DJ role set to: {role.name}.").format(role=dj_role_obj))
|
||||
|
||||
@audioset.command()
|
||||
async def settings(self, ctx):
|
||||
"""Show the current settings."""
|
||||
@ -461,6 +464,33 @@ class Audio(commands.Cog):
|
||||
embed = discord.Embed(colour=await ctx.embed_colour(), description=box(msg, lang="ini"))
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
@audioset.command()
|
||||
@checks.is_owner()
|
||||
async def spotifyapi(self, ctx):
|
||||
"""Instructions to set the Spotify API tokens."""
|
||||
message = _(
|
||||
f"1. Go to Spotify developers and log in with your Spotify account\n"
|
||||
"(https://developer.spotify.com/dashboard/applications)\n"
|
||||
'2. Click "Create An App"\n'
|
||||
"3. Fill out the form provided with your app name, etc.\n"
|
||||
'4. When asked if you\'re developing commercial integration select "No"\n'
|
||||
"5. Accept the terms and conditions.\n"
|
||||
"6. Copy your client ID and your client secret into:\n"
|
||||
"`{prefix}set api spotify client_id,your_client_id "
|
||||
"client_secret,your_client_secret`"
|
||||
).format(prefix=ctx.prefix)
|
||||
await ctx.maybe_send_embed(message)
|
||||
|
||||
@checks.is_owner()
|
||||
@audioset.command()
|
||||
async def status(self, ctx):
|
||||
"""Enable/disable tracks' titles as status."""
|
||||
status = await self.config.status()
|
||||
await self.config.status.set(not status)
|
||||
await self._embed_msg(
|
||||
ctx, _("Song titles as status: {true_or_false}.").format(true_or_false=not status)
|
||||
)
|
||||
|
||||
@audioset.command()
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def thumbnail(self, ctx):
|
||||
@ -493,21 +523,30 @@ class Audio(commands.Cog):
|
||||
await self.config.guild(ctx.guild).vote_percent.set(percent)
|
||||
await self.config.guild(ctx.guild).vote_enabled.set(enabled)
|
||||
|
||||
@checks.is_owner()
|
||||
@audioset.command()
|
||||
async def status(self, ctx):
|
||||
"""Enable/disable tracks' titles as status."""
|
||||
status = await self.config.status()
|
||||
await self.config.status.set(not status)
|
||||
await self._embed_msg(
|
||||
ctx, _("Song titles as status: {true_or_false}.").format(true_or_false=not status)
|
||||
)
|
||||
@checks.is_owner()
|
||||
async def youtubeapi(self, ctx):
|
||||
"""Instructions to set the YouTube API key."""
|
||||
message = _(
|
||||
f"1. Go to Google Developers Console and log in with your Google account.\n"
|
||||
"(https://console.developers.google.com/)\n"
|
||||
"2. You should be prompted to create a new project (name does not matter).\n"
|
||||
"3. Click on Enable APIs and Services at the top.\n"
|
||||
"4. In the list of APIs choose or search for YouTube Data API v3 and click on it. Choose Enable.\n"
|
||||
"5. Click on Credentials on the left navigation bar.\n"
|
||||
"6. Click on Create Credential at the top.\n"
|
||||
'7. At the top click the link for "API key".\n'
|
||||
"8. No application restrictions are needed. Click Create at the bottom.\n"
|
||||
"9. You now have a key to add to `{prefix}set api youtube api_key,your_api_key`"
|
||||
).format(prefix=ctx.prefix)
|
||||
await ctx.maybe_send_embed(message)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def audiostats(self, ctx):
|
||||
"""Audio stats."""
|
||||
server_num = len([p for p in lavalink.players if p.current is not None])
|
||||
total_num = len([p for p in lavalink.players])
|
||||
server_list = []
|
||||
|
||||
for p in lavalink.players:
|
||||
@ -549,7 +588,7 @@ class Audio(commands.Cog):
|
||||
servers = "\n".join(server_list)
|
||||
embed = discord.Embed(
|
||||
colour=await ctx.embed_colour(),
|
||||
title=_("Connected in {num} servers:").format(num=server_num),
|
||||
title=_("Playing in {num}/{total} servers:").format(num=server_num, total=total_num),
|
||||
description=servers,
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
@ -605,6 +644,7 @@ class Audio(commands.Cog):
|
||||
):
|
||||
return await self._embed_msg(ctx, _("There are other people listening to music."))
|
||||
else:
|
||||
self._play_lock(ctx, False)
|
||||
await lavalink.get_player(ctx.guild.id).stop()
|
||||
await lavalink.get_player(ctx.guild.id).disconnect()
|
||||
|
||||
@ -977,6 +1017,7 @@ class Audio(commands.Cog):
|
||||
@commands.guild_only()
|
||||
async def play(self, ctx, *, query):
|
||||
"""Play a URL or search for a track."""
|
||||
|
||||
guild_data = await self.config.guild(ctx.guild).all()
|
||||
restrict = await self.config.restrict()
|
||||
if restrict:
|
||||
@ -1021,6 +1062,13 @@ class Audio(commands.Cog):
|
||||
return await self._embed_msg(ctx, _("No tracks to play."))
|
||||
query = query.strip("<>")
|
||||
|
||||
if "open.spotify.com" in query:
|
||||
query = "spotify:{}".format(
|
||||
re.sub("(http[s]?:\/\/)?(open.spotify.com)\/", "", query).replace("/", ":")
|
||||
)
|
||||
if query.startswith("spotify:"):
|
||||
return await self._get_spotify_tracks(ctx, query)
|
||||
|
||||
if query.startswith("localtrack:"):
|
||||
await self._localtracks_check(ctx)
|
||||
query = query.replace("localtrack:", "").replace(
|
||||
@ -1030,9 +1078,116 @@ class Audio(commands.Cog):
|
||||
if not self._match_url(query) and not (query.lower().endswith(allowed_files)):
|
||||
query = "ytsearch:{}".format(query)
|
||||
|
||||
await self._enqueue_tracks(ctx, query)
|
||||
|
||||
async def _get_spotify_tracks(self, ctx, query):
|
||||
if ctx.invoked_with == "play":
|
||||
enqueue_tracks = True
|
||||
else:
|
||||
enqueue_tracks = False
|
||||
player = lavalink.get_player(ctx.guild.id)
|
||||
api_data = await self._check_api_tokens()
|
||||
guild_data = await self.config.guild(ctx.guild).all()
|
||||
if "open.spotify.com" in query:
|
||||
query = "spotify:{}".format(
|
||||
re.sub("(http[s]?:\/\/)?(open.spotify.com)\/", "", query).replace("/", ":")
|
||||
)
|
||||
if query.startswith("spotify:"):
|
||||
if (
|
||||
not api_data["spotify_client_id"]
|
||||
or not api_data["spotify_client_secret"]
|
||||
or not api_data["youtube_api"]
|
||||
):
|
||||
return await self._embed_msg(
|
||||
ctx,
|
||||
_(
|
||||
"The owner needs to set the Spotify client ID, Spotify client secret, "
|
||||
"and YouTube API key before Spotify URLs or codes can be used. "
|
||||
"\nSee `{prefix}audioset youtubeapi` and `{prefix}audioset spotifyapi` "
|
||||
"for instructions."
|
||||
).format(prefix=ctx.prefix),
|
||||
)
|
||||
try:
|
||||
if self.play_lock[ctx.message.guild.id]:
|
||||
return await self._embed_msg(
|
||||
ctx, _("Wait until the playlist has finished loading.")
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
parts = query.split(":")
|
||||
if "track" in parts:
|
||||
res = await self._make_spotify_req(
|
||||
"https://api.spotify.com/v1/tracks/{0}".format(parts[-1])
|
||||
)
|
||||
try:
|
||||
query = "{} {}".format(res["artists"][0]["name"], res["name"])
|
||||
if enqueue_tracks:
|
||||
return await self._enqueue_tracks(ctx, query)
|
||||
else:
|
||||
tracks = await player.get_tracks(f"ytsearch:{query}")
|
||||
if not tracks:
|
||||
return await self._embed_msg(ctx, _("Nothing found."))
|
||||
single_track = []
|
||||
single_track.append(tracks[0])
|
||||
return single_track
|
||||
|
||||
except KeyError:
|
||||
return await self._embed_msg(
|
||||
ctx,
|
||||
_(
|
||||
"The Spotify API key or client secret has not been set properly. "
|
||||
"\nUse `{prefix}audioset spotifyapi` for instructions."
|
||||
).format(prefix=ctx.prefix),
|
||||
)
|
||||
elif "album" in parts:
|
||||
query = parts[-1]
|
||||
self._play_lock(ctx, True)
|
||||
track_list = await self._spotify_playlist(
|
||||
ctx, "album", api_data["youtube_api"], query
|
||||
)
|
||||
if not track_list:
|
||||
self._play_lock(ctx, False)
|
||||
return
|
||||
if enqueue_tracks:
|
||||
return await self._enqueue_tracks(ctx, track_list)
|
||||
else:
|
||||
return track_list
|
||||
elif "playlist" in parts:
|
||||
query = parts[-1]
|
||||
self._play_lock(ctx, True)
|
||||
if "user" in parts:
|
||||
track_list = await self._spotify_playlist(
|
||||
ctx, "user_playlist", api_data["youtube_api"], query
|
||||
)
|
||||
else:
|
||||
track_list = await self._spotify_playlist(
|
||||
ctx, "playlist", api_data["youtube_api"], query
|
||||
)
|
||||
if not track_list:
|
||||
self._play_lock(ctx, False)
|
||||
return
|
||||
if enqueue_tracks:
|
||||
return await self._enqueue_tracks(ctx, track_list)
|
||||
else:
|
||||
return track_list
|
||||
|
||||
else:
|
||||
return await self._embed_msg(
|
||||
ctx, _("This doesn't seem to be a valid Spotify URL or code.")
|
||||
)
|
||||
|
||||
async def _enqueue_tracks(self, ctx, query):
|
||||
player = lavalink.get_player(ctx.guild.id)
|
||||
guild_data = await self.config.guild(ctx.guild).all()
|
||||
if type(query) is not list:
|
||||
if not query.startswith("http"):
|
||||
query = f"ytsearch:{query}"
|
||||
tracks = await player.get_tracks(query)
|
||||
if not tracks:
|
||||
return await self._embed_msg(ctx, _("Nothing found."))
|
||||
else:
|
||||
tracks = query
|
||||
|
||||
queue_duration = await self._queue_duration(ctx)
|
||||
queue_total_duration = lavalink.utils.format_time(queue_duration)
|
||||
@ -1071,14 +1226,20 @@ class Audio(commands.Cog):
|
||||
if not player.current:
|
||||
await player.play()
|
||||
else:
|
||||
try:
|
||||
single_track = tracks[0]
|
||||
if guild_data["maxlength"] > 0:
|
||||
if self._track_limit(ctx, single_track, guild_data["maxlength"]):
|
||||
player.add(ctx.author, single_track)
|
||||
else:
|
||||
return await self._embed_msg(ctx, _("Track exceeds maximum length."))
|
||||
|
||||
else:
|
||||
player.add(ctx.author, single_track)
|
||||
except IndexError:
|
||||
return await self._embed_msg(
|
||||
ctx, _("Nothing found. Check your Lavalink logs for details.")
|
||||
)
|
||||
|
||||
if "localtracks" in single_track.uri:
|
||||
if not single_track.title == "Unknown title":
|
||||
@ -1105,6 +1266,131 @@ class Audio(commands.Cog):
|
||||
if not player.current:
|
||||
await player.play()
|
||||
await ctx.send(embed=embed)
|
||||
if type(query) is list:
|
||||
self._play_lock(ctx, False)
|
||||
|
||||
async def _spotify_playlist(self, ctx, stype, yt_key, query):
|
||||
player = lavalink.get_player(ctx.guild.id)
|
||||
spotify_info = []
|
||||
if stype == "album":
|
||||
r = await self._make_spotify_req("https://api.spotify.com/v1/albums/{0}".format(query))
|
||||
else:
|
||||
r = await self._make_spotify_req(
|
||||
"https://api.spotify.com/v1/playlists/{0}/tracks".format(query)
|
||||
)
|
||||
try:
|
||||
if r["error"]["status"] == 401:
|
||||
return await self._embed_msg(
|
||||
ctx,
|
||||
_(
|
||||
"The Spotify API key or client secret has not been set properly. "
|
||||
"\nUse `{prefix}audioset spotifyapi` for instructions."
|
||||
).format(prefix=ctx.prefix),
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
while True:
|
||||
try:
|
||||
try:
|
||||
spotify_info.extend(r["tracks"]["items"])
|
||||
except KeyError:
|
||||
spotify_info.extend(r["items"])
|
||||
except KeyError:
|
||||
return await self._embed_msg(
|
||||
ctx, _("This doesn't seem to be a valid Spotify URL or code.")
|
||||
)
|
||||
|
||||
try:
|
||||
if r["next"] is not None:
|
||||
r = await self._make_spotify_req(r["next"])
|
||||
continue
|
||||
else:
|
||||
break
|
||||
except KeyError:
|
||||
if r["tracks"]["next"] is not None:
|
||||
r = await self._make_spotify_req(r["tracks"]["next"])
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
embed1 = discord.Embed(
|
||||
colour=await ctx.embed_colour(), title=_("Please wait, adding tracks...")
|
||||
)
|
||||
playlist_msg = await ctx.send(embed=embed1)
|
||||
track_list = []
|
||||
track_count = 0
|
||||
now = int(time.time())
|
||||
for i in spotify_info:
|
||||
if stype == "album":
|
||||
song_info = "{} {}".format(i["name"], i["artists"][0]["name"])
|
||||
else:
|
||||
song_info = "{} {}".format(i["track"]["name"], i["track"]["artists"][0]["name"])
|
||||
try:
|
||||
track_url = await self._youtube_api_search(yt_key, song_info)
|
||||
except:
|
||||
error_embed = discord.Embed(
|
||||
colour=await ctx.embed_colour(),
|
||||
title=_(
|
||||
"The YouTube API key has not been set properly.\n"
|
||||
"Use `{prefix}audioset youtubeapi` for instructions."
|
||||
).format(prefix=ctx.prefix),
|
||||
)
|
||||
await playlist_msg.edit(embed=error_embed)
|
||||
return None
|
||||
# let's complain about errors
|
||||
pass
|
||||
try:
|
||||
yt_track = await player.get_tracks(track_url)
|
||||
except (RuntimeError, aiohttp.client_exceptions.ServerDisconnectedError):
|
||||
return
|
||||
try:
|
||||
track_list.append(yt_track[0])
|
||||
except IndexError:
|
||||
pass
|
||||
track_count += 1
|
||||
if (track_count % 5 == 0) or (track_count == len(spotify_info)):
|
||||
embed2 = discord.Embed(
|
||||
colour=await ctx.embed_colour(),
|
||||
title=_("Loading track {num}/{total}...").format(
|
||||
num=track_count, total=len(spotify_info)
|
||||
),
|
||||
)
|
||||
if track_count == 5:
|
||||
five_time = int(time.time()) - now
|
||||
if track_count >= 5:
|
||||
remain_tracks = len(spotify_info) - track_count
|
||||
time_remain = (remain_tracks / 5) * five_time
|
||||
if track_count < len(spotify_info):
|
||||
seconds = self._dynamic_time(int(time_remain))
|
||||
if track_count == len(spotify_info):
|
||||
seconds = "0s"
|
||||
embed2.set_footer(
|
||||
text=_("Approximate time remaining: {seconds}").format(seconds=seconds)
|
||||
)
|
||||
try:
|
||||
await playlist_msg.edit(embed=embed2)
|
||||
except discord.errors.NotFound:
|
||||
pass
|
||||
|
||||
if len(track_list) == 0:
|
||||
embed3 = discord.Embed(
|
||||
colour=await ctx.embed_colour(),
|
||||
title=_(
|
||||
"Nothing found.\nThe YouTube API key may be invalid "
|
||||
"or you may be rate limited on YouTube's search service.\n"
|
||||
"Check the YouTube API key again and follow the instructions "
|
||||
"at `{prefix}audioset youtubeapi`."
|
||||
).format(prefix=ctx.prefix),
|
||||
)
|
||||
try:
|
||||
return await playlist_msg.edit(embed=embed3)
|
||||
except discord.errors.NotFound:
|
||||
pass
|
||||
try:
|
||||
await playlist_msg.delete()
|
||||
except discord.errors.NotFound:
|
||||
pass
|
||||
return track_list
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@ -1680,21 +1966,42 @@ class Audio(commands.Cog):
|
||||
|
||||
async def _playlist_tracks(self, ctx, player, query):
|
||||
search = False
|
||||
tracklist = []
|
||||
if type(query) is tuple:
|
||||
query = " ".join(query)
|
||||
if not query.startswith("http"):
|
||||
if "open.spotify.com" in query:
|
||||
query = "spotify:{}".format(
|
||||
re.sub("(http[s]?:\/\/)?(open.spotify.com)\/", "", query).replace("/", ":")
|
||||
)
|
||||
if query.startswith("spotify:"):
|
||||
try:
|
||||
if self.play_lock[ctx.message.guild.id]:
|
||||
return await self._embed_msg(
|
||||
ctx, _("Wait until the playlist has finished loading.")
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
tracks = await self._get_spotify_tracks(ctx, query)
|
||||
if not tracks:
|
||||
return await self._embed_msg(ctx, _("Nothing found."))
|
||||
for track in tracks:
|
||||
track_obj = self._track_creator(player, other_track=track)
|
||||
tracklist.append(track_obj)
|
||||
self._play_lock(ctx, False)
|
||||
elif not query.startswith("http"):
|
||||
query = " ".join(query)
|
||||
query = "ytsearch:{}".format(query)
|
||||
search = True
|
||||
tracks = await player.get_tracks(query)
|
||||
if not tracks:
|
||||
return await self._embed_msg(ctx, _("Nothing found."))
|
||||
tracklist = []
|
||||
if not search:
|
||||
else:
|
||||
tracks = await player.get_tracks(query)
|
||||
if not search and len(tracklist) == 0:
|
||||
for track in tracks:
|
||||
track_obj = self._track_creator(player, other_track=track)
|
||||
tracklist.append(track_obj)
|
||||
else:
|
||||
elif len(tracklist) == 0:
|
||||
track_obj = self._track_creator(player, other_track=tracks[0])
|
||||
tracklist.append(track_obj)
|
||||
return tracklist
|
||||
@ -2715,6 +3022,17 @@ class Audio(commands.Cog):
|
||||
|
||||
self._restart_connect()
|
||||
|
||||
async def _check_api_tokens(self):
|
||||
spotify = await self.bot.db.api_tokens.get_raw(
|
||||
"spotify", default={"client_id": "", "client_secret": ""}
|
||||
)
|
||||
youtube = await self.bot.db.api_tokens.get_raw("youtube", default={"api_key": ""})
|
||||
return {
|
||||
"spotify_client_id": spotify["client_id"],
|
||||
"spotify_client_secret": spotify["client_secret"],
|
||||
"youtube_api": youtube["api_key"],
|
||||
}
|
||||
|
||||
async def _check_external(self):
|
||||
external = await self.config.use_external_lavalink()
|
||||
if not external:
|
||||
@ -2881,6 +3199,12 @@ class Audio(commands.Cog):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _play_lock(self, ctx, tf):
|
||||
if tf:
|
||||
self.play_lock[ctx.message.guild.id] = True
|
||||
else:
|
||||
self.play_lock[ctx.message.guild.id] = False
|
||||
|
||||
@staticmethod
|
||||
def _player_check(ctx):
|
||||
try:
|
||||
@ -2972,6 +3296,7 @@ class Audio(commands.Cog):
|
||||
"vimeo.com",
|
||||
"mixer.com",
|
||||
"twitch.tv",
|
||||
"spotify.com",
|
||||
"localtracks",
|
||||
]
|
||||
query_url = urlparse(url)
|
||||
@ -2989,6 +3314,80 @@ class Audio(commands.Cog):
|
||||
else:
|
||||
return False
|
||||
|
||||
async def _youtube_api_search(self, yt_key, query):
|
||||
params = {"q": query, "part": "id", "key": yt_key, "maxResults": 1, "type": "video"}
|
||||
yt_url = "https://www.googleapis.com/youtube/v3/search"
|
||||
async with self.session.request("GET", yt_url, params=params) as r:
|
||||
if r.status == 400:
|
||||
return None
|
||||
else:
|
||||
search_response = await r.json()
|
||||
for search_result in search_response.get("items", []):
|
||||
if search_result["id"]["kind"] == "youtube#video":
|
||||
return "https://www.youtube.com/watch?v={}".format(search_result["id"]["videoId"])
|
||||
|
||||
# Spotify-related methods below are originally from: https://github.com/Just-Some-Bots/MusicBot/blob/master/musicbot/spotify.py
|
||||
|
||||
async def _check_token(self, token):
|
||||
now = int(time.time())
|
||||
return token["expires_at"] - now < 60
|
||||
|
||||
async def _get_spotify_token(self):
|
||||
if self.spotify_token and not await self._check_token(self.spotify_token):
|
||||
return self.spotify_token["access_token"]
|
||||
token = await self._request_token()
|
||||
if token is None:
|
||||
log.debug("Requested a token from Spotify, did not end up getting one.")
|
||||
try:
|
||||
token["expires_at"] = int(time.time()) + token["expires_in"]
|
||||
except KeyError:
|
||||
return
|
||||
self.spotify_token = token
|
||||
log.debug("Created a new access token for Spotify: {0}".format(token))
|
||||
return self.spotify_token["access_token"]
|
||||
|
||||
async def _make_get(self, url, headers=None):
|
||||
async with self.session.request("GET", url, headers=headers) as r:
|
||||
if r.status != 200:
|
||||
log.debug(
|
||||
"Issue making GET request to {0}: [{1.status}] {2}".format(
|
||||
url, r, await r.json()
|
||||
)
|
||||
)
|
||||
return await r.json()
|
||||
|
||||
async def _make_post(self, url, payload, headers=None):
|
||||
async with self.session.post(url, data=payload, headers=headers) as r:
|
||||
if r.status != 200:
|
||||
log.debug(
|
||||
"Issue making POST request to {0}: [{1.status}] {2}".format(
|
||||
url, r, await r.json()
|
||||
)
|
||||
)
|
||||
return await r.json()
|
||||
|
||||
async def _make_spotify_req(self, url):
|
||||
token = await self._get_spotify_token()
|
||||
return await self._make_get(url, headers={"Authorization": "Bearer {0}".format(token)})
|
||||
|
||||
def _make_token_auth(self, client_id, client_secret):
|
||||
auth_header = base64.b64encode((client_id + ":" + client_secret).encode("ascii"))
|
||||
return {"Authorization": "Basic %s" % auth_header.decode("ascii")}
|
||||
|
||||
async def _request_token(self):
|
||||
self.client_id = await self.bot.db.api_tokens.get_raw("spotify", default={"client_id": ""})
|
||||
self.client_secret = await self.bot.db.api_tokens.get_raw(
|
||||
"spotify", default={"client_secret": ""}
|
||||
)
|
||||
payload = {"grant_type": "client_credentials"}
|
||||
headers = self._make_token_auth(
|
||||
self.client_id["client_id"], self.client_secret["client_secret"]
|
||||
)
|
||||
r = await self._make_post(
|
||||
"https://accounts.spotify.com/api/token", payload=payload, headers=headers
|
||||
)
|
||||
return r
|
||||
|
||||
async def on_voice_state_update(self, member, before, after):
|
||||
if after.channel != before.channel:
|
||||
try:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user