import os import requests import discord from discord.ext import commands from discord import app_commands from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv("DISCORD_TOKEN") JELLYFIN_URL = os.getenv("JELLYFIN_URL") JELLYFIN_API_KEY = os.getenv("JELLYFIN_API_KEY") headers= { "X-Emby-Token": JELLYFIN_API_KEY } intents = discord.Intents.default() intents.message_content = True intents.voice_states = True class JellyfinBot(commands.Bot): def __init__(self): super().__init__(command_prefix="/", intents=intents) async def setup_hook(self): await self.tree.sync() bot = JellyfinBot() @bot.event async def on_ready(): print(f"🪼 Logged in as {bot.user}") @bot.tree.command(name="play", description="Play a song from Jellyfin") @app_commands.describe(song="Song title to play") async def play(interaction: discord.Interaction, song: str): await interaction.response.defer(ephemeral=False) if not interaction.user.voice: await interaction.response.send_message("You must be in a voice channel.") return url = f"{JELLYFIN_URL}/Items" params = { "searchTerm": song, "Recursive": True, "IncludeItemTypes": "Audio", "Limit": 5 } try: response = requests.get(url, headers=headers, params=params) response.raise_for_status() data = response.json().get("Items", []) if not data: await interaction.followup.send(f"❌ No song found matching `{song}`.") return print(f"Found {len(data)} items matching `{song}`.") item = data[0] item_id = item.get("Id") stream_url = f"{JELLYFIN_URL}/Audio/{item_id}/stream?static=True" print(f"Stream URL: {stream_url}") channel = interaction.user.voice.channel voice_client = interaction.guild.voice_client if voice_client is None: voice_client = await channel.connect() elif voice_client.channel != channel: await voice_client.move_to(channel) voice_client = await channel.connect() if voice_client.is_playing(): voice_client.stop() headers_str = f"-headers \"X-Emby-Token: {JELLYFIN_API_KEY}\"" source = discord.FFmpegPCMAudio(stream_url, before_options=f'{headers_str} -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', options="-vn") voice_client.play(source, after=lambda e: print(f"Finished playing: {e}")) await interaction.followup.send(f"🎶 Now playing: **{item.get('Name')}** by *{item.get('AlbumArtist', ['Unknown Artist'])}*") except requests.RequestException as e: await interaction.followup.send("⚠️ Failed to contact Jellyfin server.") print(f"Error: {e}") @bot.tree.command(name="search", description="Search for a song on Jellyfin") @app_commands.describe(title="Song title to search for") async def search(interaction: discord.Interaction, title: str): await interaction.response.defer(ephemeral=False) url = f"{JELLYFIN_URL}/Items" params = { "searchTerm": title, "Recursive": True, "IncludeItemTypes": "Audio", "Limit": 5 } try: response = requests.get(url, headers=headers, params=params) response.raise_for_status() data = response.json() items = data.get("Items", []) if not items: await interaction.followup.send(f"❌ No song found matching `{title}`.") return lines = [] for item in items: title = item.get("Name") artist = item.get("AlbumArtist", ["Unknown Artist"]) lines.append(f"✅ **{title}** by *{artist}*") await interaction.followup.send("\n".join(lines)) except requests.RequestException as e: await interaction.followup.send("⚠️ Failed to contact Jellyfin server.") print(f"Error: {e}") bot.run(TOKEN)