From 66bf2a6cc21590a938a03913f36586744c871f57 Mon Sep 17 00:00:00 2001 From: Parker Date: Fri, 12 Apr 2024 00:29:24 -0500 Subject: [PATCH] Create autoplay feature Use ChatGPT to get song recommendations based on current song queue. --- code/ai_recommendations.py | 74 +++++++++++++++++++++++++++++ code/bot.py | 1 + code/cogs/autoplay.py | 95 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 code/ai_recommendations.py create mode 100644 code/cogs/autoplay.py diff --git a/code/ai_recommendations.py b/code/ai_recommendations.py new file mode 100644 index 0000000..3aa2da5 --- /dev/null +++ b/code/ai_recommendations.py @@ -0,0 +1,74 @@ +from lavalink import LoadType + +from global_variables import CLIENT + + +async def add_song_recommendations(bot_user, player, number, inputs, retries: int = 1): + input_string = "" + for song, artist in inputs.items(): + input_string += f"{song} - {artist}, " + # Remove the final ", " + input_string = input_string[:-2] + + completion = ( + CLIENT.chat.completions.create( + messages=[ + { + "role": "user", + "content": f"""I need songs that are similar in nature to ones that I list. + Send {number} songs formatted as: + + SONG NAME - ARTIST NAME + SONG NAME - ARTIST NAME + ... + + Do not provide anything except for the exactly what I need, no + list numbers, no quotations, only what I have shown. + + The songs you should base the list off of are: {input_string} + + NOTE: If you believe that there are not many songs that are similar to the ones I list, then please just respond with the message "SONG FIND ERROR" + """, + } + ], + model="gpt-3.5-turbo", + ) + .choices[0] + .message.content.strip() + .strip('"') + ) + + # Sometimes, we get false failures, so we check for a failure, and it we haven't tried + # at least 3 times, then continue retrying, otherwise, we actually can't get any songs + if completion == "SONG FIND ERROR": + if retries <= 3: + await add_song_recommendations( + bot_user, player, number, inputs, retries + 1 + ) + else: + return False + + else: + for entry in completion.split("\n"): + song, artist = entry.split(" - ") + + ytsearch = f"ytsearch:{song} {artist} audio" + results = await player.node.get_tracks(ytsearch) + + if not results.tracks or results.load_type in ( + LoadType.EMPTY, + LoadType.ERROR, + ): + dzsearch = f"dzsearch:{song} {artist}" + results = await player.node.get_tracks(dzsearch) + + if not results.tracks or results.load_type in ( + LoadType.EMPTY, + LoadType.ERROR, + ): + continue + + track = results.tracks[0] + player.add(requester=bot_user, track=track) + + return True diff --git a/code/bot.py b/code/bot.py index b1996ab..983e92f 100644 --- a/code/bot.py +++ b/code/bot.py @@ -29,6 +29,7 @@ class MyBot(commands.Bot): bot = MyBot() bot.remove_command("help") bot.temp_command_count = {} # command_name: count +bot.autoplay = [] # guild_id, guild_id, etc. @bot.event diff --git a/code/cogs/autoplay.py b/code/cogs/autoplay.py new file mode 100644 index 0000000..cc5de91 --- /dev/null +++ b/code/cogs/autoplay.py @@ -0,0 +1,95 @@ +import discord +import datetime +from discord import app_commands +from discord.ext import commands +from cogs.music import Music +from typing import Literal +from ai_recommendations import add_song_recommendations + +from global_variables import BOT_COLOR + + +class Autoplay(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @app_commands.command() + @app_commands.check(Music.create_player) + @app_commands.describe(toggle="Turn autoplay ON or OFF") + async def autoplay( + self, interaction: discord.Interaction, toggle: Literal["ON", "OFF"] + ): + "Keep the music playing forever with music suggestions from OpenAI" + if toggle == "OFF": + self.bot.autoplay.remove(interaction.guild.id) + + embed = discord.Embed( + title="Autoplay Off", + description="Autoplay has been turned off. I will no longer automatically add new songs to the queue based on AI recommendations.", + color=BOT_COLOR, + ) + return await interaction.response.send_message(embed=embed) + + # Otherwise, toggle must be "ON", so enable autoplaying + + if interaction.guild.id in self.bot.autoplay: + embed = discord.Embed( + title="Autoplay Already Enabled", + description="Autoplay is already enabled. If you would like to turn it off, choose the `OFF` option in the `/autoplay` command.", + color=BOT_COLOR, + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + + player = self.bot.lavalink.player_manager.get(interaction.guild.id) + + if len(player.queue) < 5: + embed = discord.Embed( + title="Not Enough Context", + description="You must have at least 5 songs in the queue so that I can get a good understanding of what music I should continue to play. Add some more music to the queue, then try again.", + color=BOT_COLOR, + ) + embed.set_footer( + text=datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + + inputs = {} + for song in player.queue[:10]: + inputs[song.title] = song.author + + embed = discord.Embed( + title="Getting AI Recommendations", + description="Attempting to generate recommendations based on the current songs in your queue. Just a moment...", + color=BOT_COLOR, + ) + await interaction.response.send_message(embed=embed) + + if await add_song_recommendations(self.bot.user, player, 5, inputs): + self.bot.autoplay.append(interaction.guild.id) + embed = discord.Embed( + title=":infinity: Autoplay Enabled :infinity:", + description=f"I have added a few similar songs to the queue and will continue to do so once the queue gets low again. Now just sit back and enjoy the music!\n\nEnabled by: {interaction.user.mention}", + color=BOT_COLOR, + ) + await interaction.edit_original_response(embed=embed) + + else: + embed = discord.Embed( + title="Autoplay Error", + description="Autoplay is an experimental feature, meaning sometimes it doesn't work as expected. I had an error when attempting to get similar songs for you, please try running the command again. If the issue persists, fill out a bug report with the command.", + color=BOT_COLOR, + ) + embed.set_footer( + text=datetime.datetime.now(datetime.timezone.utc).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + await interaction.edit_original_response(embed=embed) + + +async def setup(bot): + await bot.add_cog(Autoplay(bot))