Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
4a64f9b0b2 | |||
5c5a00483f | |||
dd873d5b11 | |||
03e7111604 | |||
b343d0d45b | |||
106692551b | |||
e578077df3 | |||
06034d0b37 | |||
b335e82699 | |||
378e1c9057 | |||
ca9e76d694 | |||
9221d04287 | |||
5c4e55c502 |
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Guava is a Discord music bot with support for multiple different music and video streaming platforms. Guava is a part of >200 Discord servers and currently supports these services:
|
Guava is a Discord music bot with support for multiple different music and video streaming platforms. Guava is a part of >225 Discord servers and currently supports these services:
|
||||||
|
|
||||||
- YouTube
|
- YouTube
|
||||||
- Apple Music
|
- Apple Music
|
||||||
@ -106,10 +106,11 @@ Field | Description | Requirement
|
|||||||
GENIUS_CLIENT_ID | `CLIENT ID`: ID from Genius API Dashboard | **OPTIONAL** - *Used for the /lyrics command*
|
GENIUS_CLIENT_ID | `CLIENT ID`: ID from Genius API Dashboard | **OPTIONAL** - *Used for the /lyrics command*
|
||||||
GENIUS_CLIENT_SECRET | `CLIENT SECRET`: Secret string from Genius API Dashboard | **OPTIONAL** - *Used for the /lyrics command*
|
GENIUS_CLIENT_SECRET | `CLIENT SECRET`: Secret string from Genius API Dashboard | **OPTIONAL** - *Used for the /lyrics command*
|
||||||
|
|
||||||
## OPENAI | OPTIONAL
|
## AI | OPTIONAL
|
||||||
Field | Description | Requirement
|
Field | Description | Requirement
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
OPENAI_API_KEY | API Key from OpenAI for autoplay recommendations | **OPTIONAL** - *Used to support the /autoplay feature*
|
SERVICE | Which providers API you will use. Supports `groq` or `openai` | **OPTIONAL** - *Used to support the /autoplay feature*
|
||||||
|
API_KEY | API key for the provider you are using | **OPTIONAL** - *Used to support the /autoplay feature*
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
211
application.yml
211
application.yml
@ -1,120 +1,123 @@
|
|||||||
server:
|
server:
|
||||||
port: 2333
|
port: 2333
|
||||||
address: localhost
|
address: 127.0.0.1
|
||||||
http2:
|
http2:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
youtube:
|
youtube:
|
||||||
enabled: true
|
enabled: true
|
||||||
allowSearch: true
|
allowSearch: true
|
||||||
allowDirectVideoIds: true
|
allowDirectVideoIds: true
|
||||||
allowDirectPlaylistIds: true
|
allowDirectPlaylistIds: true
|
||||||
clients:
|
clients:
|
||||||
- MUSIC
|
- MUSIC
|
||||||
- ANDROID_VR
|
- ANDROID_VR
|
||||||
- WEB
|
- WEB
|
||||||
- WEBEMBEDDED
|
- WEBEMBEDDED
|
||||||
pot:
|
pot: # https://github.com/iv-org/youtube-trusted-session-generator
|
||||||
token: "" # Your token data here
|
token: ""
|
||||||
visitorData: "" # Your visitor data here
|
visitorData: ""
|
||||||
lavasrc:
|
lavasrc:
|
||||||
providers:
|
providers:
|
||||||
- "scsearch:\"%ISRC%\""
|
- "scsearch:\"%ISRC%\""
|
||||||
- "scsearch:%QUERY%"
|
- "scsearch:%QUERY%"
|
||||||
- "dzisrc:\"%ISRC%\""
|
- "dzisrc:\"%ISRC%\""
|
||||||
- "dzsearch:%QUERY%"
|
- "dzsearch:%QUERY%"
|
||||||
sources:
|
sources:
|
||||||
spotify: false
|
spotify: false
|
||||||
applemusic: false
|
applemusic: false
|
||||||
deezer: true
|
deezer: true
|
||||||
yandexmusic: false
|
yandexmusic: false
|
||||||
flowerytts: falsee
|
flowerytts: false
|
||||||
youtube: false
|
youtube: false
|
||||||
deezer:
|
deezer:
|
||||||
masterDecryptionKey: "" # master decryption key from deezer
|
masterDecryptionKey: "" # Find on your own
|
||||||
|
arl: "" # Guides can be found, use Google
|
||||||
|
formats: [ "MP3_128", "MP3_64" ] # "FLAC", "MP3_320", "MP3_256", & "AAC_64" require premium and valid arl
|
||||||
|
|
||||||
lavalink:
|
lavalink:
|
||||||
plugins:
|
plugins:
|
||||||
- dependency: "dev.lavalink.youtube:youtube-plugin:1.11.1"
|
- dependency: "dev.lavalink.youtube:youtube-plugin:5ce67f60c656e0dc60687ea7b471663c8718ea1c"
|
||||||
snapshot: false
|
repository: "https://maven.kikkia.dev/snapshots"
|
||||||
- dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.3.0"
|
snapshot: true
|
||||||
snapshot: false
|
- dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.4.2"
|
||||||
|
snapshot: false
|
||||||
|
|
||||||
server:
|
server:
|
||||||
password: "youshallnotpass"
|
password: "youshallnotpass"
|
||||||
sources:
|
sources:
|
||||||
youtube: false
|
youtube: false
|
||||||
bandcamp: true
|
bandcamp: true
|
||||||
soundcloud: true
|
soundcloud: true
|
||||||
twitch: true
|
twitch: true
|
||||||
vimeo: true
|
vimeo: true
|
||||||
http: true
|
http: true
|
||||||
local: false
|
local: false
|
||||||
filters:
|
filters:
|
||||||
volume: false
|
volume: false
|
||||||
equalizer: false
|
equalizer: false
|
||||||
karaoke: false
|
karaoke: false
|
||||||
timescale: false
|
timescale: false
|
||||||
tremolo: false
|
tremolo: false
|
||||||
vibrato: false
|
vibrato: false
|
||||||
distortion: false
|
distortion: false
|
||||||
rotation: false
|
rotation: false
|
||||||
channelMix: false
|
channelMix: false
|
||||||
lowPass: false
|
lowPass: false
|
||||||
|
|
||||||
bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses. Duration <= 0 to disable JDA-NAS. Minimum of 40ms, lower values may introduce pauses.
|
bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses. Duration <= 0 to disable JDA-NAS. Minimum of 40ms, lower values may introduce pauses.
|
||||||
frameBufferDurationMs: 8000 # How many milliseconds of audio to keep buffered
|
frameBufferDurationMs: 8000 # How many milliseconds of audio to keep buffered
|
||||||
opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU.
|
opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU.
|
||||||
resamplingQuality: LOW # Quality of resampling operations. Valid values are LOW, MEDIUM and HIGH, where HIGH uses the most CPU.
|
resamplingQuality: LOW # Quality of resampling operations. Valid values are LOW, MEDIUM and HIGH, where HIGH uses the most CPU.
|
||||||
trackStuckThresholdMs: 10000 # The threshold for how long a track can be stuck. A track is stuck if does not return any audio data.
|
trackStuckThresholdMs: 10000 # The threshold for how long a track can be stuck. A track is stuck if does not return any audio data.
|
||||||
useSeekGhosting: true # Seek ghosting is the effect where whilst a seek is in progress, the audio buffer is read from until empty, or until seek is ready.
|
useSeekGhosting: true # Seek ghosting is the effect where whilst a seek is in progress, the audio buffer is read from until empty, or until seek is ready.
|
||||||
youtubePlaylistLoadLimit: 6 # Number of pages at 100 each
|
youtubePlaylistLoadLimit: 3 # Number of pages at 100 each
|
||||||
playerUpdateInterval: 5 # How frequently to send player updates to clients, in seconds
|
playerUpdateInterval: 5 # How frequently to send player updates to clients, in seconds
|
||||||
youtubeSearchEnabled: true
|
youtubeSearchEnabled: true
|
||||||
soundcloudSearchEnabled: true
|
soundcloudSearchEnabled: true
|
||||||
gc-warnings: true
|
gc-warnings: true
|
||||||
#ratelimit:
|
# ratelimit:
|
||||||
# ipBlocks: [""] # list of ip blocks
|
# ipBlocks: [""] # list of ip blocks
|
||||||
# excludedIps: [] # ips which should be explicit excluded from usage by lavalink
|
# excludedIps: [] # ips which should be explicit excluded from usage by lavalink
|
||||||
# strategy: "LoadBalance" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch
|
# strategy: "LoadBalance" # RotateOnBan | LoadBalance | NanoSwitch | RotatingNanoSwitch
|
||||||
# searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing
|
# searchTriggersFail: true # Whether a search 429 should trigger marking the ip as failing
|
||||||
# retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times
|
# retryLimit: -1 # -1 = use default lavaplayer value | 0 = infinity | >0 = retry will happen this numbers times
|
||||||
#httpConfig: # Useful for blocking bad-actors from ip-grabbing your music node and attacking it, this way only the http proxy will be attacked
|
# httpConfig: # Useful for blocking bad-actors from ip-grabbing your music node and attacking it, this way only the http proxy will be attacked
|
||||||
#proxyHost: "localhost" # Hostname of the proxy, (ip or domain)
|
# proxyHost: "localhost" # Hostname of the proxy, (ip or domain)
|
||||||
#proxyPort: 3128 # Proxy port, 3128 is the default for squidProxy
|
# proxyPort: 3128 # Proxy port, 3128 is the default for squidProxy
|
||||||
#proxyUser: "" # Optional user for basic authentication fields, leave blank if you don't use basic auth
|
# proxyUser: "" # Optional user for basic authentication fields, leave blank if you don't use basic auth
|
||||||
#proxyPassword: "" # Password for basic authentication
|
# proxyPassword: "" # Password for basic authentication
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
prometheus:
|
prometheus:
|
||||||
enabled: false
|
enabled: false
|
||||||
endpoint: /metrics
|
endpoint: /metrics
|
||||||
|
|
||||||
sentry:
|
sentry:
|
||||||
dsn: ""
|
dsn: ""
|
||||||
environment: ""
|
environment: ""
|
||||||
# tags:
|
# tags:
|
||||||
# some_key: some_value
|
# some_key: some_value
|
||||||
# another_key: another_value
|
# another_key: another_value
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
file:
|
file:
|
||||||
path: ./logs/
|
path: ./logs/
|
||||||
|
|
||||||
level:
|
level:
|
||||||
root: INFO
|
root: INFO
|
||||||
lavalink: INFO
|
lavalink: INFO
|
||||||
|
|
||||||
request:
|
request:
|
||||||
enabled: true
|
enabled: true
|
||||||
includeClientInfo: true
|
includeClientInfo: true
|
||||||
includeHeaders: false
|
includeHeaders: false
|
||||||
includeQueryString: true
|
includeQueryString: true
|
||||||
includePayload: true
|
includePayload: true
|
||||||
maxPayloadLength: 10000
|
maxPayloadLength: 10000
|
||||||
|
|
||||||
logback:
|
logback:
|
||||||
rollingpolicy:
|
rollingpolicy:
|
||||||
max-file-size: 1GB
|
max-file-size: 1GB
|
||||||
max-history: 30
|
max-history: 30
|
||||||
|
30
code/bot.py
30
code/bot.py
@ -2,7 +2,6 @@ import discord
|
|||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import openai
|
|
||||||
import lyricsgenius
|
import lyricsgenius
|
||||||
|
|
||||||
import utils.config as config
|
import utils.config as config
|
||||||
@ -20,19 +19,17 @@ class MyBot(commands.Bot):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def setup_hook(self):
|
async def setup_hook(self):
|
||||||
# Get Spotify, Apple Music, Genius, and OpenAI access tokens/clients
|
# Get Spotify, Apple Music, and Genius access tokens/clients
|
||||||
get_access_token.start()
|
get_access_token.start()
|
||||||
refresh_media_api_key.start()
|
refresh_media_api_key.start()
|
||||||
login_genius.start()
|
login_genius.start()
|
||||||
if config.OPENAI_API_KEY:
|
|
||||||
bot.openai = openai.OpenAI(api_key=config.OPENAI_API_KEY)
|
|
||||||
|
|
||||||
config.LOG.info("Loading cogs...")
|
config.LOG.info("Loading cogs...")
|
||||||
config.LOG.info(
|
if config.YOUTUBE_SUPPORT:
|
||||||
"YouTube support is enabled, make sure to set a poToken"
|
config.LOG.info(
|
||||||
if config.YOUTUBE_SUPPORT
|
"YouTube support is enabled, make sure to set a poToken"
|
||||||
else "YouTube support is disabled"
|
)
|
||||||
)
|
else:
|
||||||
|
config.LOG.warn("YouTube support is disabled")
|
||||||
for ext in os.listdir("./code/cogs"):
|
for ext in os.listdir("./code/cogs"):
|
||||||
if ext.endswith(".py"):
|
if ext.endswith(".py"):
|
||||||
# Load the OPTIONAL feedback cog
|
# Load the OPTIONAL feedback cog
|
||||||
@ -40,28 +37,28 @@ class MyBot(commands.Bot):
|
|||||||
ext[:-3] == "feedback"
|
ext[:-3] == "feedback"
|
||||||
and config.FEEDBACK_CHANNEL_ID == None
|
and config.FEEDBACK_CHANNEL_ID == None
|
||||||
):
|
):
|
||||||
config.LOG.info(
|
config.LOG.warn(
|
||||||
"Skipped loading feedback cog - channel ID not"
|
"Skipped loading feedback cog - channel ID not"
|
||||||
" provided"
|
" provided"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
# Load the OPTIONAL bug cog
|
# Load the OPTIONAL bug cog
|
||||||
if ext[:-3] == "bug" and config.BUG_CHANNEL_ID == None:
|
if ext[:-3] == "bug" and config.BUG_CHANNEL_ID == None:
|
||||||
config.LOG.info(
|
config.LOG.warn(
|
||||||
"Skipped loading bug cog - channel ID not provided"
|
"Skipped loading bug cog - channel ID not provided"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
# Load the OPTIONAL lyrics cog
|
# Load the OPTIONAL lyrics cog
|
||||||
if ext[:-3] == "lyrics" and config.GENIUS_CLIENT_ID == None:
|
if ext[:-3] == "lyrics" and config.GENIUS_CLIENT_ID == None:
|
||||||
config.LOG.info(
|
config.LOG.warn(
|
||||||
"Skipped loading lyrics cog - Genius API credentials"
|
"Skipped loading lyrics cog - Genius API credentials"
|
||||||
" not provided"
|
" not provided"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
# Load the OPTIONAL autoplay cog
|
# Load the OPTIONAL autoplay cog
|
||||||
if ext[:-3] == "autoplay" and config.OPENAI_API_KEY == None:
|
if ext[:-3] == "autoplay" and config.AI_CLIENT == None:
|
||||||
config.LOG.info(
|
config.LOG.warn(
|
||||||
"Skipped loading autoplay cog - OpenAI API credentials"
|
"Skipped loading autoplay cog - AI API credentials"
|
||||||
" not provided"
|
" not provided"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -83,6 +80,7 @@ bot = MyBot()
|
|||||||
bot.remove_command("help")
|
bot.remove_command("help")
|
||||||
bot.temp_command_count = {} # command_name: count
|
bot.temp_command_count = {} # command_name: count
|
||||||
bot.autoplay = [] # guild_id, guild_id, etc.
|
bot.autoplay = [] # guild_id, guild_id, etc.
|
||||||
|
bot.youtube_broken = False
|
||||||
|
|
||||||
|
|
||||||
@tasks.loop(minutes=45)
|
@tasks.loop(minutes=45)
|
||||||
|
@ -75,9 +75,7 @@ class Autoplay(commands.Cog):
|
|||||||
)
|
)
|
||||||
await interaction.response.send_message(embed=embed)
|
await interaction.response.send_message(embed=embed)
|
||||||
|
|
||||||
if await add_song_recommendations(
|
if await add_song_recommendations(self.bot.user, player, 5, inputs):
|
||||||
self.bot.openai, self.bot.user, player, 5, inputs
|
|
||||||
):
|
|
||||||
self.bot.autoplay.append(interaction.guild.id)
|
self.bot.autoplay.append(interaction.guild.id)
|
||||||
embed = create_embed(
|
embed = create_embed(
|
||||||
title=":infinity: Autoplay Enabled :infinity:",
|
title=":infinity: Autoplay Enabled :infinity:",
|
||||||
|
@ -56,7 +56,8 @@ commands_and_descriptions = {
|
|||||||
},
|
},
|
||||||
"autoplay": {
|
"autoplay": {
|
||||||
"description": (
|
"description": (
|
||||||
"Keep the music playing forever with music suggestions from OpenAI"
|
"Keep the music playing forever with automatic song"
|
||||||
|
" recommendations"
|
||||||
),
|
),
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"on": "Turn autoplay feature on",
|
"on": "Turn autoplay feature on",
|
||||||
|
@ -245,7 +245,7 @@ class Music(commands.Cog):
|
|||||||
for song in event.player.queue[:10]:
|
for song in event.player.queue[:10]:
|
||||||
inputs[song.title] = song.author
|
inputs[song.title] = song.author
|
||||||
await add_song_recommendations(
|
await add_song_recommendations(
|
||||||
self.bot.openai, self.bot.user, event.player, 5, inputs
|
self.bot.user, event.player, 5, inputs
|
||||||
)
|
)
|
||||||
|
|
||||||
@lavalink.listener(lavalink.events.NodeConnectedEvent)
|
@lavalink.listener(lavalink.events.NodeConnectedEvent)
|
||||||
|
29
code/cogs/owner/send.py
Normal file
29
code/cogs/owner/send.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
|
class Send(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.dm_only()
|
||||||
|
@commands.is_owner()
|
||||||
|
async def send(self, ctx, user_id: int, *, message: str):
|
||||||
|
"""Send a message to a user (follow-up on bug reports)"""
|
||||||
|
user = await self.bot.fetch_user(user_id)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return await ctx.send("User not found.")
|
||||||
|
|
||||||
|
elif not message:
|
||||||
|
return await ctx.send("No message for user.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
await user.send(message)
|
||||||
|
await ctx.send("Message sent to user.")
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send("Error sending message to user.")
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(Send(bot))
|
31
code/cogs/owner/toggle.py
Normal file
31
code/cogs/owner/toggle.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from discord.ext import commands
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
|
||||||
|
class Toggle(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
@commands.dm_only()
|
||||||
|
@commands.is_owner()
|
||||||
|
async def toggle(self, ctx, action: Literal["on", "off"]):
|
||||||
|
"""Toggle YouTube as broken or not"""
|
||||||
|
if action == "on":
|
||||||
|
self.bot.youtube_broken = False
|
||||||
|
return await ctx.send("YouTube has been enabled.")
|
||||||
|
|
||||||
|
if action == "off":
|
||||||
|
self.bot.youtube_broken = True
|
||||||
|
return await ctx.send("YouTube has been marked as broken.")
|
||||||
|
|
||||||
|
@toggle.error
|
||||||
|
async def toggle_error(self, ctx, error):
|
||||||
|
if isinstance(error, commands.BadLiteralArgument):
|
||||||
|
return await ctx.send("Invalid action. Use either 'on' or 'off'.")
|
||||||
|
else:
|
||||||
|
return await ctx.send("An unknown error occurred.")
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(Toggle(bot))
|
@ -45,6 +45,22 @@ class Play(commands.Cog):
|
|||||||
embed=embed, ephemeral=True
|
embed=embed, ephemeral=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.bot.youtube_broken:
|
||||||
|
embed = create_embed(
|
||||||
|
title="YouTube Broken",
|
||||||
|
description=(
|
||||||
|
"YouTube support is currently broken. This is a known"
|
||||||
|
" issue and is being actively worked on, please try"
|
||||||
|
" again later. Other sources should still be in"
|
||||||
|
" working order. Submit a bug report with "
|
||||||
|
" </bug:1224840889906499626> if issues persist. Sorry"
|
||||||
|
" for the inconvenience."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
embed=embed, ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
# Check for custom sources (Apple Music/Spotify)
|
# Check for custom sources (Apple Music/Spotify)
|
||||||
if "music.apple.com" in query:
|
if "music.apple.com" in query:
|
||||||
results, embed = await parse_custom_source(
|
results, embed = await parse_custom_source(
|
||||||
|
@ -1,40 +1,52 @@
|
|||||||
from lavalink import LoadType
|
from lavalink import LoadType
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from utils.config import AI_CLIENT, AI_MODEL
|
||||||
|
|
||||||
|
|
||||||
async def add_song_recommendations(
|
async def add_song_recommendations(
|
||||||
openai_client, bot_user, player, number, inputs, retries: int = 1
|
bot_user, player, number, inputs, retries: int = 1
|
||||||
):
|
):
|
||||||
input_list = [f'"{song} by {artist}"' for song, artist in inputs.items()]
|
input_list = [f'"{song} by {artist}"' for song, artist in inputs.items()]
|
||||||
|
|
||||||
completion = (
|
completion = (
|
||||||
openai_client.chat.completions.create(
|
AI_CLIENT.chat.completions.create(
|
||||||
messages=[
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": f"""
|
||||||
|
Given an input list of songs formatted as ["song_name
|
||||||
|
by artist_name", "song_name by artist_name", ...], generate
|
||||||
|
a list of 5 new songs that the user may enjoy based on
|
||||||
|
the input.
|
||||||
|
|
||||||
|
Thoroughly analyze each song in the input list, considering
|
||||||
|
factors such as tempo, beat, mood, genre, lyrical themes,
|
||||||
|
instrumentation, and overall meaning. Use this analysis to
|
||||||
|
recommend 5 songs that closely align with the user's musical
|
||||||
|
preferences.
|
||||||
|
|
||||||
|
The output must be formatted in the exact same way:
|
||||||
|
["song_name by artist_name", "song_name by artist_name", ...].
|
||||||
|
|
||||||
|
If you are unable to find 5 new songs or encounter any issues,
|
||||||
|
return the following list instead: ["NOTHING_FOUND"]. Do
|
||||||
|
not return partial results—either provide 5 songs or return
|
||||||
|
["NOTHING_FOUND"]. Ensure accuracy in song and artist names.
|
||||||
|
|
||||||
|
DO NOT include any additional information or text in the
|
||||||
|
output, it should STRICTLY be either a list of the songs
|
||||||
|
or ["NOTHING_FOUND"].
|
||||||
|
""",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": f"""
|
"content": f"""
|
||||||
BACKGROUND: You're an AI music recommendation system with a knack for understanding
|
{input_list}
|
||||||
user preferences based on provided input. Your task is to generate a list
|
|
||||||
of {number} songs that the user might enjoy, derived from a given list of {number} songs.
|
|
||||||
The input will be in the format of
|
|
||||||
["Song-1-Name by Song-1-Artist", "Song-2-Name by Song-2-Artist", ...]
|
|
||||||
and you need to return a list formatted in the same way.
|
|
||||||
|
|
||||||
When recommending songs, consider the genre, tempo, and mood of the input
|
|
||||||
songs to suggest similar ones that align with the user's tastes. Also, it
|
|
||||||
is important to mix up the artists, don't only give the same artists that
|
|
||||||
are already in the queue. If you cannot find {number} songs that match the
|
|
||||||
criteria or encounter any issues, return the list ["NOTHING FOUND"].
|
|
||||||
|
|
||||||
Please be sure to also only use characters A-Z, a-z, 0-9, and spaces in the
|
|
||||||
song and artist names. Do not include escape/special characters, emojis, or
|
|
||||||
quotes in the output.
|
|
||||||
|
|
||||||
INPUT: {input_list}
|
|
||||||
""",
|
""",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
model="gpt-4o-mini",
|
model=AI_MODEL,
|
||||||
)
|
)
|
||||||
.choices[0]
|
.choices[0]
|
||||||
.message.content.strip()
|
.message.content.strip()
|
||||||
@ -47,7 +59,7 @@ async def add_song_recommendations(
|
|||||||
if completion == '["NOTHING FOUND"]':
|
if completion == '["NOTHING FOUND"]':
|
||||||
if retries <= 3:
|
if retries <= 3:
|
||||||
await add_song_recommendations(
|
await add_song_recommendations(
|
||||||
openai_client, bot_user, player, number, inputs, retries + 1
|
bot_user, player, number, inputs, retries + 1
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -8,7 +8,7 @@ import sys
|
|||||||
import discord
|
import discord
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from groq import Groq
|
||||||
from colorlog import ColoredFormatter
|
from colorlog import ColoredFormatter
|
||||||
|
|
||||||
log_level = logging.DEBUG
|
log_level = logging.DEBUG
|
||||||
@ -39,7 +39,8 @@ SPOTIFY_CLIENT_ID = None
|
|||||||
SPOTIFY_CLIENT_SECRET = None
|
SPOTIFY_CLIENT_SECRET = None
|
||||||
GENIUS_CLIENT_ID = None
|
GENIUS_CLIENT_ID = None
|
||||||
GENIUS_CLIENT_SECRET = None
|
GENIUS_CLIENT_SECRET = None
|
||||||
OPENAI_API_KEY = None
|
AI_CLIENT = None
|
||||||
|
AI_MODEL = None
|
||||||
LAVALINK_HOST = None
|
LAVALINK_HOST = None
|
||||||
LAVALINK_PORT = None
|
LAVALINK_PORT = None
|
||||||
LAVALINK_PASSWORD = None
|
LAVALINK_PASSWORD = None
|
||||||
@ -82,12 +83,13 @@ schema = {
|
|||||||
},
|
},
|
||||||
"required": ["genius_client_id", "genius_client_secret"],
|
"required": ["genius_client_id", "genius_client_secret"],
|
||||||
},
|
},
|
||||||
"openai": {
|
"ai": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"openai_api_key": {"type": "string"},
|
"service": {"enum": ["openai", "groq"]},
|
||||||
|
"api_key": {"type": "string"},
|
||||||
},
|
},
|
||||||
"required": ["openai_api_key"],
|
"required": ["service"],
|
||||||
},
|
},
|
||||||
"lavalink": {
|
"lavalink": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -144,9 +146,9 @@ genius:
|
|||||||
genius_client_id:
|
genius_client_id:
|
||||||
genius_client_secret:
|
genius_client_secret:
|
||||||
|
|
||||||
openai:
|
ai:
|
||||||
openai_api_key:
|
service:
|
||||||
"""
|
api_key: """
|
||||||
)
|
)
|
||||||
|
|
||||||
sys.exit(
|
sys.exit(
|
||||||
@ -160,7 +162,7 @@ openai:
|
|||||||
|
|
||||||
# Thouroughly validate all of the options in the config.yaml file
|
# Thouroughly validate all of the options in the config.yaml file
|
||||||
def validate_config(file_contents):
|
def validate_config(file_contents):
|
||||||
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, LOG_SONGS, YOUTUBE_SUPPORT, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, GENIUS_CLIENT_ID, GENIUS_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, LOG_SONGS, YOUTUBE_SUPPORT, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, GENIUS_CLIENT_ID, GENIUS_CLIENT_SECRET, AI_CLIENT, AI_MODEL, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
||||||
config = yaml.safe_load(file_contents)
|
config = yaml.safe_load(file_contents)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -270,17 +272,24 @@ def validate_config(file_contents):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# If the OPENAI section is present, make sure the API key is valid
|
# If the AI section is present, make sure the API key is valid
|
||||||
#
|
#
|
||||||
|
|
||||||
if "openai" in config:
|
if "ai" in config:
|
||||||
client = openai.OpenAI(api_key=config["openai"]["openai_api_key"])
|
if config["ai"]["service"] == "openai":
|
||||||
|
client = openai.OpenAI(api_key=config["ai"]["api_key"])
|
||||||
|
model = "gpt-4o-mini"
|
||||||
|
elif config["ai"]["service"] == "groq":
|
||||||
|
client = Groq(api_key=config["ai"]["api_key"])
|
||||||
|
model = "llama-3.3-70b-specdec"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.models.list()
|
client.models.list()
|
||||||
OPENAI_API_KEY = config["openai"]["openai_api_key"]
|
AI_CLIENT = client
|
||||||
|
AI_MODEL = model
|
||||||
except openai.AuthenticationError:
|
except openai.AuthenticationError:
|
||||||
LOG.critical(
|
LOG.critical(
|
||||||
"Error in config.yaml file: OpenAI API key is invalid"
|
"Error in config.yaml file: OpenAI/Groq API key is invalid"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set appropriate values for all non-optional variables
|
# Set appropriate values for all non-optional variables
|
||||||
|
@ -22,5 +22,6 @@ genius:
|
|||||||
genius_client_id:
|
genius_client_id:
|
||||||
genius_client_secret:
|
genius_client_secret:
|
||||||
|
|
||||||
openai:
|
ai:
|
||||||
openai_api_key:
|
service:
|
||||||
|
api_key:
|
@ -4,6 +4,7 @@ colorlog==6.8.2
|
|||||||
validators==0.28.3
|
validators==0.28.3
|
||||||
openai==1.56.0
|
openai==1.56.0
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
lyricsgenius==3.0.1
|
lyricsgenius==3.2.0
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
jsonschema==4.23.0
|
jsonschema==4.23.0
|
||||||
|
groq==0.18.0
|
Loading…
x
Reference in New Issue
Block a user