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 import logging
from collections import namedtuple from collections import namedtuple
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path
from typing import List, MutableMapping, Optional, Union from typing import List, MutableMapping, Optional, Union
import discord import discord
import lavalink import lavalink
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_list from redbot.core.utils.chat_formatting import humanize_list
from ..errors import InvalidPlaylistScope, MissingAuthor, MissingGuild from ..errors import InvalidPlaylistScope, MissingAuthor, MissingGuild
from ..utils import PlaylistScope from ..utils import PlaylistScope
log = logging.getLogger("red.cogs.Audio.api.utils") log = logging.getLogger("red.cogs.Audio.api.utils")
_ = Translator("Audio", Path(__file__))
@dataclass @dataclass

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import json import json
import logging import logging
from pathlib import Path
from typing import TYPE_CHECKING, Mapping, Optional, Union from typing import TYPE_CHECKING, Mapping, Optional, Union
@ -8,6 +9,7 @@ import aiohttp
from redbot.core import Config from redbot.core import Config
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.commands import Cog from redbot.core.commands import Cog
from redbot.core.i18n import Translator
from ..errors import YouTubeApiError from ..errors import YouTubeApiError
@ -15,7 +17,7 @@ if TYPE_CHECKING:
from .. import Audio from .. import Audio
log = logging.getLogger("red.cogs.Audio.api.YouTube") log = logging.getLogger("red.cogs.Audio.api.YouTube")
_ = Translator("Audio", Path(__file__))
SEARCH_ENDPOINT = "https://www.googleapis.com/youtube/v3/search" SEARCH_ENDPOINT = "https://www.googleapis.com/youtube/v3/search"
@ -32,7 +34,7 @@ class YouTubeWrapper:
self._token: Mapping[str, str] = {} self._token: Mapping[str, str] = {}
self.cog = cog 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 self._token = new_token
async def _get_api_key( async def _get_api_key(
@ -54,11 +56,28 @@ class YouTubeWrapper:
"type": "video", "type": "video",
} }
async with self.session.request("GET", SEARCH_ENDPOINT, params=params) as r: 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 return None
elif r.status in [403, 429]: elif r.status == 404:
if r.reason == "quotaExceeded": return None
raise YouTubeApiError("Your YouTube Data API quota has been reached.") 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 return None
else: else:
search_response = await r.json(loads=json.loads) search_response = await r.json(loads=json.loads)

View File

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

View File

@ -1,6 +1,7 @@
import argparse import argparse
import functools import functools
import re import re
from pathlib import Path
from typing import Final, MutableMapping, Optional, Pattern, Tuple, Union 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 .errors import NoMatchesFound, TooManyMatches
from .utils import PlaylistScope from .utils import PlaylistScope
_ = Translator("Audio", __file__) _ = Translator("Audio", Path(__file__))
__all__ = [ __all__ = [
"ComplexScopeParser", "ComplexScopeParser",

View File

@ -2,6 +2,7 @@ import asyncio
import json import json
from collections import Counter from collections import Counter
from pathlib import Path
from typing import Mapping from typing import Mapping
import aiohttp import aiohttp
@ -11,11 +12,13 @@ from redbot.core import Config
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.commands import Cog from redbot.core.commands import Cog
from redbot.core.data_manager import cog_data_path 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 ..utils import PlaylistScope
from . import abc, cog_utils, commands, events, tasks, utilities 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(_) @cog_i18n(_)

View File

@ -1,10 +1,8 @@
from abc import ABC from abc import ABC
from pathlib import Path
from typing import Final from typing import Final
from redbot import VersionInfo from redbot import VersionInfo
from redbot.core import commands from redbot.core import commands
from redbot.core.i18n import Translator
from ..converters import get_lazy_converter, get_playlist_converter 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"] __author__ = ["aikaterna", "Draper"]
_ = Translator("Audio", Path(__file__).parent)
_SCHEMA_VERSION: Final[int] = 3 _SCHEMA_VERSION: Final[int] = 3
_OWNER_NOTIFICATION: Final[int] = 1 _OWNER_NOTIFICATION: Final[int] = 1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,15 @@
import asyncio import asyncio
import logging import logging
from pathlib import Path
from typing import Literal, Mapping from typing import Literal, Mapping
from redbot.core import commands from redbot.core import commands
from redbot.core.i18n import Translator
from ..abc import MixinMeta from ..abc import MixinMeta
from ..cog_utils import CompositeMetaClass from ..cog_utils import CompositeMetaClass
log = logging.getLogger("red.cogs.Audio.cog.Events.red") log = logging.getLogger("red.cogs.Audio.cog.Events.red")
_ = Translator("Audio", Path(__file__))
class RedEvents(MixinMeta, metaclass=CompositeMetaClass): class RedEvents(MixinMeta, metaclass=CompositeMetaClass):
@ -15,11 +18,11 @@ class RedEvents(MixinMeta, metaclass=CompositeMetaClass):
self, service_name: str, api_tokens: Mapping[str, str] self, service_name: str, api_tokens: Mapping[str, str]
) -> None: ) -> None:
if service_name == "youtube": 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": 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": 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( async def red_delete_data_for_user(
self, self,

View File

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

View File

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

View File

@ -2,12 +2,14 @@ import asyncio
import datetime import datetime
import itertools import itertools
import logging import logging
from pathlib import Path
from typing import Optional from typing import Optional
import lavalink import lavalink
from redbot.core.data_manager import cog_data_path 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._internal_utils import send_to_owners_with_prefix_replaced
from redbot.core.utils.dbtools import APSWConnectionWrapper 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 ...audio_logging import debug_exc_log
from ...utils import task_callback from ...utils import task_callback
from ..abc import MixinMeta 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") log = logging.getLogger("red.cogs.Audio.cog.Tasks.startup")
_ = Translator("Audio", Path(__file__))
class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass): class StartUpTasks(MixinMeta, metaclass=CompositeMetaClass):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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