Automatically retrieve Apple Music media API key
This commit is contained in:
parent
fda03dff62
commit
d6a713a948
13
README.md
13
README.md
@ -72,24 +72,11 @@ FEEDBACK_CHANNEL_ID | `CHANNEL ID`: Discord channel for feedback messages to be
|
|||||||
BUG_CHANNEL_ID | `CHANNEL ID`: Discord channel for bug messages to be sent to | **OPTIONAL**
|
BUG_CHANNEL_ID | `CHANNEL ID`: Discord channel for bug messages to be sent to | **OPTIONAL**
|
||||||
SPOTIFY_CLIENT_ID | Client ID from Spotify Developer account | **REQUIRED**
|
SPOTIFY_CLIENT_ID | Client ID from Spotify Developer account | **REQUIRED**
|
||||||
SPOTIFY_CLIENT_SECRET | Client Secret from Spotify Developer account | **REQUIRED**
|
SPOTIFY_CLIENT_SECRET | Client Secret from Spotify Developer account | **REQUIRED**
|
||||||
APPLE_MUSIC_KEY | See the `note` below to get a media api key without a developer account | **REQUIRED**
|
|
||||||
OPENAI_API_KEY | API Key from OpenAI for autoplay recommendations | **REQUIRED**
|
OPENAI_API_KEY | API Key from OpenAI for autoplay recommendations | **REQUIRED**
|
||||||
HOST | Host address for your Lavalink node | **REQUIRED**
|
HOST | Host address for your Lavalink node | **REQUIRED**
|
||||||
PORT | Port for your Lavalink node | **REQUIRED**
|
PORT | Port for your Lavalink node | **REQUIRED**
|
||||||
PASSWORD | Password to authenticate into the Lavalink node | **REQUIRED**
|
PASSWORD | Password to authenticate into the Lavalink node | **REQUIRED**
|
||||||
|
|
||||||
<br>
|
|
||||||
<details>
|
|
||||||
<summary><strong>NOTE: Media API Key</strong></summary>
|
|
||||||
|
|
||||||
1. Go to https://music.apple.com
|
|
||||||
2. Open the debuger tab in dev tools
|
|
||||||
3. Regex this `"(?<token>(ey[\w-]+)\.([\w-]+)\.([\w-]+))"`
|
|
||||||
4. Copy the entire token from the JS file
|
|
||||||
|
|
||||||
</details>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
# Lavalink Information
|
# Lavalink Information
|
||||||
|
|
||||||
As previously state, a Lavalink node running at least `v4` with the LavaSrc plugin is required. Due to the plugin requirement, it is unlikely that you will be able to use a free/public Lavalink node.
|
As previously state, a Lavalink node running at least `v4` with the LavaSrc plugin is required. Due to the plugin requirement, it is unlikely that you will be able to use a free/public Lavalink node.
|
||||||
|
14
code/bot.py
14
code/bot.py
@ -6,6 +6,7 @@ import openai
|
|||||||
|
|
||||||
import utils.config as config
|
import utils.config as config
|
||||||
from utils.command_tree import Tree
|
from utils.command_tree import Tree
|
||||||
|
from utils.media_api_key import get_media_api_key
|
||||||
|
|
||||||
|
|
||||||
class MyBot(commands.Bot):
|
class MyBot(commands.Bot):
|
||||||
@ -19,6 +20,7 @@ class MyBot(commands.Bot):
|
|||||||
|
|
||||||
async def setup_hook(self):
|
async def setup_hook(self):
|
||||||
get_access_token.start()
|
get_access_token.start()
|
||||||
|
refresh_media_api_key.start()
|
||||||
config.LOG.info("Loading cogs...")
|
config.LOG.info("Loading cogs...")
|
||||||
for ext in os.listdir("./code/cogs"):
|
for ext in os.listdir("./code/cogs"):
|
||||||
if ext.endswith(".py"):
|
if ext.endswith(".py"):
|
||||||
@ -59,6 +61,18 @@ async def get_access_token():
|
|||||||
bot.spotify_headers = {"Authorization": f"Bearer {access_token}"}
|
bot.spotify_headers = {"Authorization": f"Bearer {access_token}"}
|
||||||
|
|
||||||
|
|
||||||
|
@tasks.loop(hours=24)
|
||||||
|
async def refresh_media_api_key():
|
||||||
|
media_api_key = get_media_api_key()
|
||||||
|
if media_api_key is not None:
|
||||||
|
bot.apple_headers = {
|
||||||
|
"Authorization": f"Bearer {media_api_key}",
|
||||||
|
"Origin": "https://apple.com",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
bot.apple_headers = None
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
config.load_config()
|
config.load_config()
|
||||||
bot.run(config.TOKEN)
|
bot.run(config.TOKEN)
|
||||||
|
@ -7,17 +7,12 @@ import re
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from cogs.music import Music, LavalinkVoiceClient
|
from cogs.music import Music, LavalinkVoiceClient
|
||||||
from utils.config import BOT_COLOR, APPLE_MUSIC_KEY
|
from utils.config import BOT_COLOR
|
||||||
from utils.custom_sources import SpotifySource, AppleSource
|
from utils.custom_sources import SpotifySource, AppleSource
|
||||||
|
|
||||||
|
|
||||||
url_rx = re.compile(r"https?://(?:www\.)?.+")
|
url_rx = re.compile(r"https?://(?:www\.)?.+")
|
||||||
|
|
||||||
apple_headers = {
|
|
||||||
"Authorization": f"Bearer {APPLE_MUSIC_KEY}",
|
|
||||||
"Origin": "https://apple.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Play(commands.Cog):
|
class Play(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
@ -45,18 +40,26 @@ class Play(commands.Cog):
|
|||||||
###
|
###
|
||||||
|
|
||||||
if "music.apple.com" in query:
|
if "music.apple.com" in query:
|
||||||
|
if not self.bot.apple_headers:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Apple Music Error",
|
||||||
|
description="Apple Music support seems to be broken at the moment. Please try again and fill out a bug report with </bug:1224840889906499626> if this continues to happen.",
|
||||||
|
color=BOT_COLOR,
|
||||||
|
)
|
||||||
|
return await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
embed = discord.Embed(color=BOT_COLOR)
|
embed = discord.Embed(color=BOT_COLOR)
|
||||||
|
|
||||||
if "/playlist/" in query and "?i=" not in query:
|
if "/playlist/" in query and "?i=" not in query:
|
||||||
playlist_id = query.split("/playlist/")[1].split("/")[1]
|
playlist_id = query.split("/playlist/")[1].split("/")[1]
|
||||||
# Get all of the tracks in the playlist (limit at 250)
|
# Get all of the tracks in the playlist (limit at 250)
|
||||||
playlist_url = f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}/tracks?limit=100"
|
playlist_url = f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}/tracks?limit=100"
|
||||||
response = requests.get(playlist_url, headers=apple_headers)
|
response = requests.get(playlist_url, headers=self.bot.apple_headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
playlist = response.json()
|
playlist = response.json()
|
||||||
# Get the general playlist info (name, artwork)
|
# Get the general playlist info (name, artwork)
|
||||||
playlist_info_url = f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}"
|
playlist_info_url = f"https://api.music.apple.com/v1/catalog/us/playlists/{playlist_id}"
|
||||||
playlist_info = requests.get(playlist_info_url, headers=apple_headers)
|
playlist_info = requests.get(playlist_info_url, headers=self.bot.apple_headers)
|
||||||
playlist_info = playlist_info.json()
|
playlist_info = playlist_info.json()
|
||||||
try:
|
try:
|
||||||
artwork_url = playlist_info["data"][0]["attributes"]["artwork"]["url"].replace(
|
artwork_url = playlist_info["data"][0]["attributes"]["artwork"]["url"].replace(
|
||||||
@ -90,7 +93,7 @@ class Play(commands.Cog):
|
|||||||
if "/album/" in query and "?i=" not in query:
|
if "/album/" in query and "?i=" not in query:
|
||||||
album_id = query.split("/album/")[1].split("/")[1]
|
album_id = query.split("/album/")[1].split("/")[1]
|
||||||
album_url = f"https://api.music.apple.com/v1/catalog/us/albums/{album_id}"
|
album_url = f"https://api.music.apple.com/v1/catalog/us/albums/{album_id}"
|
||||||
response = requests.get(album_url, headers=apple_headers)
|
response = requests.get(album_url, headers=self.bot.apple_headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
album = response.json()
|
album = response.json()
|
||||||
|
|
||||||
@ -117,7 +120,7 @@ class Play(commands.Cog):
|
|||||||
if "/album/" in query and "?i=" in query:
|
if "/album/" in query and "?i=" in query:
|
||||||
song_id = query.split("/album/")[1].split("?i=")[1]
|
song_id = query.split("/album/")[1].split("?i=")[1]
|
||||||
song_url = f"https://api.music.apple.com/v1/catalog/us/songs/{song_id}"
|
song_url = f"https://api.music.apple.com/v1/catalog/us/songs/{song_id}"
|
||||||
response = requests.get(song_url, headers=apple_headers)
|
response = requests.get(song_url, headers=self.bot.apple_headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
song = response.json()
|
song = response.json()
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import os
|
|||||||
import validators
|
import validators
|
||||||
import sys
|
import sys
|
||||||
import discord
|
import discord
|
||||||
import openai
|
|
||||||
import logging
|
import logging
|
||||||
from colorlog import ColoredFormatter
|
from colorlog import ColoredFormatter
|
||||||
|
|
||||||
@ -31,7 +30,6 @@ FEEDBACK_CHANNEL_ID = None
|
|||||||
BUG_CHANNEL_ID = None
|
BUG_CHANNEL_ID = None
|
||||||
SPOTIFY_CLIENT_ID = None
|
SPOTIFY_CLIENT_ID = None
|
||||||
SPOTIFY_CLIENT_SECRET = None
|
SPOTIFY_CLIENT_SECRET = None
|
||||||
APPLE_MUSIC_KEY = None
|
|
||||||
OPENAI_API_KEY = None
|
OPENAI_API_KEY = None
|
||||||
LAVALINK_HOST = None
|
LAVALINK_HOST = None
|
||||||
LAVALINK_PORT = None
|
LAVALINK_PORT = None
|
||||||
@ -71,10 +69,6 @@ def load_config():
|
|||||||
"SPOTIFY_CLIENT_SECRET": "",
|
"SPOTIFY_CLIENT_SECRET": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
config["APPLE_MUSIC"] = {
|
|
||||||
"APPLE_MUSIC_KEY": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
config["OPENAI"] = {
|
config["OPENAI"] = {
|
||||||
"OPENAI_API_KEY": "",
|
"OPENAI_API_KEY": "",
|
||||||
}
|
}
|
||||||
@ -101,7 +95,7 @@ Validate all of the options in the config.ini file.
|
|||||||
|
|
||||||
|
|
||||||
def validate_config(file_contents):
|
def validate_config(file_contents):
|
||||||
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, APPLE_MUSIC_KEY, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read_string(file_contents)
|
config.read_string(file_contents)
|
||||||
|
|
||||||
@ -111,7 +105,7 @@ def validate_config(file_contents):
|
|||||||
errors = 0
|
errors = 0
|
||||||
|
|
||||||
# Make sure all sections are present
|
# Make sure all sections are present
|
||||||
if ["BOT_INFO", "SPOTIFY", "APPLE_MUSIC", "OPENAI", "LAVALINK"] != config.sections():
|
if ["BOT_INFO", "SPOTIFY", "OPENAI", "LAVALINK"] != config.sections():
|
||||||
sys.exit(
|
sys.exit(
|
||||||
LOG.critical(
|
LOG.critical(
|
||||||
"Missing sections in config.ini file. Delete the file and re-run the bot to generate a blank config.ini file."
|
"Missing sections in config.ini file. Delete the file and re-run the bot to generate a blank config.ini file."
|
||||||
@ -132,13 +126,6 @@ def validate_config(file_contents):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ["apple_music_key"] != config.options("APPLE_MUSIC"):
|
|
||||||
sys.exit(
|
|
||||||
LOG.critical(
|
|
||||||
"Missing options in APPLE_MUSIC section of config.ini file. Delete the file and re-run the bot to generate a blank config.ini file."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if ["openai_api_key"] != config.options("OPENAI"):
|
if ["openai_api_key"] != config.options("OPENAI"):
|
||||||
sys.exit(
|
sys.exit(
|
||||||
LOG.critical(
|
LOG.critical(
|
||||||
@ -187,7 +174,6 @@ def validate_config(file_contents):
|
|||||||
TOKEN = config["BOT_INFO"]["TOKEN"]
|
TOKEN = config["BOT_INFO"]["TOKEN"]
|
||||||
SPOTIFY_CLIENT_ID = config["SPOTIFY"]["SPOTIFY_CLIENT_ID"]
|
SPOTIFY_CLIENT_ID = config["SPOTIFY"]["SPOTIFY_CLIENT_ID"]
|
||||||
SPOTIFY_CLIENT_SECRET = config["SPOTIFY"]["SPOTIFY_CLIENT_SECRET"]
|
SPOTIFY_CLIENT_SECRET = config["SPOTIFY"]["SPOTIFY_CLIENT_SECRET"]
|
||||||
APPLE_MUSIC_KEY = config["APPLE_MUSIC"]["APPLE_MUSIC_KEY"]
|
|
||||||
OPENAI_API_KEY = config["OPENAI"]["OPENAI_API_KEY"]
|
OPENAI_API_KEY = config["OPENAI"]["OPENAI_API_KEY"]
|
||||||
LAVALINK_HOST = config["LAVALINK"]["HOST"]
|
LAVALINK_HOST = config["LAVALINK"]["HOST"]
|
||||||
LAVALINK_PORT = config["LAVALINK"]["PORT"]
|
LAVALINK_PORT = config["LAVALINK"]["PORT"]
|
||||||
@ -207,7 +193,7 @@ Validate all of the environment variables.
|
|||||||
|
|
||||||
|
|
||||||
def validate_env_vars():
|
def validate_env_vars():
|
||||||
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, APPLE_MUSIC_KEY, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
global TOKEN, BOT_COLOR, BOT_INVITE_LINK, FEEDBACK_CHANNEL_ID, BUG_CHANNEL_ID, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, OPENAI_API_KEY, LAVALINK_HOST, LAVALINK_PORT, LAVALINK_PASSWORD
|
||||||
|
|
||||||
hex_pattern_one = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
hex_pattern_one = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
||||||
hex_pattern_two = "^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
hex_pattern_two = "^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
|
||||||
@ -215,7 +201,7 @@ def validate_env_vars():
|
|||||||
errors = 0
|
errors = 0
|
||||||
|
|
||||||
# Make sure all required variables are present in the environment
|
# Make sure all required variables are present in the environment
|
||||||
required_vars = ["TOKEN", "BOT_COLOR", "BOT_INVITE_LINK", "SPOTIFY_CLIENT_ID", "SPOTIFY_CLIENT_SECRET", "APPLE_MUSIC_KEY", "OPENAI_API_KEY", "LAVALINK_HOST", "LAVALINK_PORT", "LAVALINK_PASSWORD"]
|
required_vars = ["TOKEN", "BOT_COLOR", "BOT_INVITE_LINK", "SPOTIFY_CLIENT_ID", "SPOTIFY_CLIENT_SECRET", "OPENAI_API_KEY", "LAVALINK_HOST", "LAVALINK_PORT", "LAVALINK_PASSWORD"]
|
||||||
|
|
||||||
for var in required_vars:
|
for var in required_vars:
|
||||||
if var not in os.environ:
|
if var not in os.environ:
|
||||||
@ -267,7 +253,6 @@ def validate_env_vars():
|
|||||||
TOKEN = os.environ["TOKEN"]
|
TOKEN = os.environ["TOKEN"]
|
||||||
SPOTIFY_CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"]
|
SPOTIFY_CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"]
|
||||||
SPOTIFY_CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"]
|
SPOTIFY_CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"]
|
||||||
APPLE_MUSIC_KEY = os.environ["APPLE_MUSIC_KEY"]
|
|
||||||
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
||||||
LAVALINK_HOST = os.environ["LAVALINK_HOST"]
|
LAVALINK_HOST = os.environ["LAVALINK_HOST"]
|
||||||
LAVALINK_PORT = os.environ["LAVALINK_PORT"]
|
LAVALINK_PORT = os.environ["LAVALINK_PORT"]
|
||||||
|
28
code/utils/media_api_key.py
Normal file
28
code/utils/media_api_key.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import requests
|
||||||
|
import re
|
||||||
|
|
||||||
|
from utils.config import LOG
|
||||||
|
|
||||||
|
"""
|
||||||
|
Search through the JS files on the Apple Music website to pull
|
||||||
|
a media API key
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_media_api_key():
|
||||||
|
url = "https://music.apple.com"
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
js_files = re.findall(r"assets/(.*?\.js)", response.text)
|
||||||
|
|
||||||
|
# Look for `const Ga="TOKEN HERE"`
|
||||||
|
for js_file in js_files:
|
||||||
|
response = requests.get(f"{url}/assets/{js_file}")
|
||||||
|
match = re.search(r"const Ga=\"(.*?)\"", response.text)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
LOG.error(
|
||||||
|
"Failed to find media API key. Apple Music support will not work until a key is found or manually set."
|
||||||
|
)
|
||||||
|
return None
|
@ -10,7 +10,6 @@ services:
|
|||||||
- BUG_CHANNEL_ID=
|
- BUG_CHANNEL_ID=
|
||||||
- SPOTIFY_CLIENT_ID=
|
- SPOTIFY_CLIENT_ID=
|
||||||
- SPOTIFY_CLIENT_SECRET=
|
- SPOTIFY_CLIENT_SECRET=
|
||||||
- APPLE_MUSIC_KEY=
|
|
||||||
- OPENAI_API_KEY=
|
- OPENAI_API_KEY=
|
||||||
- LAVALINK_HOST=
|
- LAVALINK_HOST=
|
||||||
- LAVALINK_PORT=
|
- LAVALINK_PORT=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user