aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/music/play.py
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-02-04 16:16:26 +0100
committersalmonik <raisersoft@proton.me>2026-02-04 16:16:26 +0100
commitce61bf6bd5949046d15f58609f572fedcb8a073e (patch)
treecafaa12f11a06337bda5de08881e395ac66c1c11 /cogs/music/play.py
parentbetter reply context (diff)
downloadsenator-sperma-ce61bf6bd5949046d15f58609f572fedcb8a073e.tar.gz
senator-sperma-ce61bf6bd5949046d15f58609f572fedcb8a073e.zip
refactor music
Diffstat (limited to 'cogs/music/play.py')
-rw-r--r--cogs/music/play.py219
1 files changed, 0 insertions, 219 deletions
diff --git a/cogs/music/play.py b/cogs/music/play.py
deleted file mode 100644
index c7dd9f1..0000000
--- a/cogs/music/play.py
+++ /dev/null
@@ -1,219 +0,0 @@
1# This file is part of Senator Sperma
2# Licensed under the GPL-3.0 License
3# Copyright (C) 2026 Salmonik @ salmonik@tuta.io
4# https://codeberg.org/salmonik/senator-sperma
5
6import os
7import discord
8import mutagen
9from typing import List, Tuple, Callable, Awaitable
10from os import PathLike
11
12from cogs.music import play_utils
13from discord.ext import commands
14from discord.commands import OptionChoice
15import logging
16
17logger = logging.getLogger(__name__)
18
19class Play(commands.Cog):
20 def __init__(self, bot):
21 self.bot = bot
22 self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "temp")
23 self.path_mapping = {} # Maps display names to full paths
24
25 @staticmethod
26 async def _create_search_embed(candidates: List[str]) -> Tuple[discord.Embed, discord.ui.View]:
27 """Create an embed and view for search results."""
28 embed = discord.Embed(
29 title="Wyniki wyszukiwania",
30 description="Wybierz plik do odtworzenia:",
31 color=discord.Color.blue()
32 )
33
34 view = discord.ui.View(disable_on_timeout=True)
35
36 for i, candidate in enumerate(candidates, start=1):
37 filename = os.path.basename(candidate)
38 title = None
39
40 # Try to get metadata if it's an audio file
41 if os.path.splitext(filename)[1].lower() in ['.mp3', '.wav', '.opus']:
42 try:
43 metadata = mutagen.File(candidate)
44 if metadata:
45 title = metadata.get('title', None)
46 except Exception as e:
47 logger.debug(f"Could not read metadata for {filename}: {str(e)}")
48
49 # Add field with title if available, otherwise use filename
50 display_name = title if title else filename
51 embed.add_field(name=f"{i}. {display_name}", value="\u200b", inline=False)
52
53 return embed, view
54
55 async def _handle_search_results(
56 self,
57 ctx: discord.ApplicationContext,
58 candidates: List[str],
59 play_callback: Callable[[discord.Interaction, str], Awaitable[None]]
60 ) -> None:
61 """Handle the display of search results and button interactions."""
62 if not candidates:
63 await ctx.respond("Nie znaleziono pliku do odtworzenia", ephemeral=True)
64 return
65
66 embed, view = await self._create_search_embed(candidates)
67
68 # Add buttons for each candidate
69 for i, candidate in enumerate(candidates, start=1):
70 button = discord.ui.Button(label=f"Play {i}")
71
72 async def button_callback(interaction: discord.Interaction, candidate_path=candidate):
73 await interaction.response.defer(ephemeral=True)
74 await ctx.interaction.delete_original_response()
75 try:
76 await play_callback(interaction, candidate_path)
77 except Exception as e:
78 logger.error(f"Error in play callback: {str(e)}", exc_info=True)
79 await interaction.followup.send(f"Wystąpił błąd podczas odtwarzania: {str(e)}", ephemeral=True)
80
81 button.callback = button_callback
82 view.add_item(button)
83
84 await ctx.respond(embed=embed, view=view, ephemeral=True)
85
86 async def _search_autocomplete(self, ctx: discord.AutocompleteContext) -> List[OptionChoice]:
87 """Autocomplete handler for search queries."""
88 query = ctx.value.lower()
89 try:
90 results = await play_utils.PlayUtils.search_local_autocomplete(query, max_results=25)
91 choices = []
92 self.path_mapping = {} # Reset the mapping for each new query
93
94 for path in results:
95 filename = os.path.splitext(os.path.basename(path))[0] # Remove extension
96 display = filename # Default display name
97
98 # Try to get metadata for display
99 try:
100 if os.path.splitext(path)[1].lower() in ['.mp3', '.wav', '.opus', '.m4a']:
101 metadata = mutagen.File(path, easy=True)
102
103 # Get metadata fields, falling back to filename if not available
104 title = metadata.get('title', [filename])[0]
105 artist = metadata.get('artist', [''])[0]
106 album_artist = metadata.get('albumartist', [artist])[0]
107
108 # Build display name based on available metadata
109 if album_artist and title:
110 display = f"{album_artist} - {title}"
111 elif artist and title:
112 display = f"{artist} - {title}"
113 elif title:
114 display = title
115
116 except Exception as e:
117 logger.debug(f"Could not read metadata for {filename}: {str(e)}")
118
119 # Clean up the display name (remove any extra whitespace or brackets)
120 display = display.strip()
121
122 # Store the mapping (use the cleaned display name as the key)
123 self.path_mapping[display] = path
124
125 # Truncate display name if too long
126 if len(display) > 90: # Leave room for potential numbering
127 display = display[:87] + '...'
128
129 choices.append(OptionChoice(display[:100], display[:100]))
130
131 # Limit to 25 choices (Discord's limit)
132 if len(choices) >= 25:
133 break
134
135 return choices
136
137 except Exception as e:
138 logger.error(f"Error in autocomplete: {str(e)}", exc_info=True)
139 return []
140
141 @discord.slash_command(name="grajcuj", description="grajcujmy wszyscy!")
142 async def play(
143 self,
144 ctx: discord.ApplicationContext,
145 file: str = discord.Option(
146 str,
147 "wyszukaj plik z serwera",
148 autocomplete=_search_autocomplete,
149 required=False
150 ),
151 stream: str = discord.Option(
152 str,
153 "odtwórz stream internetowy (e.g. radio)",
154 required=False
155 ),
156 compressed: bool = discord.Option(
157 bool,
158 "Czy skompresować dźwięk? (tylko dla plików lokalnych)",
159 default=False
160 )
161 ):
162 """Odtwarza wybrany plik dźwiękowy lub stream internetowy."""
163 await ctx.defer()
164
165 # Check if either file or stream is provided
166 if not file and not stream:
167 await ctx.respond("Musisz podać plik do odtworzenia lub adres streamu.", ephemeral=True)
168 return
169
170 if file and stream:
171 await ctx.respond("Możesz podać tylko plik LUB stream, nie oba naraz.", ephemeral=True)
172 return
173
174 try:
175 if stream:
176 # Handle internet stream
177 await play_utils.PlayUtils(self.bot).play(ctx, stream)
178 else:
179 # Handle local file
180 full_path = self.path_mapping.get(file, file)
181
182 if not os.path.exists(full_path):
183 logger.warning(f"File not found: {full_path} (display name: {file})")
184 await ctx.respond("Wybrany plik nie istnieje.", ephemeral=True)
185 return
186
187 if compressed:
188 # Compress the file before playing
189 compressed_path = await play_utils.PlayUtils(self.bot).compress(ctx, full_path)
190 if not compressed_path:
191 await ctx.respond("Nie udało się skompresować pliku.", ephemeral=True)
192 return
193
194 try:
195 await play_utils.PlayUtils(self.bot).play(ctx, compressed_path, delete_after_play=True)
196 except Exception as e:
197 # Clean up the compressed file if play fails
198 if os.path.exists(compressed_path):
199 try:
200 os.remove(compressed_path)
201 logger.info(f"Cleaned up temporary file after play error: {compressed_path}")
202 except Exception as cleanup_error:
203 logger.error(f"Error cleaning up file {compressed_path}: {str(cleanup_error)}")
204 raise e
205 else:
206 await play_utils.PlayUtils(self.bot).play(ctx, full_path)
207 except Exception as e:
208 logger.error(f"Error playing: {str(e)}", exc_info=True)
209 await ctx.respond(f"Wystąpił błąd podczas odtwarzania: {str(e)}", ephemeral=True)
210
211 @discord.slash_command(name="grajcuj_plik", description="grajcuj plik")
212 async def play_file(self, ctx: discord.ApplicationContext, attachment: discord.Attachment = discord.Option(discord.Attachment, "co zagrać??", required=True)):
213 await ctx.defer()
214 file_path: PathLike[str] | str = os.path.join(self.temp_dir, attachment.filename)
215 await attachment.save(file_path)
216 await play_utils.PlayUtils(self.bot).play(ctx, file_path)
217
218def setup(bot):
219 bot.add_cog(Play(bot)) \ No newline at end of file