Audio Fixes (#4492)

* handles #4491

* add typing indicators to audio playlists commands like discussed with aika.

* recheck perms upon change of token to avoid needing a reload.

* Ensure the player lock is always released... on rewrite to this as a callback to the task.

* ffs

* resolves#4495

* missed one

* aaaaaaaaa

* fix https://canary.discord.com/channels/133049272517001216/387398816317440000/766711707921678396

* some tweaks

* Clear errors to users around YouTube Quota
This commit is contained in:
Draper 2020-10-20 17:57:02 +01:00 committed by GitHub
parent 335e2a7c25
commit e31196d19f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1255 additions and 1014 deletions

View File

@ -3,18 +3,21 @@ import json
import logging
from collections import namedtuple
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, MutableMapping, Optional, Union
import discord
import lavalink
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_list
from ..errors import InvalidPlaylistScope, MissingAuthor, MissingGuild
from ..utils import PlaylistScope
log = logging.getLogger("red.cogs.Audio.api.utils")
_ = Translator("Audio", Path(__file__))
@dataclass

View File

@ -4,6 +4,7 @@ import json
import logging
from copy import copy
from pathlib import Path
from typing import TYPE_CHECKING, Mapping, Optional, Union
import aiohttp
@ -12,6 +13,7 @@ from lavalink.rest_api import LoadResult
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.i18n import Translator
from ..audio_dataclasses import Query
from ..audio_logging import IS_DEBUG, debug_exc_log
@ -20,7 +22,7 @@ if TYPE_CHECKING:
from .. import Audio
_API_URL = "https://api.redbot.app/"
_ = Translator("Audio", Path(__file__))
log = logging.getLogger("red.cogs.Audio.api.GlobalDB")
@ -38,8 +40,9 @@ class GlobalCacheWrapper:
self._token: Mapping[str, str] = {}
self.cog = cog
def update_token(self, new_token: Mapping[str, str]):
async def update_token(self, new_token: Mapping[str, str]):
self._token = new_token
await self.get_perms()
async def _get_api_key(
self,
@ -165,7 +168,6 @@ class GlobalCacheWrapper:
global_api_user = copy(self.cog.global_api_user)
await self._get_api_key()
is_enabled = await self.config.global_db_enabled()
await self._get_api_key()
if (not is_enabled) or self.api_key is None:
return global_api_user
with contextlib.suppress(Exception):

View File

@ -7,6 +7,7 @@ import random
import time
from collections import namedtuple
from pathlib import Path
from typing import TYPE_CHECKING, Callable, List, MutableMapping, Optional, Tuple, Union, cast
import aiohttp
@ -23,7 +24,7 @@ from redbot.core.utils.dbtools import APSWConnectionWrapper
from ..audio_dataclasses import Query
from ..audio_logging import IS_DEBUG, debug_exc_log
from ..errors import DatabaseError, SpotifyFetchError, TrackEnqueueError
from ..errors import DatabaseError, SpotifyFetchError, TrackEnqueueError, YouTubeApiError
from ..utils import CacheLevel, Notifier
from .api_utils import LavalinkCacheFetchForGlobalResult
from .global_db import GlobalCacheWrapper
@ -37,7 +38,7 @@ from .youtube import YouTubeWrapper
if TYPE_CHECKING:
from .. import Audio
_ = Translator("Audio", __file__)
_ = Translator("Audio", Path(__file__))
log = logging.getLogger("red.cogs.Audio.api.AudioAPIInterface")
_TOP_100_US = "https://www.youtube.com/playlist?list=PL4fGSI1pDJn5rWitrRWFKdm-ulaFiIyoK"
# TODO: Get random from global Cache
@ -209,6 +210,7 @@ class AudioAPIInterface:
track_count = 0
time_now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
youtube_cache = CacheLevel.set_youtube().is_subset(current_cache_level)
youtube_api_error = None
async for track in AsyncIter(tracks):
if isinstance(track, str):
break
@ -248,9 +250,13 @@ class AudioAPIInterface:
debug_exc_log(log, exc, f"Failed to fetch {track_info} from YouTube table")
if val is None:
try:
val = await self.fetch_youtube_query(
ctx, track_info, current_cache_level=current_cache_level
)
except YouTubeApiError as err:
val = None
youtube_api_error = err.message
if youtube_cache and val:
task = ("update", ("youtube", {"track": track_info}))
self.append_task(ctx, *task)
@ -261,6 +267,13 @@ class AudioAPIInterface:
track_count += 1
if notifier is not None and ((track_count % 2 == 0) or (track_count == total_tracks)):
await notifier.notify_user(current=track_count, total=total_tracks, key="youtube")
if notifier is not None and youtube_api_error:
error_embed = discord.Embed(
colour=await ctx.embed_colour(),
title=_("Failing to get tracks, skipping remaining."),
)
await notifier.update_embed(error_embed)
break
if CacheLevel.set_spotify().is_subset(current_cache_level):
task = ("insert", ("spotify", database_entries))
self.append_task(ctx, *task)
@ -438,6 +451,7 @@ class AudioAPIInterface:
global_entry = globaldb_toggle and query_global
track_list: List = []
has_not_allowed = False
youtube_api_error = None
try:
current_cache_level = CacheLevel(await self.config.cache_level())
guild_data = await self.config.guild(ctx.guild).all()
@ -461,7 +475,6 @@ class AudioAPIInterface:
return track_list
database_entries = []
time_now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
youtube_cache = CacheLevel.set_youtube().is_subset(current_cache_level)
spotify_cache = CacheLevel.set_spotify().is_subset(current_cache_level)
async for track_count, track in AsyncIter(tracks_from_spotify).enumerate(start=1):
@ -506,9 +519,14 @@ class AudioAPIInterface:
llresponse = LoadResult(llresponse)
val = llresponse or None
if val is None:
try:
val = await self.fetch_youtube_query(
ctx, track_info, current_cache_level=current_cache_level
)
except YouTubeApiError as err:
val = None
youtube_api_error = err.message
if not youtube_api_error:
if youtube_cache and val and llresponse is None:
task = ("update", ("youtube", {"track": track_info}))
self.append_task(ctx, *task)
@ -537,7 +555,9 @@ class AudioAPIInterface:
lock(ctx, False)
error_embed = discord.Embed(
colour=await ctx.embed_colour(),
title=_("The connection was reset while loading the playlist."),
title=_(
"The connection was reset while loading the playlist."
),
)
if notifier is not None:
await notifier.update_embed(error_embed)
@ -554,6 +574,8 @@ class AudioAPIInterface:
track_object = result.tracks
else:
track_object = []
else:
track_object = []
if (track_count % 2 == 0) or (track_count == total_tracks):
key = "lavalink"
seconds = "???"
@ -567,13 +589,16 @@ class AudioAPIInterface:
seconds=seconds,
)
if consecutive_fails >= (100 if global_entry else 10):
if youtube_api_error or consecutive_fails >= (20 if global_entry else 10):
error_embed = discord.Embed(
colour=await ctx.embed_colour(),
title=_("Failing to get tracks, skipping remaining."),
)
if notifier is not None:
await notifier.update_embed(error_embed)
if youtube_api_error:
lock(ctx, False)
raise SpotifyFetchError(message=youtube_api_error)
break
if not track_object:
consecutive_fails += 1
@ -631,16 +656,6 @@ class AudioAPIInterface:
if not player.current:
await player.play()
if not track_list and not has_not_allowed:
raise SpotifyFetchError(
message=_(
"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`."
)
)
player.maybe_shuffle()
if enqueue and tracks_from_spotify:
if total_tracks > enqueued_tracks:
maxlength_msg = _(" {bad_tracks} tracks cannot be queued.").format(
@ -667,6 +682,16 @@ class AudioAPIInterface:
if notifier is not None:
await notifier.update_embed(embed)
lock(ctx, False)
if not track_list and not has_not_allowed:
raise SpotifyFetchError(
message=_(
"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`."
)
)
player.maybe_shuffle()
if spotify_cache:
task = ("insert", ("spotify", database_entries))
@ -718,9 +743,12 @@ class AudioAPIInterface:
except Exception as exc:
debug_exc_log(log, exc, f"Failed to fetch {track_info} from YouTube table")
if val is None:
try:
youtube_url = await self.fetch_youtube_query(
ctx, track_info, current_cache_level=current_cache_level
)
except YouTubeApiError as err:
youtube_url = None
else:
if cache_enabled:
task = ("update", ("youtube", {"track": track_info}))

View File

@ -4,6 +4,7 @@ import datetime
import logging
import random
import time
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, Callable, List, MutableMapping, Optional, Tuple, Union
@ -11,6 +12,7 @@ from typing import TYPE_CHECKING, Callable, List, MutableMapping, Optional, Tupl
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.dbtools import APSWConnectionWrapper
@ -59,7 +61,7 @@ if TYPE_CHECKING:
log = logging.getLogger("red.cogs.Audio.api.LocalDB")
_ = Translator("Audio", Path(__file__))
_SCHEMA_VERSION = 3

View File

@ -2,6 +2,7 @@ import concurrent
import json
import logging
import time
from pathlib import Path
from types import SimpleNamespace
from typing import TYPE_CHECKING, List, Union
@ -11,6 +12,7 @@ import lavalink
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.dbtools import APSWConnectionWrapper
@ -33,6 +35,7 @@ from ..sql_statements import (
from .api_utils import QueueFetchResult
log = logging.getLogger("red.cogs.Audio.api.PersistQueueWrapper")
_ = Translator("Audio", Path(__file__))
if TYPE_CHECKING:
from .. import Audio

View File

@ -1,4 +1,5 @@
import logging
from pathlib import Path
from typing import List, MutableMapping, Optional, Union
@ -7,6 +8,7 @@ import lavalink
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from ..errors import NotAllowed
@ -15,6 +17,7 @@ from .api_utils import PlaylistFetchResult, prepare_config_scope, standardize_sc
from .playlist_wrapper import PlaylistWrapper
log = logging.getLogger("red.cogs.Audio.api.PlaylistsInterface")
_ = Translator("Audio", Path(__file__))
class Playlist:

View File

@ -1,12 +1,14 @@
import concurrent
import json
import logging
from pathlib import Path
from types import SimpleNamespace
from typing import List, MutableMapping, Optional
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.dbtools import APSWConnectionWrapper
@ -33,6 +35,7 @@ from ..utils import PlaylistScope
from .api_utils import PlaylistFetchResult
log = logging.getLogger("red.cogs.Audio.api.Playlists")
_ = Translator("Audio", Path(__file__))
class PlaylistWrapper:

View File

@ -3,6 +3,7 @@ import contextlib
import json
import logging
import time
from pathlib import Path
from typing import TYPE_CHECKING, List, Mapping, MutableMapping, Optional, Tuple, Union
@ -19,7 +20,7 @@ from ..errors import SpotifyFetchError
if TYPE_CHECKING:
from .. import Audio
_ = Translator("Audio", __file__)
_ = Translator("Audio", Path(__file__))
log = logging.getLogger("red.cogs.Audio.api.Spotify")
@ -104,7 +105,7 @@ class SpotifyWrapper:
log.debug(f"Issue making GET request to {url}: [{r.status}] {data}")
return data
def update_token(self, new_token: Mapping[str, str]):
async def update_token(self, new_token: Mapping[str, str]):
self._token = new_token
async def get_token(self) -> None:

View File

@ -1,5 +1,6 @@
import json
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Mapping, Optional, Union
@ -8,6 +9,7 @@ import aiohttp
from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.i18n import Translator
from ..errors import YouTubeApiError
@ -15,7 +17,7 @@ if TYPE_CHECKING:
from .. import Audio
log = logging.getLogger("red.cogs.Audio.api.YouTube")
_ = Translator("Audio", Path(__file__))
SEARCH_ENDPOINT = "https://www.googleapis.com/youtube/v3/search"
@ -32,7 +34,7 @@ class YouTubeWrapper:
self._token: Mapping[str, str] = {}
self.cog = cog
def update_token(self, new_token: Mapping[str, str]):
async def update_token(self, new_token: Mapping[str, str]):
self._token = new_token
async def _get_api_key(
@ -54,11 +56,28 @@ class YouTubeWrapper:
"type": "video",
}
async with self.session.request("GET", SEARCH_ENDPOINT, params=params) as r:
if r.status in [400, 404]:
if r.status == 400:
if r.reason == "Bad Request":
raise YouTubeApiError(
_(
"Your YouTube Data API token is invalid.\n"
"Check the YouTube API key again and follow the instructions "
"at `{prefix}audioset youtubeapi`."
)
)
return None
elif r.status in [403, 429]:
if r.reason == "quotaExceeded":
raise YouTubeApiError("Your YouTube Data API quota has been reached.")
elif r.status == 404:
return None
elif r.status == 403:
if r.reason in ["Forbidden", "quotaExceeded"]:
raise YouTubeApiError(
_(
"YouTube API error code: 403\nYour YouTube API key may have "
"reached the account's query limit for today. Please check "
"<https://developers.google.com/youtube/v3/getting-started#quota> "
"for more information."
)
)
return None
else:
search_response = await r.json(loads=json.loads)

View File

@ -22,8 +22,11 @@ from urllib.parse import urlparse
import lavalink
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
_ = Translator("Audio", Path(__file__))
_RE_REMOVE_START: Final[Pattern] = re.compile(r"^(sc|list) ")
_RE_YOUTUBE_TIMESTAMP: Final[Pattern] = re.compile(r"[&|?]t=(\d+)s?")
_RE_YOUTUBE_INDEX: Final[Pattern] = re.compile(r"&index=(\d+)")

View File

@ -1,6 +1,7 @@
import argparse
import functools
import re
from pathlib import Path
from typing import Final, MutableMapping, Optional, Pattern, Tuple, Union
@ -16,7 +17,7 @@ from .apis.playlist_interface import get_all_playlist_converter
from .errors import NoMatchesFound, TooManyMatches
from .utils import PlaylistScope
_ = Translator("Audio", __file__)
_ = Translator("Audio", Path(__file__))
__all__ = [
"ComplexScopeParser",

View File

@ -2,6 +2,7 @@ import asyncio
import json
from collections import Counter
from pathlib import Path
from typing import Mapping
import aiohttp
@ -11,11 +12,13 @@ from redbot.core import Config
from redbot.core.bot import Red
from redbot.core.commands import Cog
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import cog_i18n
from redbot.core.i18n import Translator, cog_i18n
from ..utils import PlaylistScope
from . import abc, cog_utils, commands, events, tasks, utilities
from .cog_utils import CompositeMetaClass, _
from .cog_utils import CompositeMetaClass
_ = Translator("Audio", Path(__file__))
@cog_i18n(_)

View File

@ -1,10 +1,8 @@
from abc import ABC
from pathlib import Path
from typing import Final
from redbot import VersionInfo
from redbot.core import commands
from redbot.core.i18n import Translator
from ..converters import get_lazy_converter, get_playlist_converter
@ -12,7 +10,6 @@ __version__ = VersionInfo.from_json({"major": 2, "minor": 3, "micro": 0, "releas
__author__ = ["aikaterna", "Draper"]
_ = Translator("Audio", Path(__file__).parent)
_SCHEMA_VERSION: Final[int] = 3
_OWNER_NOTIFICATION: Final[int] = 1

View File

@ -1,6 +1,7 @@
import asyncio
import contextlib
import logging
from pathlib import Path
from typing import Union
@ -9,6 +10,7 @@ import lavalink
from redbot.core import bank, commands
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import box, humanize_number
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu, start_adding_reactions
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
@ -18,10 +20,12 @@ from ...converters import ScopeParser
from ...errors import MissingGuild, TooManyMatches
from ...utils import CacheLevel, PlaylistScope
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, PlaylistConverter, _, __version__
from ..cog_utils import CompositeMetaClass, PlaylistConverter, __version__
log = logging.getLogger("red.cogs.Audio.cog.Commands.audioset")
_ = Translator("Audio", Path(__file__))
class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
@commands.group(name="audioset")
@ -1378,7 +1382,7 @@ class AudioSetCommands(MixinMeta, metaclass=CompositeMetaClass):
async def command_audioset_audiodb_toggle(self, ctx: commands.Context):
"""Toggle the server settings.
Default is ON
Default is OFF
"""
state = await self.config.global_db_enabled()
await self.config.global_db_enabled.set(not state)

View File

@ -3,6 +3,7 @@ import contextlib
import datetime
import logging
import time
from pathlib import Path
from typing import Optional, Union
@ -10,15 +11,17 @@ import discord
import lavalink
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
from redbot.core.utils.menus import start_adding_reactions
from redbot.core.utils.predicates import ReactionPredicate
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.player_controller")
_ = Translator("Audio", Path(__file__))
class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
@ -98,9 +101,8 @@ class PlayerControllerCommands(MixinMeta, metaclass=CompositeMetaClass):
await self.get_track_description(player.current, self.local_folder_current_path)
or ""
)
song += _("\n Requested by: **{track.requester}**")
song += "\n\n{arrow}`{pos}`/`{dur}`"
song = song.format(track=player.current, arrow=arrow, pos=pos, dur=dur)
song += _("\n Requested by: **{track.requester}**").format(track=player.current)
song += "\n\n{arrow}`{pos}`/`{dur}`".format(arrow=arrow, pos=pos, dur=dur)
else:
song = _("Nothing.")

View File

@ -2,20 +2,23 @@ import asyncio
import contextlib
import logging
import re
from pathlib import Path
import discord
import lavalink
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import box, humanize_number, pagify
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu, start_adding_reactions
from redbot.core.utils.predicates import MessagePredicate, ReactionPredicate
from ...equalizer import Equalizer
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.equalizer")
_ = Translator("Audio", Path(__file__))
class EqualizerCommands(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -4,11 +4,13 @@ from pathlib import Path
import discord
from redbot.core import commands
from redbot.core.i18n import Translator
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.lavalink_setup")
_ = Translator("Audio", Path(__file__))
class LavalinkSetupCommands(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -7,13 +7,15 @@ from typing import MutableMapping
import discord
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils.menus import DEFAULT_CONTROLS, close_menu, menu, next_page, prev_page
from ...audio_dataclasses import LocalPath, Query
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.local_track")
_ = Translator("Audio", Path(__file__))
class LocalTrackCommands(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -3,19 +3,22 @@ import heapq
import logging
import math
import random
from pathlib import Path
import discord
import lavalink
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number, pagify
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.miscellaneous")
_ = Translator("Audio", Path(__file__))
class MiscellaneousCommands(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -3,6 +3,7 @@ import datetime
import logging
import math
import time
from pathlib import Path
from typing import MutableMapping
@ -12,6 +13,7 @@ import lavalink
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.commands import UserInputOptional
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.menus import DEFAULT_CONTROLS, close_menu, menu, next_page, prev_page
@ -24,9 +26,10 @@ from ...errors import (
TrackEnqueueError,
)
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.player")
_ = Translator("Audio", Path(__file__))
class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
@ -125,6 +128,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx, title=_("Unable To Play Tracks"), description=err.message
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
@commands.command(name="bumpplay")
@commands.guild_only()
@ -230,6 +236,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx, title=_("Unable To Play Tracks"), description=err.message
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
if isinstance(tracks, discord.Message):
return
elif not tracks:
@ -248,7 +257,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
title = _("Track is not playable.")
embed = discord.Embed(title=title)
embed.description = _(
"**{suffix}** is not a fully supported format and some " "tracks may not play."
"**{suffix}** is not a fully supported format and some tracks may not play."
).format(suffix=query.suffix)
return await self.send_embed_msg(ctx, embed=embed)
queue_dur = await self.track_remaining_duration(ctx)
@ -611,6 +620,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
"minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
if not guild_data["auto_play"]:
await ctx.invoke(self.command_audioset_autoplay_toggle)
@ -745,6 +757,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
"try again in a few minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
tracks = result.tracks
else:
@ -761,6 +776,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
"try again in a few minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
if not tracks:
embed = discord.Embed(title=_("Nothing found."))
if await self.config.use_external_lavalink() and query.is_local:
@ -877,6 +895,9 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass):
"try again in a few minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
tracks = result.tracks
if not tracks:
embed = discord.Embed(title=_("Nothing found."))

View File

@ -7,6 +7,7 @@ import tarfile
import time
from io import BytesIO
from pathlib import Path
from typing import cast
import discord
@ -15,6 +16,7 @@ import lavalink
from redbot.core import commands
from redbot.core.commands import UserInputOptional
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import bold, pagify
from redbot.core.utils.menus import DEFAULT_CONTROLS, menu
@ -28,9 +30,10 @@ from ...converters import ComplexScopeParser, ScopeParser
from ...errors import MissingGuild, TooManyMatches, TrackEnqueueError
from ...utils import PlaylistScope
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, LazyGreedyConverter, PlaylistConverter, _
from ..cog_utils import CompositeMetaClass, LazyGreedyConverter, PlaylistConverter
log = logging.getLogger("red.cogs.Audio.cog.Commands.playlist")
_ = Translator("Audio", Path(__file__))
class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
@ -107,6 +110,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
(scope, author, guild, specified_user) = scope_data
if not await self._playlist_check(ctx):
return
async with ctx.typing():
try:
(playlist, playlist_arg, scope) = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -117,7 +121,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist").format(
arg=playlist_arg
),
)
if not await self.can_manage_playlist(scope, playlist, ctx, author, guild):
return
@ -156,7 +162,10 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
description=_(
"{track} is already in {playlist} (`{id}`) [**{scope}**]."
).format(
track=to.title, playlist=playlist.name, id=playlist.id, scope=scope_name
track=to.title,
playlist=playlist.name,
id=playlist.id,
scope=scope_name,
),
footer=_("Playlist limit reached: Could not add track.").format(not_added)
if not_added > 0
@ -192,9 +201,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
)
if to_append_count > appended:
diff = to_append_count - appended
desc += _("\n{existing} {plural} already in the playlist and were skipped.").format(
existing=diff, plural=_("tracks are") if diff != 1 else _("track is")
)
desc += _(
"\n{existing} {plural} already in the playlist and were skipped."
).format(existing=diff, plural=_("tracks are") if diff != 1 else _("track is"))
embed = discord.Embed(title=_("Playlist Modified"), description=desc)
await self.send_embed_msg(
@ -282,6 +291,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
specified_to_user,
) = scope_data
to_scope = to_scope or PlaylistScope.GUILD.value
async with ctx.typing():
try:
from_playlist, playlist_arg, from_scope = await self.get_playlist_match(
ctx, playlist_matches, from_scope, from_author, from_guild, specified_from_user
@ -295,11 +305,15 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
temp_playlist = cast(Playlist, FakePlaylist(to_author.id, to_scope))
if not await self.can_manage_playlist(to_scope, temp_playlist, ctx, to_author, to_guild):
if not await self.can_manage_playlist(
to_scope, temp_playlist, ctx, to_author, to_guild
):
ctx.command.reset_cooldown(ctx)
return
@ -392,6 +406,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
scope_name = self.humanize_scope(
scope, ctx=guild if scope == PlaylistScope.GUILD.value else author
)
async with ctx.typing():
if not await self.can_manage_playlist(scope, temp_playlist, ctx, author, guild):
return
playlist_name = playlist_name.split(" ")[0].strip('"')[:32]
@ -465,7 +480,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -476,7 +491,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
if not await self.can_manage_playlist(scope, playlist, ctx, author, guild):
return
@ -688,7 +705,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -701,7 +718,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
schema = 2
@ -746,7 +765,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
with tarfile.open(str(temp_tar), "w:gz") as tar:
tar.add(
str(temp_file), arcname=str(temp_file.relative_to(datapath)), recursive=False
str(temp_file),
arcname=str(temp_file.relative_to(datapath)),
recursive=False,
)
try:
if os.path.getsize(str(temp_tar)) > ctx.guild.filesize_limit - 10000:
@ -817,6 +838,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -833,7 +855,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
track_len = len(playlist.tracks)
@ -841,7 +865,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if track_len > 0:
spaces = "\N{EN SPACE}" * (len(str(len(playlist.tracks))) + 2)
async for track_idx, track in AsyncIter(playlist.tracks).enumerate(start=1):
query = Query.process_input(track["info"]["uri"], self.local_folder_current_path)
query = Query.process_input(
track["info"]["uri"], self.local_folder_current_path
)
if query.is_local:
if track["info"]["title"] != "Unknown title":
msg += "`{}.` **{} - {}**\n{}{}\n".format(
@ -862,9 +888,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
msg = "No tracks."
if not playlist.url:
embed_title = _("Playlist info for {playlist_name} (`{id}`) [**{scope}**]:\n").format(
playlist_name=playlist.name, id=playlist.id, scope=scope_name
)
embed_title = _(
"Playlist info for {playlist_name} (`{id}`) [**{scope}**]:\n"
).format(playlist_name=playlist.name, id=playlist.id, scope=scope_name)
else:
embed_title = _(
"Playlist info for {playlist_name} (`{id}`) [**{scope}**]:\nURL: {url}"
@ -935,7 +961,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
if scope is None:
global_matches = await get_all_playlist(
@ -1003,9 +1029,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("No saved playlists for {scope} created by {author}.").format(
scope=name, author=author
),
description=_(
"No saved playlists for {scope} created by {author}."
).format(scope=name, author=author),
)
elif not playlists:
ctx.command.reset_cooldown(ctx)
@ -1029,7 +1055,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
or playlist.author
or _("Unknown")
),
_("Scope: {scope}\n").format(scope=self.humanize_scope(playlist.scope)),
_("Scope: {scope}\n").format(
scope=self.humanize_scope(playlist.scope)
),
)
)
)
@ -1199,6 +1227,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -1212,7 +1241,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
if not await self.can_manage_playlist(scope, playlist, ctx, author, guild):
return
@ -1231,7 +1262,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
guild=guild,
author=playlist.author,
)
return await self.send_embed_msg(ctx, title=_("No tracks left, removing playlist."))
return await self.send_embed_msg(
ctx, title=_("No tracks left, removing playlist.")
)
update = {"tracks": clean_list, "url": None}
await playlist.edit(update)
if del_count > 1:
@ -1242,7 +1275,10 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
"{num} entries have been removed "
"from the playlist {playlist_name} (`{id}`) [**{scope}**]."
).format(
num=del_count, playlist_name=playlist.name, id=playlist.id, scope=scope_name
num=del_count,
playlist_name=playlist.name,
id=playlist.id,
scope=scope_name,
),
)
else:
@ -1313,6 +1349,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
scope_name = self.humanize_scope(
scope, ctx=guild if scope == PlaylistScope.GUILD.value else author
)
async with ctx.typing():
temp_playlist = cast(Playlist, FakePlaylist(author.id, scope))
if not await self.can_manage_playlist(scope, temp_playlist, ctx, author, guild):
return ctx.command.reset_cooldown(ctx)
@ -1360,9 +1397,14 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
description=_(
"Playlist {name} (`{id}`) [**{scope}**] saved: {num} tracks added."
).format(
name=playlist.name, num=len(tracklist), id=playlist.id, scope=scope_name
name=playlist.name,
num=len(tracklist),
id=playlist.id,
scope=scope_name,
),
footer=_("Playlist limit reached: Could not add {} tracks.").format(not_added)
footer=_("Playlist limit reached: Could not add {} tracks.").format(
not_added
)
if not_added > 0
else None,
)
@ -1440,7 +1482,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
description=_("You need the DJ role to start playing playlists."),
)
return False
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -1453,7 +1495,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist").format(
arg=playlist_arg
),
)
if not await self._playlist_check(ctx):
@ -1608,6 +1652,8 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
embeds = None
async with ctx.typing():
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -1621,7 +1667,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
if not await self._playlist_check(ctx):
@ -1632,7 +1680,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return
if playlist.url:
player = lavalink.get_player(ctx.guild.id)
added, removed, playlist = await self._maybe_update_playlist(ctx, player, playlist)
added, removed, playlist = await self._maybe_update_playlist(
ctx, player, playlist
)
else:
ctx.command.reset_cooldown(ctx)
return await self.send_embed_msg(
@ -1679,7 +1729,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if i % 10 == 0 or i == total_removed:
page_count += 1
embed = discord.Embed(
title=_("Tracks removed"), colour=_colour, description=removed_text
title=_("Tracks removed"),
colour=_colour,
description=removed_text,
)
text = _("Page {page_num}/{total_pages}").format(
page_num=page_count, total_pages=total_pages
@ -1708,7 +1760,6 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
added_embeds.append(embed)
added_text = ""
embeds = removed_embeds + added_embeds
await menu(ctx, embeds, DEFAULT_CONTROLS)
else:
return await self.send_embed_msg(
ctx,
@ -1717,6 +1768,8 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
id=playlist.id, name=playlist.name, scope=scope_name
),
)
if embeds:
await menu(ctx, embeds, DEFAULT_CONTROLS)
@command_playlist.command(name="upload", usage="[args]")
@commands.is_owner()
@ -1770,9 +1823,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
scope, author, guild, specified_user = scope_data
scope = scope or PlaylistScope.GUILD.value
temp_playlist = cast(Playlist, FakePlaylist(author.id, scope))
async with ctx.typing():
if not await self.can_manage_playlist(scope, temp_playlist, ctx, author, guild):
return
if not await self._playlist_check(ctx):
return
player = lavalink.get_player(ctx.guild.id)
@ -1834,7 +1887,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
await self.api_interface.fetch_track(
ctx,
player,
Query.process_input(uploaded_playlist_url, self.local_folder_current_path),
Query.process_input(
uploaded_playlist_url, self.local_folder_current_path
),
)
)[0].tracks
):
@ -1874,6 +1929,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
"minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
@commands.cooldown(1, 60, commands.BucketType.member)
@command_playlist.command(
@ -1929,7 +1987,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
if scope_data is None:
scope_data = [None, ctx.author, ctx.guild, False]
scope, author, guild, specified_user = scope_data
async with ctx.typing():
new_name = new_name.split(" ")[0].strip('"')[:32]
if new_name.isnumeric():
ctx.command.reset_cooldown(ctx)
@ -1941,7 +1999,6 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
"characters) and not numbers only."
),
)
try:
playlist, playlist_arg, scope = await self.get_playlist_match(
ctx, playlist_matches, scope, author, guild, specified_user
@ -1954,7 +2011,9 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx,
title=_("Playlist Not Found"),
description=_("Could not match '{arg}' to a playlist.").format(arg=playlist_arg),
description=_("Could not match '{arg}' to a playlist.").format(
arg=playlist_arg
),
)
if not await self.can_manage_playlist(scope, playlist, ctx, author, guild):
ctx.command.reset_cooldown(ctx)

View File

@ -3,6 +3,7 @@ import contextlib
import datetime
import logging
import math
from pathlib import Path
from typing import MutableMapping, Optional
@ -10,6 +11,7 @@ import discord
import lavalink
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.menus import (
DEFAULT_CONTROLS,
@ -22,9 +24,10 @@ from redbot.core.utils.menus import (
from redbot.core.utils.predicates import ReactionPredicate
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Commands.queue")
_ = Translator("Audio", Path(__file__))
class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
@ -71,9 +74,8 @@ class QueueCommands(MixinMeta, metaclass=CompositeMetaClass):
await self.get_track_description(player.current, self.local_folder_current_path)
or ""
)
song += _("\n Requested by: **{track.requester}**")
song += "\n\n{arrow}`{pos}`/`{dur}`"
song = song.format(track=player.current, arrow=arrow, pos=pos, dur=dur)
song += _("\n Requested by: **{track.requester}**").format(track=player.current)
song += f"\n\n{arrow}`{pos}`/`{dur}`"
embed = discord.Embed(title=_("Now Playing"), description=song)
guild_data = await self.config.guild(ctx.guild).all()
if guild_data["thumbnail"] and player.current and player.current.thumbnail:

View File

@ -2,6 +2,7 @@ import asyncio
import datetime
import logging
import time
from pathlib import Path
from typing import Optional
@ -9,6 +10,7 @@ import discord
import lavalink
from redbot.core import commands
from redbot.core.i18n import Translator
from ...apis.playlist_interface import Playlist, delete_playlist, get_playlist
from ...audio_logging import debug_exc_log
@ -17,6 +19,7 @@ from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Events.audio")
_ = Translator("Audio", Path(__file__))
class AudioEvents(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -13,15 +13,16 @@ import lavalink
from aiohttp import ClientConnectorError
from discord.ext.commands import CheckFailure
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import box, humanize_list
from ...audio_logging import debug_exc_log
from ...errors import TrackEnqueueError
from ..abc import MixinMeta
from ..cog_utils import HUMANIZED_PERM, CompositeMetaClass, _
from ..cog_utils import HUMANIZED_PERM, CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Events.dpy")
_ = Translator("Audio", Path(__file__))
RE_CONVERSION: Final[Pattern] = re.compile('Converting to "(.*)" failed for parameter "(.*)".')

View File

@ -1,15 +1,18 @@
import asyncio
import contextlib
import logging
from pathlib import Path
import discord
import lavalink
from redbot.core.i18n import Translator
from ...errors import DatabaseError, TrackEnqueueError
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Events.lavalink")
_ = Translator("Audio", Path(__file__))
class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
@ -174,6 +177,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
player.current = None
if not guild_id:
return
guild_id = int(guild_id)
self._error_counter.setdefault(guild_id, 0)
if guild_id not in self._error_counter:
self._error_counter[guild_id] = 0

View File

@ -1,12 +1,15 @@
import asyncio
import logging
from pathlib import Path
from typing import Literal, Mapping
from redbot.core import commands
from redbot.core.i18n import Translator
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Events.red")
_ = Translator("Audio", Path(__file__))
class RedEvents(MixinMeta, metaclass=CompositeMetaClass):
@ -15,11 +18,11 @@ class RedEvents(MixinMeta, metaclass=CompositeMetaClass):
self, service_name: str, api_tokens: Mapping[str, str]
) -> None:
if service_name == "youtube":
self.api_interface.youtube_api.update_token(api_tokens)
await self.api_interface.youtube_api.update_token(api_tokens)
elif service_name == "spotify":
self.api_interface.spotify_api.update_token(api_tokens)
await self.api_interface.spotify_api.update_token(api_tokens)
elif service_name == "audiodb":
self.api_interface.global_cache_api.update_token(api_tokens)
await self.api_interface.global_cache_api.update_token(api_tokens)
async def red_delete_data_for_user(
self,

View File

@ -1,14 +1,17 @@
import asyncio
import logging
from pathlib import Path
import lavalink
from redbot.core.i18n import Translator
from ...errors import LavalinkDownloadFailed
from ...manager import ServerManager
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.lavalink")
_ = Translator("Audio", Path(__file__))
class LavalinkTasks(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -1,11 +1,13 @@
import asyncio
import logging
import time
from pathlib import Path
from typing import Dict
import lavalink
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from ...audio_logging import debug_exc_log
@ -13,6 +15,7 @@ from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.player")
_ = Translator("Audio", Path(__file__))
class PlayerTasks(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -2,12 +2,14 @@ import asyncio
import datetime
import itertools
import logging
from pathlib import Path
from typing import Optional
import lavalink
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator
from redbot.core.utils._internal_utils import send_to_owners_with_prefix_replaced
from redbot.core.utils.dbtools import APSWConnectionWrapper
@ -16,9 +18,10 @@ from ...apis.playlist_wrapper import PlaylistWrapper
from ...audio_logging import debug_exc_log
from ...utils import task_callback
from ..abc import MixinMeta
from ..cog_utils import _, _OWNER_NOTIFICATION, _SCHEMA_VERSION, CompositeMetaClass
from ..cog_utils import _OWNER_NOTIFICATION, _SCHEMA_VERSION, CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Tasks.startup")
_ = Translator("Audio", Path(__file__))
class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -3,6 +3,7 @@ import logging
import math
import re
import time
from pathlib import Path
from typing import List, Optional
@ -11,16 +12,17 @@ import lavalink
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box, escape
from ...audio_dataclasses import LocalPath, Query
from ...audio_logging import IS_DEBUG
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.formatting")
_ = Translator("Audio", Path(__file__))
RE_SQUARE = re.compile(r"[\[\]]")
@ -157,7 +159,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
if not await self.is_query_allowed(
self.config,
ctx,
f"{search_choice.title} {search_choice.author} {search_choice.uri} " f"{str(query)}",
f"{search_choice.title} {search_choice.author} {search_choice.uri} {str(query)}",
query_obj=query,
):
if IS_DEBUG:
@ -295,7 +297,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
if shorten:
string = f"{track.author} - {track.title}"
if len(string) > 40:
string = "{}...".format((string[:40]).rstrip(" "))
string = f"{(string[:40]).rstrip(' ')}..."
string = f'**{escape(f"{string}", formatting=True)}**'
else:
string = (
@ -306,7 +308,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
if shorten:
string = f"{track.title}"
if len(string) > 40:
string = "{}...".format((string[:40]).rstrip(" "))
string = f"{(string[:40]).rstrip(' ')}..."
string = f'**{escape(f"{string}", formatting=True)}**'
else:
string = f'**{escape(f"{track.title}", formatting=True)}**' + escape(
@ -315,7 +317,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
else:
string = query.to_string_user()
if shorten and len(string) > 40:
string = "{}...".format((string[:40]).rstrip(" "))
string = f"{(string[:40]).rstrip(' ')}..."
string = f'**{escape(f"{string}", formatting=True)}**'
else:
if track.is_stream:
@ -330,13 +332,13 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
title = track.title
string = f"{title}"
if shorten and len(string) > 40:
string = "{}...".format((string[:40]).rstrip(" "))
string = f"{(string[:40]).rstrip(' ')}..."
string = re.sub(RE_SQUARE, "", string)
string = f"**[{escape(string, formatting=True)}]({track.uri}) **"
elif hasattr(track, "to_string_user") and track.is_local:
string = track.to_string_user() + " "
if shorten and len(string) > 40:
string = "{}...".format((string[:40]).rstrip(" "))
string = f"{(string[:40]).rstrip(' ')}..."
string = f'**{escape(f"{string}", formatting=True)}**'
return string
@ -391,7 +393,7 @@ class FormattingUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def draw_time(self, ctx) -> str:
player = lavalink.get_player(ctx.guild.id)
paused = player.paused
pos = player.position
pos = player.position or 1
dur = getattr(player.current, "length", player.position or 1)
sections = 12
loc_time = round((pos / dur if dur != 0 else pos) * sections)

View File

@ -8,14 +8,16 @@ import lavalink
from fuzzywuzzy import process
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from ...audio_dataclasses import LocalPath, Query
from ...errors import TrackEnqueueError
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.local_tracks")
_ = Translator("Audio", Path(__file__))
class LocalTrackUtilities(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -5,6 +5,7 @@ import functools
import json
import logging
import re
from pathlib import Path
from typing import Any, Final, Mapping, MutableMapping, Pattern, Union, cast
@ -14,16 +15,17 @@ import lavalink
from discord.embeds import EmptyEmbed
from redbot.core import bank, commands
from redbot.core.commands import Context
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
from ...apis.playlist_interface import get_all_playlist_for_migration23
from ...utils import PlaylistScope, task_callback
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.miscellaneous")
_ = Translator("Audio", Path(__file__))
_RE_TIME_CONVERTER: Final[Pattern] = re.compile(r"(?:(\d+):)?([0-5]?[0-9]):([0-5][0-9])")
_prefer_lyrics_cache = {}
@ -204,11 +206,8 @@ class MiscellaneousUtilities(MixinMeta, metaclass=CompositeMetaClass):
async def queue_duration(self, ctx: commands.Context) -> int:
player = lavalink.get_player(ctx.guild.id)
duration = []
async for i in AsyncIter(range(len(player.queue))):
if not player.queue[i].is_stream:
duration.append(player.queue[i].length)
queue_dur = sum(duration)
dur = [i.length async for i in AsyncIter(player.queue, steps=50)]
queue_dur = sum(dur)
if not player.queue:
queue_dur = 0
try:

View File

@ -1,5 +1,6 @@
import logging
import time
from pathlib import Path
from typing import List, Optional, Tuple, Union
@ -9,6 +10,7 @@ import lavalink
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import bold, escape
@ -17,9 +19,10 @@ from ...audio_logging import IS_DEBUG, debug_exc_log
from ...errors import QueryUnauthorized, SpotifyFetchError, TrackEnqueueError
from ...utils import Notifier
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.player")
_ = Translator("Audio", Path(__file__))
class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
@ -279,6 +282,9 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
return await self.send_embed_msg(
ctx, title=error.message.format(prefix=ctx.prefix)
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
self.update_player_lock(ctx, False)
try:
if enqueue_tracks:
@ -327,7 +333,11 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
"\nUse `{prefix}audioset spotifyapi` for instructions."
).format(prefix=ctx.prefix),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
elif query.is_album or query.is_playlist:
try:
self.update_player_lock(ctx, True)
track_list = await self.fetch_spotify_playlist(
ctx,
@ -336,6 +346,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
enqueue_tracks,
forced=forced,
)
finally:
self.update_player_lock(ctx, False)
return track_list
else:
@ -387,6 +398,9 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
"try again in a few minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
tracks = result.tracks
playlist_data = result.playlist_info
if not enqueue:
@ -581,6 +595,9 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
if await self.bot.is_owner(ctx.author):
desc = _("Please check your console or logs for details.")
return await self.send_embed_msg(ctx, title=title, description=desc)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
description = await self.get_track_description(
single_track, self.local_folder_current_path
)
@ -659,6 +676,7 @@ class PlayerUtilities(MixinMeta, metaclass=CompositeMetaClass):
except Exception as e:
self.update_player_lock(ctx, False)
raise e
finally:
self.update_player_lock(ctx, False)
return track_list

View File

@ -4,6 +4,7 @@ import datetime
import json
import logging
import math
from pathlib import Path
from typing import List, MutableMapping, Optional, Tuple, Union
@ -12,6 +13,7 @@ import lavalink
from discord.embeds import EmptyEmbed
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import box
from redbot.core.utils.menus import start_adding_reactions
@ -23,9 +25,10 @@ from ...audio_logging import debug_exc_log
from ...errors import TooManyMatches, TrackEnqueueError
from ...utils import Notifier, PlaylistScope
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.playlists")
_ = Translator("Audio", Path(__file__))
class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
@ -413,6 +416,9 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
"try again in a few minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
track = result.tracks[0]
except Exception as err:
@ -599,6 +605,9 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
"minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
tracks = result.tracks
if not tracks:
@ -625,6 +634,9 @@ class PlaylistUtilities(MixinMeta, metaclass=CompositeMetaClass):
"minutes."
),
)
except Exception as e:
self.update_player_lock(ctx, False)
raise e
tracks = result.tracks

View File

@ -1,5 +1,6 @@
import logging
import math
from pathlib import Path
from typing import List, Tuple
@ -8,14 +9,16 @@ import lavalink
from fuzzywuzzy import process
from redbot.core import commands
from redbot.core.i18n import Translator
from redbot.core.utils import AsyncIter
from redbot.core.utils.chat_formatting import humanize_number
from ...audio_dataclasses import LocalPath, Query
from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass, _
from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Utilities.queue")
_ = Translator("Audio", Path(__file__))
class QueueUtilities(MixinMeta, metaclass=CompositeMetaClass):

View File

@ -1,7 +1,12 @@
# The equalizer class and some audio eq functions are derived from
# 180093157554388993's work, with his permission
from pathlib import Path
from typing import Final
from redbot.core.i18n import Translator
_ = Translator("Audio", Path(__file__))
class Equalizer:
def __init__(self):

View File

@ -1,5 +1,11 @@
from pathlib import Path
import aiohttp
from redbot.core.i18n import Translator
_ = Translator("Audio", Path(__file__))
class AudioError(Exception):
"""Base exception for errors in the Audio cog."""
@ -91,6 +97,10 @@ class SpotifyFetchError(SpotifyApiError):
class YouTubeApiError(ApiError):
"""Base exception for YouTube Data API errors."""
def __init__(self, message, *args):
self.message = message
super().__init__(*args)
class DatabaseError(AudioError):
"""Base exception for database errors in the Audio cog."""

View File

@ -10,17 +10,18 @@ import shutil
import sys
import tempfile
import time
from typing import ClassVar, Final, List, Optional, Pattern, Tuple
import aiohttp
from tqdm import tqdm
from redbot.core import data_manager
from tqdm import tqdm
from redbot.core.i18n import Translator
from .errors import LavalinkDownloadFailed
from .utils import task_callback
_ = Translator("Audio", pathlib.Path(__file__))
log = logging.getLogger("red.audio.manager")
JAR_VERSION: Final[str] = "3.3.1.4"
JAR_BUILD: Final[int] = 1115

View File

@ -4,13 +4,16 @@ import logging
import time
from enum import Enum, unique
from pathlib import Path
from typing import MutableMapping
import discord
from redbot.core import commands
from redbot.core.i18n import Translator
log = logging.getLogger("red.cogs.Audio.task.callback")
_ = Translator("Audio", Path(__file__))
class CacheLevel: