示例#1
0
    async def get_random_track_from_db(self,
                                       tries=0) -> Optional[MutableMapping]:
        """Get a random track from the local database and return it."""
        track: Optional[MutableMapping] = {}
        try:
            query_data = {}
            date = datetime.datetime.now(
                datetime.timezone.utc) - datetime.timedelta(days=7)
            date_timestamp = int(date.timestamp())
            query_data["day"] = date_timestamp
            max_age = await self.config.cache_age()
            maxage = datetime.datetime.now(
                tz=datetime.timezone.utc) - datetime.timedelta(days=max_age)
            maxage_int = int(time.mktime(maxage.timetuple()))
            query_data["maxage"] = maxage_int
            track = await self.local_cache_api.lavalink.fetch_random(query_data
                                                                     )
            if track is not None:
                if track.get("loadType") == "V2_COMPACT":
                    track["loadType"] = "V2_COMPAT"
                results = LoadResult(track)
                track = random.choice(list(results.tracks))
        except Exception as exc:
            debug_exc_log(log, exc,
                          "Failed to fetch a random track from database")
            track = {}

        if not track:
            return None

        return track
示例#2
0
    async def get_random_from_db(self):
        tracks = []
        try:
            query_data = {}
            date = datetime.datetime.now(
                datetime.timezone.utc) - datetime.timedelta(days=7)
            date = int(date.timestamp())
            query_data["day"] = date
            max_age = await self.config.cache_age()
            maxage = datetime.datetime.now(
                tz=datetime.timezone.utc) - datetime.timedelta(days=max_age)
            maxage_int = int(time.mktime(maxage.timetuple()))
            query_data["maxage"] = maxage_int

            vals = await self.database.fetch_all("lavalink", "data",
                                                 query_data)
            recently_played = [
                r.tracks for r in vals if r if isinstance(tracks, dict)
            ]

            if recently_played:
                track = random.choice(recently_played)
                if track.get("loadType") == "V2_COMPACT":
                    track["loadType"] = "V2_COMPAT"
                results = LoadResult(track)
                tracks = list(results.tracks)
        except Exception:
            tracks = []

        return tracks
示例#3
0
    async def _api_nuker(self, ctx: commands.Context, db_entries) -> None:
        tasks = []
        for i, entry in enumerate(db_entries, start=1):
            query = entry.query
            data = entry.data
            _raw_query = audio_dataclasses.Query.process_input(query)
            results = LoadResult(json.loads(data))
            with contextlib.suppress(Exception):
                if not _raw_query.is_local and not results.has_error and len(
                        results.tracks) >= 1:
                    global_task = dict(llresponse=results, query=_raw_query)
                    tasks.append(global_task)
                if i % 20000 == 0:
                    log.debug("Running pending writes to database")
                    await asyncio.gather(
                        *[
                            asyncio.ensure_future(self.update_global(**a))
                            for a in tasks
                        ],
                        loop=self.bot.loop,
                        return_exceptions=True,
                    )
                    tasks = []
                    log.debug("Pending writes to database have finished")
            if i % 20000 == 0:
                await ctx.send(f"20k-sleeping")
                await asyncio.sleep(5)

        await ctx.send(
            f"20k-Local Audio cache sent upstream, thanks for contributing")
示例#4
0
    def get_load_track(self, track: Track):
        load_track = {
            "loadType": LoadType.TRACK_LOADED,
            "playlistInfo": {},
            "tracks": [self.track_creator(track)],
        }

        return LoadResult(load_track)
示例#5
0
 async def contribute_to_global(
         self, ctx: commands.Context,
         db_entries: List[LavalinkCacheFetchForGlobalResult]) -> None:
     tasks = []
     async for i, entry in AsyncIter(db_entries).enumerate(start=1):
         query = entry.query
         data = entry.data
         _raw_query = Query.process_input(
             query, self.cog.local_folder_current_path)
         if data.get("loadType") == "V2_COMPACT":
             data["loadType"] = "V2_COMPAT"
         results = LoadResult(data)
         with contextlib.suppress(Exception):
             if not _raw_query.is_local and not results.has_error and len(
                     results.tracks) >= 1:
                 global_task = dict(llresponse=results, query=_raw_query)
                 tasks.append(global_task)
             if i % 500 == 0:
                 if IS_DEBUG:
                     log.debug("Running pending writes to database")
                 await asyncio.gather(
                     *[
                         self.global_cache_api.update_global(**a)
                         for a in tasks
                     ],
                     return_exceptions=True,
                 )
                 tasks = []
                 if IS_DEBUG:
                     log.debug("Pending writes to database have finished")
                 await asyncio.sleep(5)
     with contextlib.suppress(Exception):
         if tasks:
             if IS_DEBUG:
                 log.debug("Running pending writes to database")
             await asyncio.gather(
                 *[self.global_cache_api.update_global(**a) for a in tasks],
                 return_exceptions=True,
             )
             if IS_DEBUG:
                 log.debug("Pending writes to database have finished")
     await ctx.tick()
示例#6
0
    async def play_random(self):
        tracks = []
        try:
            query_data = {}
            for i in range(1, 8):
                date = ("%" + str(
                    (datetime.datetime.now(datetime.timezone.utc) -
                     datetime.timedelta(days=i)).date()) + "%")
                query_data[f"day{i}"] = date

            vals = await self.fetch_all("lavalink", "data", query_data)
            recently_played = [r.data for r in vals if r]

            if recently_played:
                track = random.choice(recently_played)
                results = LoadResult(json.loads(track))
                tracks = list(results.tracks)
        except Exception:
            tracks = []

        return tracks
示例#7
0
    async def lavalink_query(
        self,
        ctx: commands.Context,
        player: lavalink.Player,
        query: audio_dataclasses.Query,
        forced: bool = False,
    ) -> Tuple[LoadResult, bool]:
        """A replacement for :code:`lavalink.Player.load_tracks`. This will try to get a valid
        cached entry first if not found or if in valid it will then call the lavalink API.

        Parameters
        ----------
        ctx: commands.Context
            The context this method is being called under.
        player : lavalink.Player
            The player who's requesting the query.
        query: audio_dataclasses.Query
            The Query object for the query in question.
        forced:bool
            Whether or not to skip cache and call API first..
        Returns
        -------
        Tuple[lavalink.LoadResult, bool]
            Tuple with the Load result and whether or not the API was called.
        """
        current_cache_level = CacheLevel(await self.config.cache_level())
        cache_enabled = CacheLevel.set_lavalink().is_subset(
            current_cache_level)
        val = None
        _raw_query = audio_dataclasses.Query.process_input(query)
        query = str(_raw_query)
        if cache_enabled and not forced and not _raw_query.is_local:
            update = True
            with contextlib.suppress(SQLError):
                (val, update) = await self.database.fetch_one(
                    "lavalink", "data", {"query": query})
            if update:
                val = None
            if val and isinstance(val, dict):
                log.debug(f"Querying Local Database for {query}")
                task = ("update", ("lavalink", {"query": query}))
                self.append_task(ctx, *task)
            else:
                val = None
        if val and not forced and isinstance(val, dict):
            data = val
            data["query"] = query
            if data.get("loadType") == "V2_COMPACT":
                data["loadType"] = "V2_COMPAT"
            results = LoadResult(data)
            called_api = False
            if results.has_error:
                # If cached value has an invalid entry make a new call so that it gets updated
                return await self.lavalink_query(ctx,
                                                 player,
                                                 _raw_query,
                                                 forced=True)
        else:
            called_api = True
            results = None
            try:
                results = await player.load_tracks(query)
            except KeyError:
                results = None
            except RuntimeError:
                raise TrackEnqueueError
            if results is None:
                results = LoadResult({
                    "loadType": "LOAD_FAILED",
                    "playlistInfo": {},
                    "tracks": []
                })
            if (cache_enabled and results.load_type and not results.has_error
                    and not _raw_query.is_local and results.tracks):
                with contextlib.suppress(SQLError):
                    time_now = int(
                        datetime.datetime.now(
                            datetime.timezone.utc).timestamp())
                    data = json.dumps(results._raw)
                    if all(
                            k in data for k in
                        ["loadType", "playlistInfo", "isSeekable", "isStream"
                         ]):
                        task = (
                            "insert",
                            (
                                "lavalink",
                                [{
                                    "query": query,
                                    "data": data,
                                    "last_updated": time_now,
                                    "last_fetched": time_now,
                                }],
                            ),
                        )
                        self.append_task(ctx, *task)
        return results, called_api
示例#8
0
    async def fetch_track(
        self,
        ctx: commands.Context,
        player: lavalink.Player,
        query: Query,
        forced: bool = False,
        lazy: bool = False,
        should_query_global: bool = True,
    ) -> Tuple[LoadResult, bool]:
        """A replacement for :code:`lavalink.Player.load_tracks`. This will try to get a valid
        cached entry first if not found or if in valid it will then call the lavalink API.

        Parameters
        ----------
        ctx: commands.Context
            The context this method is being called under.
        player : lavalink.Player
            The player who's requesting the query.
        query: audio_dataclasses.Query
            The Query object for the query in question.
        forced:bool
            Whether or not to skip cache and call API first.
        lazy:bool
            If set to True, it will not call the api if a track is not found.
        should_query_global:bool
            If the method should query the global database.

        Returns
        -------
        Tuple[lavalink.LoadResult, bool]
            Tuple with the Load result and whether or not the API was called.
        """
        current_cache_level = CacheLevel(await self.config.cache_level())
        cache_enabled = CacheLevel.set_lavalink().is_subset(
            current_cache_level)
        val = None
        query = Query.process_input(query, self.cog.local_folder_current_path)
        query_string = str(query)
        globaldb_toggle = await self.config.global_db_enabled()
        valid_global_entry = False
        results = None
        called_api = False
        prefer_lyrics = await self.cog.get_lyrics_status(ctx)
        if prefer_lyrics and query.is_youtube and query.is_search:
            query_string = f"{query} - lyrics"
        if cache_enabled and not forced and not query.is_local:
            try:
                (val,
                 last_updated) = await self.local_cache_api.lavalink.fetch_one(
                     {"query": query_string})
            except Exception as exc:
                debug_exc_log(
                    log, exc,
                    f"Failed to fetch '{query_string}' from Lavalink table")

            if val and isinstance(val, dict):
                if IS_DEBUG:
                    log.debug(f"Updating Local Database with {query_string}")
                task = ("update", ("lavalink", {"query": query_string}))
                self.append_task(ctx, *task)
            else:
                val = None

            if val and not forced and isinstance(val, dict):
                valid_global_entry = False
                called_api = False
            else:
                val = None
        if (globaldb_toggle and not val and should_query_global and not forced
                and not query.is_local and not query.is_spotify):
            valid_global_entry = False
            with contextlib.suppress(Exception):
                global_entry = await self.global_cache_api.get_call(query=query
                                                                    )
                if global_entry.get("loadType") == "V2_COMPACT":
                    global_entry["loadType"] = "V2_COMPAT"
                results = LoadResult(global_entry)
                if results.load_type in [
                        LoadType.PLAYLIST_LOADED,
                        LoadType.TRACK_LOADED,
                        LoadType.SEARCH_RESULT,
                        LoadType.V2_COMPAT,
                ]:
                    valid_global_entry = True
                if valid_global_entry:
                    if IS_DEBUG:
                        log.debug(f"Querying Global DB api for {query}")
                    results, called_api = results, False
        if valid_global_entry:
            pass
        elif lazy is True:
            called_api = False
        elif val and not forced and isinstance(val, dict):
            data = val
            data["query"] = query_string
            if data.get("loadType") == "V2_COMPACT":
                data["loadType"] = "V2_COMPAT"
            results = LoadResult(data)
            called_api = False
            if results.has_error:
                # If cached value has an invalid entry make a new call so that it gets updated
                results, called_api = await self.fetch_track(ctx,
                                                             player,
                                                             query,
                                                             forced=True)
            valid_global_entry = False
        else:
            if IS_DEBUG:
                log.debug(f"Querying Lavalink api for {query_string}")
            called_api = True
            try:
                results = await player.load_tracks(query_string)
            except KeyError:
                results = None
            except RuntimeError:
                raise TrackEnqueueError
        if results is None:
            results = LoadResult({
                "loadType": "LOAD_FAILED",
                "playlistInfo": {},
                "tracks": []
            })
            valid_global_entry = False
        update_global = (globaldb_toggle and not valid_global_entry
                         and self.global_cache_api.has_api_key)
        with contextlib.suppress(Exception):
            if (update_global and not query.is_local and not results.has_error
                    and len(results.tracks) >= 1):
                global_task = ("global", dict(llresponse=results, query=query))
                self.append_task(ctx, *global_task)
        if (cache_enabled and results.load_type and not results.has_error
                and not query.is_local and results.tracks):
            try:
                time_now = int(
                    datetime.datetime.now(datetime.timezone.utc).timestamp())
                data = json.dumps(results._raw)
                if all(k in data for k in
                       ["loadType", "playlistInfo", "isSeekable", "isStream"]):
                    task = (
                        "insert",
                        (
                            "lavalink",
                            [{
                                "query": query_string,
                                "data": data,
                                "last_updated": time_now,
                                "last_fetched": time_now,
                            }],
                        ),
                    )
                    self.append_task(ctx, *task)
            except Exception as exc:
                debug_exc_log(
                    log,
                    exc,
                    f"Failed to enqueue write task for '{query_string}' to Lavalink table",
                )
        return results, called_api
示例#9
0
    async def spotify_enqueue(
        self,
        ctx: commands.Context,
        query_type: str,
        uri: str,
        enqueue: bool,
        player: lavalink.Player,
        lock: Callable,
        notifier: Optional[Notifier] = None,
        forced: bool = False,
        query_global: bool = True,
    ) -> List[lavalink.Track]:
        """Queries the Database then falls back to Spotify and YouTube APIs then Enqueued matched
        tracks.

        Parameters
        ----------
        ctx: commands.Context
            The context this method is being called under.
        query_type : str
            Type of query to perform (Pl
        uri: str
            Spotify URL ID.
        enqueue:bool
            Whether or not to enqueue the tracks
        player: lavalink.Player
            The current Player.
        notifier: Notifier
            A Notifier object to handle the user UI notifications while tracks are loaded.
        lock: Callable
            A callable handling the Track enqueue lock while spotify tracks are being added.
        query_global: bool
            Whether or not to query the global API.
        forced: bool
            Ignore Cache and make a fetch from API.
        Returns
        -------
        List[str]
            List of Youtube URLs.
        """
        await self.global_cache_api._get_api_key()
        globaldb_toggle = await self.config.global_db_enabled()
        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()
            enqueued_tracks = 0
            consecutive_fails = 0
            queue_dur = await self.cog.queue_duration(ctx)
            queue_total_duration = self.cog.format_time(queue_dur)
            before_queue_length = len(player.queue)
            tracks_from_spotify = await self.fetch_from_spotify_api(
                query_type, uri, params=None, notifier=notifier)
            total_tracks = len(tracks_from_spotify)
            if total_tracks < 1 and notifier is not None:
                lock(ctx, False)
                embed3 = discord.Embed(
                    colour=await ctx.embed_colour(),
                    title=
                    _("This doesn't seem to be a supported Spotify URL or code."
                      ),
                )
                await notifier.update_embed(embed3)

                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):
                (
                    song_url,
                    track_info,
                    uri,
                    artist_name,
                    track_name,
                    _id,
                    _type,
                ) = await self.spotify_api.get_spotify_track_info(track, ctx)

                database_entries.append({
                    "id": _id,
                    "type": _type,
                    "uri": uri,
                    "track_name": track_name,
                    "artist_name": artist_name,
                    "song_url": song_url,
                    "track_info": track_info,
                    "last_updated": time_now,
                    "last_fetched": time_now,
                })
                val = None
                llresponse = None
                if youtube_cache:
                    try:
                        (val, last_updated
                         ) = await self.local_cache_api.youtube.fetch_one(
                             {"track": track_info})
                    except Exception as exc:
                        debug_exc_log(
                            log, exc,
                            f"Failed to fetch {track_info} from YouTube table")
                should_query_global = globaldb_toggle and query_global and val is None
                if should_query_global:
                    llresponse = await self.global_cache_api.get_spotify(
                        track_name, artist_name)
                    if llresponse:
                        if llresponse.get("loadType") == "V2_COMPACT":
                            llresponse["loadType"] = "V2_COMPAT"
                        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)

                    if isinstance(llresponse, LoadResult):
                        track_object = llresponse.tracks
                    elif val:
                        result = None
                        if should_query_global:
                            llresponse = await self.global_cache_api.get_call(
                                val)
                            if llresponse:
                                if llresponse.get("loadType") == "V2_COMPACT":
                                    llresponse["loadType"] = "V2_COMPAT"
                                llresponse = LoadResult(llresponse)
                            result = llresponse or None
                        if not result:
                            try:
                                (result, called_api) = await self.fetch_track(
                                    ctx,
                                    player,
                                    Query.process_input(
                                        val,
                                        self.cog.local_folder_current_path),
                                    forced=forced,
                                    should_query_global=not should_query_global,
                                )
                            except (RuntimeError,
                                    aiohttp.ServerDisconnectedError):
                                lock(ctx, False)
                                error_embed = discord.Embed(
                                    colour=await ctx.embed_colour(),
                                    title=
                                    _("The connection was reset while loading the playlist."
                                      ),
                                )
                                if notifier is not None:
                                    await notifier.update_embed(error_embed)
                                break
                            except asyncio.TimeoutError:
                                lock(ctx, False)
                                error_embed = discord.Embed(
                                    colour=await ctx.embed_colour(),
                                    title=
                                    _("Player timeout, skipping remaining tracks."
                                      ),
                                )
                                if notifier is not None:
                                    await notifier.update_embed(error_embed)
                                break
                        track_object = result.tracks
                    else:
                        track_object = []
                else:
                    track_object = []
                if (track_count % 2 == 0) or (track_count == total_tracks):
                    key = "lavalink"
                    seconds = "???"
                    second_key = None
                    if notifier is not None:
                        await notifier.notify_user(
                            current=track_count,
                            total=total_tracks,
                            key=key,
                            seconds_key=second_key,
                            seconds=seconds,
                        )

                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
                    continue
                consecutive_fails = 0
                single_track = track_object[0]
                query = Query.process_input(single_track,
                                            self.cog.local_folder_current_path)
                if not await self.cog.is_query_allowed(
                        self.config,
                        ctx,
                        f"{single_track.title} {single_track.author} {single_track.uri} {query}",
                        query_obj=query,
                ):
                    has_not_allowed = True
                    if IS_DEBUG:
                        log.debug(
                            f"Query is not allowed in {ctx.guild} ({ctx.guild.id})"
                        )
                    continue
                track_list.append(single_track)
                if enqueue:
                    if len(player.queue) >= 10000:
                        continue
                    if guild_data["maxlength"] > 0:
                        if self.cog.is_track_length_allowed(
                                single_track, guild_data["maxlength"]):
                            enqueued_tracks += 1
                            single_track.extras.update({
                                "enqueue_time":
                                int(time.time()),
                                "vc":
                                player.channel.id,
                                "requester":
                                ctx.author.id,
                            })
                            player.add(ctx.author, single_track)
                            self.bot.dispatch(
                                "red_audio_track_enqueue",
                                player.channel.guild,
                                single_track,
                                ctx.author,
                            )
                    else:
                        enqueued_tracks += 1
                        single_track.extras.update({
                            "enqueue_time":
                            int(time.time()),
                            "vc":
                            player.channel.id,
                            "requester":
                            ctx.author.id,
                        })
                        player.add(ctx.author, single_track)
                        self.bot.dispatch(
                            "red_audio_track_enqueue",
                            player.channel.guild,
                            single_track,
                            ctx.author,
                        )

                    if not player.current:
                        await player.play()
            if enqueue and tracks_from_spotify:
                if total_tracks > enqueued_tracks:
                    maxlength_msg = _(
                        " {bad_tracks} tracks cannot be queued.").format(
                            bad_tracks=(total_tracks - enqueued_tracks))
                else:
                    maxlength_msg = ""

                embed = discord.Embed(
                    colour=await ctx.embed_colour(),
                    title=_("Playlist Enqueued"),
                    description=_(
                        "Added {num} tracks to the queue.{maxlength_msg}").
                    format(num=enqueued_tracks, maxlength_msg=maxlength_msg),
                )
                if not guild_data["shuffle"] and queue_dur > 0:
                    embed.set_footer(
                        text=_("{time} until start of playlist"
                               " playback: starts at #{position} in queue"
                               ).format(time=queue_total_duration,
                                        position=before_queue_length + 1))

                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))
                self.append_task(ctx, *task)
        except Exception as exc:
            lock(ctx, False)
            raise exc
        finally:
            lock(ctx, False)
        return track_list
示例#10
0
    async def spotify_enqueue(
        self,
        ctx: commands.Context,
        query_type: str,
        uri: str,
        enqueue: bool,
        player: lavalink.Player,
        lock: Callable,
        notifier: Optional[Notifier] = None,
        query_global=True,
    ) -> List[lavalink.Track]:
        track_list = []
        has_not_allowed = False
        await self.audio_api._get_api_key()
        try:
            current_cache_level = (CacheLevel(await self.config.cache_level())
                                   if HAS_SQL else CacheLevel.none())
            guild_data = await self.config.guild(ctx.guild).all()

            # now = int(time.time())
            enqueued_tracks = 0
            consecutive_fails = 0
            queue_dur = await queue_duration(ctx)
            queue_total_duration = lavalink.utils.format_time(queue_dur)
            before_queue_length = len(player.queue)
            tracks_from_spotify = await self._spotify_fetch_tracks(
                query_type, uri, params=None, notifier=notifier)
            globaldb_toggle = await _config.enabled()
            total_tracks = len(tracks_from_spotify)
            if total_tracks < 1:
                lock(ctx, False)
                embed3 = discord.Embed(
                    colour=await ctx.embed_colour(),
                    title=
                    _("This doesn't seem to be a supported Spotify URL or code."
                      ),
                )
                await notifier.update_embed(embed3)

                return track_list
            database_entries = []
            time_now = str(datetime.datetime.now(datetime.timezone.utc))

            youtube_cache = CacheLevel.set_youtube().is_subset(
                current_cache_level)
            spotify_cache = CacheLevel.set_spotify().is_subset(
                current_cache_level)
            for track_count, track in enumerate(tracks_from_spotify):
                (
                    song_url,
                    track_info,
                    uri,
                    artist_name,
                    track_name,
                    _id,
                    _type,
                ) = self._get_spotify_track_info(track)

                database_entries.append({
                    "id": _id,
                    "type": _type,
                    "uri": uri,
                    "track_name": track_name,
                    "artist_name": artist_name,
                    "song_url": song_url,
                    "track_info": track_info,
                    "last_updated": time_now,
                    "last_fetched": time_now,
                })

                val = None
                llresponse = None
                if youtube_cache:
                    update = True
                    with contextlib.suppress(SQLError):
                        val, update = await self.fetch_one(
                            "youtube", "youtube_url", {"track": track_info})
                    if update:
                        val = None
                should_query_global = (globaldb_toggle and not update
                                       and query_global and val is None)
                if should_query_global:
                    llresponse = await self.audio_api.get_spotify(
                        track_name, artist_name)
                    if llresponse:
                        llresponse = LoadResult(llresponse)
                    val = llresponse or None

                if val is None:
                    val = await self._youtube_first_time_query(
                        ctx,
                        track_info,
                        current_cache_level=current_cache_level)
                if youtube_cache and val and llresponse is None:
                    task = ("update", ("youtube", {"track": track_info}))
                    self.append_task(ctx, *task)

                if llresponse:
                    track_object = llresponse.tracks
                elif val:
                    try:
                        result, called_api = await self.lavalink_query(
                            ctx,
                            player,
                            audio_dataclasses.Query.process_input(val),
                            should_query_global=not should_query_global,
                        )
                    except (RuntimeError, aiohttp.ServerDisconnectedError):
                        lock(ctx, False)
                        error_embed = discord.Embed(
                            colour=await ctx.embed_colour(),
                            title=
                            _("The connection was reset while loading the playlist."
                              ),
                        )
                        await notifier.update_embed(error_embed)
                        break
                    except asyncio.TimeoutError:
                        lock(ctx, False)
                        error_embed = discord.Embed(
                            colour=await ctx.embed_colour(),
                            title=_(
                                "Player timeout, skipping remaining tracks."),
                        )
                        await notifier.update_embed(error_embed)
                        break
                    track_object = result.tracks
                else:
                    track_object = []
                if (track_count % 2 == 0) or (track_count == total_tracks):
                    key = "lavalink"
                    seconds = "???"
                    second_key = None
                    await notifier.notify_user(
                        current=track_count,
                        total=total_tracks,
                        key=key,
                        seconds_key=second_key,
                        seconds=seconds,
                    )

                if consecutive_fails >= 10:
                    error_embed = discord.Embed(
                        colour=await ctx.embed_colour(),
                        title=_("Failing to get tracks, skipping remaining."),
                    )
                    await notifier.update_embed(error_embed)
                    break
                if not track_object:
                    consecutive_fails += 1
                    continue
                consecutive_fails = 0
                single_track = track_object[0]
                if not await is_allowed(
                        ctx.guild,
                    (f"{single_track.title} {single_track.author} {single_track.uri} "
                     f"{str(audio_dataclasses.Query.process_input(single_track))}"
                     ),
                ):
                    has_not_allowed = True
                    log.debug(
                        f"Query is not allowed in {ctx.guild} ({ctx.guild.id})"
                    )
                    continue
                track_list.append(single_track)
                if enqueue:
                    if guild_data["maxlength"] > 0:
                        if track_limit(single_track, guild_data["maxlength"]):
                            enqueued_tracks += 1
                            player.add(ctx.author, single_track)
                            self.bot.dispatch(
                                "red_audio_track_enqueue",
                                player.channel.guild,
                                single_track,
                                ctx.author,
                            )
                    else:
                        enqueued_tracks += 1
                        player.add(ctx.author, single_track)
                        self.bot.dispatch(
                            "red_audio_track_enqueue",
                            player.channel.guild,
                            single_track,
                            ctx.author,
                        )

                    if not player.current:
                        await player.play()
            if len(track_list) == 0:
                if not has_not_allowed:
                    embed3 = discord.Embed(
                        colour=await ctx.embed_colour(),
                        title=
                        _("Nothing found.\nThe YouTube API key may be invalid "
                          "or you may be rate limited on YouTube's search service.\n"
                          "Check the YouTube API key again and follow the instructions "
                          "at `{prefix}audioset youtubeapi`.").format(
                              prefix=ctx.prefix),
                    )
                    await ctx.send(embed=embed3)
            player.maybe_shuffle()
            if enqueue and tracks_from_spotify:
                if total_tracks > enqueued_tracks:
                    maxlength_msg = " {bad_tracks} tracks cannot be queued.".format(
                        bad_tracks=(total_tracks - enqueued_tracks))
                else:
                    maxlength_msg = ""

                embed = discord.Embed(
                    colour=await ctx.embed_colour(),
                    title=_("Playlist Enqueued"),
                    description=_(
                        "Added {num} tracks to the queue.{maxlength_msg}").
                    format(num=enqueued_tracks, maxlength_msg=maxlength_msg),
                )
                if not guild_data["shuffle"] and queue_dur > 0:
                    embed.set_footer(
                        text=_("{time} until start of playlist"
                               " playback: starts at #{position} in queue"
                               ).format(time=queue_total_duration,
                                        position=before_queue_length + 1))

                await notifier.update_embed(embed)
            lock(ctx, False)

            if spotify_cache:
                task = ("insert", ("spotify", database_entries))
                self.append_task(ctx, *task)
        except Exception as e:
            lock(ctx, False)
            raise e
        finally:
            lock(ctx, False)
        return track_list
示例#11
0
    async def lavalink_query(
        self,
        ctx: commands.Context,
        player: lavalink.Player,
        query: audio_dataclasses.Query,
        forced: bool = False,
        lazy: bool = False,
        should_query_global: bool = True,
    ) -> Tuple[LoadResult, bool]:
        """A replacement for :code:`lavalink.Player.load_tracks`. This will try to get a valid
        cached entry first if not found or if in valid it will then call the lavalink API.

        Parameters
        ----------
        ctx: commands.Context
            The context this method is being called under.
        player : lavalink.Player
            The player who's requesting the query.
        query: audio_dataclasses.Query
            The Query object for the query in question.
        forced:bool
            Whether or not to skip cache and call API first..
        lazy:bool
            If set to True, it will not call the api if a track is not found.
        Returns
        -------
        Tuple[lavalink.LoadResult, bool]
            Tuple with the Load result and whether or not the API was called.
        """
        await self.audio_api._get_api_key()
        current_cache_level = (CacheLevel(await self.config.cache_level())
                               if HAS_SQL else CacheLevel.none())
        cache_enabled = CacheLevel.set_lavalink().is_subset(
            current_cache_level)
        val = None
        _raw_query = audio_dataclasses.Query.process_input(query)
        query = str(_raw_query)
        valid_global_entry = True
        results = None
        globaldb_toggle = await _config.enabled()

        if cache_enabled and not forced and not _raw_query.is_local:
            update = True
            with contextlib.suppress(SQLError):
                val, update = await self.fetch_one("lavalink", "data",
                                                   {"query": query})
            if update:
                val = None
            if val:
                local_task = ("update", ("lavalink", {"query": query}))
                self.append_task(ctx, *local_task)
                valid_global_entry = False
        if (globaldb_toggle and not val and should_query_global and not forced
                and not _raw_query.is_local and not _raw_query.is_spotify):
            valid_global_entry = False
            with contextlib.suppress(Exception):
                global_entry = await self.audio_api.get_call(query=_raw_query)
                results = LoadResult(global_entry)
                if results.load_type in [
                        LoadType.PLAYLIST_LOADED,
                        LoadType.TRACK_LOADED,
                        LoadType.SEARCH_RESULT,
                        LoadType.V2_COMPAT,
                ]:
                    valid_global_entry = True
                if valid_global_entry:
                    return results, False

        if lazy is True:
            called_api = False
        elif val and not forced:
            data = json.loads(val)
            data["query"] = query
            results = LoadResult(data)
            called_api = False
            if results.has_error:
                # If cached value has an invalid entry make a new call so that it gets updated
                results, called_api = await self.lavalink_query(ctx,
                                                                player,
                                                                _raw_query,
                                                                forced=True)
            valid_global_entry = False
        else:
            called_api = True
            results = None
            try:
                results = await player.load_tracks(query)
            except KeyError:
                results = None
            if results is None:
                results = LoadResult({
                    "loadType": "LOAD_FAILED",
                    "playlistInfo": {},
                    "tracks": []
                })
            valid_global_entry = False
        update_global = globaldb_toggle and not valid_global_entry and _WRITE_GLOBAL_API_ACCESS
        with contextlib.suppress(Exception):
            if (update_global and not _raw_query.is_local
                    and not results.has_error and len(results.tracks) >= 1):
                global_task = ("global",
                               dict(llresponse=results, query=_raw_query))
                self.append_task(ctx, *global_task)
        if (cache_enabled and results.load_type and not results.has_error
                and not _raw_query.is_local and results.tracks):
            with contextlib.suppress(SQLError):
                time_now = str(datetime.datetime.now(datetime.timezone.utc))
                local_task = (
                    "insert",
                    (
                        "lavalink",
                        [{
                            "query": query,
                            "data": json.dumps(results._raw),
                            "last_updated": time_now,
                            "last_fetched": time_now,
                        }],
                    ),
                )
                self.append_task(ctx, *local_task)
        return results, called_api