Beispiel #1
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
Beispiel #2
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,
    ) -> List[lavalink.Track]:
        track_list = []
        has_not_allowed = False
        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)
            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
                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
                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:
                    task = ("update", ("youtube", {"track": track_info}))
                    self.append_task(ctx, *task)

                if val:
                    try:
                        result, called_api = await self.lavalink_query(
                            ctx, player,
                            audio_dataclasses.Query.process_input(val))
                    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 timedout, skipping remaning 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
                    # if track_count == 2:
                    #     five_time = int(time.time()) - now
                    # if track_count >= 2:
                    # remain_tracks = total_tracks - track_count
                    # time_remain = (remain_tracks / 2) * five_time
                    # if track_count < total_tracks:
                    #     seconds = dynamic_time(int(time_remain))
                    # if track_count == total_tracks:
                    #     seconds = "0s"
                    # second_key = "lavalink_time"
                    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
Beispiel #3
0
    async def autoplay(self, player: lavalink.Player,
                       playlist_api: PlaylistWrapper):
        """Enqueue a random track."""
        autoplaylist = await self.config.guild(player.channel.guild
                                               ).autoplaylist()
        current_cache_level = CacheLevel(await self.config.cache_level())
        cache_enabled = CacheLevel.set_lavalink().is_subset(
            current_cache_level)
        playlist = None
        tracks = None
        if autoplaylist["enabled"]:
            try:
                playlist = await get_playlist(
                    autoplaylist["id"],
                    autoplaylist["scope"],
                    self.bot,
                    playlist_api,
                    player.channel.guild,
                    player.channel.guild.me,
                )
                tracks = playlist.tracks_obj
            except Exception as exc:
                debug_exc_log(log, exc,
                              "Failed to fetch playlist for autoplay")

        if not tracks or not getattr(playlist, "tracks", None):
            if cache_enabled:
                track = await self.get_random_track_from_db()
                tracks = [] if not track else [track]
            if not tracks:
                ctx = namedtuple("Context", "message guild cog")
                (results, called_api) = await self.fetch_track(
                    cast(
                        commands.Context,
                        ctx(player.channel.guild, player.channel.guild,
                            self.cog),
                    ),
                    player,
                    Query.process_input(_TOP_100_US,
                                        self.cog.local_folder_current_path),
                )
                tracks = list(results.tracks)
        if tracks:
            multiple = len(tracks) > 1
            valid = not multiple
            tries = len(tracks)
            track = tracks[0]
            while valid is False and multiple:
                tries -= 1
                if tries <= 0:
                    raise DatabaseError("No valid entry found")
                track = random.choice(tracks)
                query = Query.process_input(track,
                                            self.cog.local_folder_current_path)
                await asyncio.sleep(0.001)
                if ((not query.valid) or query.is_nsfw or
                    (query.is_local and query.local_track_path is not None
                     and not query.local_track_path.exists())):
                    continue
                notify_channel = self.bot.get_channel(player.fetch("channel"))
                if not await self.cog.is_query_allowed(
                        self.config,
                        notify_channel,
                        f"{track.title} {track.author} {track.uri} {query}",
                        query_obj=query,
                ):
                    if IS_DEBUG:
                        log.debug(
                            "Query is not allowed in "
                            f"{player.channel.guild} ({player.channel.guild.id})"
                        )
                    continue
                valid = True
            track.extras.update({
                "autoplay": True,
                "enqueue_time": int(time.time()),
                "vc": player.channel.id,
                "requester": player.channel.guild.me.id,
            })
            player.add(player.channel.guild.me, track)
            self.bot.dispatch(
                "red_audio_track_auto_play",
                player.channel.guild,
                track,
                player.channel.guild.me,
            )
            if not player.current:
                await player.play()
Beispiel #4
0
    async def autoplay(self, player: lavalink.Player):
        autoplaylist = await self.config.guild(player.channel.guild
                                               ).autoplaylist()
        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)
        playlist = None
        tracks = None
        if autoplaylist["enabled"]:
            with contextlib.suppress(Exception):
                playlist = await get_playlist(
                    autoplaylist["id"],
                    autoplaylist["scope"],
                    self.bot,
                    player.channel.guild,
                    player.channel.guild.me,
                )
                tracks = playlist.tracks_obj

        if not tracks or not getattr(playlist, "tracks", None):
            if cache_enabled:
                tracks = await self.play_random()
            if not tracks:
                ctx = namedtuple("Context", "message")
                results, called_api = await self.lavalink_query(
                    ctx(player.channel.guild),
                    player,
                    audio_dataclasses.Query.process_input(_TOP_100_US),
                )
                tracks = list(results.tracks)
        if tracks:
            multiple = len(tracks) > 1
            track = tracks[0]

            valid = not multiple
            tries = len(tracks)
            while valid is False and multiple:
                tries -= 1
                if tries <= 0:
                    raise DatabaseError("No valid entry found")
                track = random.choice(tracks)
                query = audio_dataclasses.Query.process_input(track)
                await asyncio.sleep(0.001)
                if not query.valid:
                    continue
                if query.is_local and not query.track.exists():
                    continue
                if not await is_allowed(
                        player.channel.guild,
                    (f"{track.title} {track.author} {track.uri} "
                     f"{str(audio_dataclasses.Query.process_input(track))}"),
                ):
                    log.debug(
                        "Query is not allowed in "
                        f"{player.channel.guild} ({player.channel.guild.id})")
                    continue
                valid = True

            track.extras = {"autoplay": True}
            player.add(player.channel.guild.me, track)
            self.bot.dispatch("red_audio_track_auto_play",
                              player.channel.guild, track,
                              player.channel.guild.me)
            if not player.current:
                await player.play()