From dcbd7859d7dda32bf237bbfc0c71cfe349336f31 Mon Sep 17 00:00:00 2001 From: Marcin Macierzanka Date: Sat, 4 Jul 2026 15:53:03 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20przeniesienie=20konfiguracji=20kana?= =?UTF-8?q?=C5=82u=20log=C3=B3w=20do=20dedykowanego=20modalu=20Ustawienia?= =?UTF-8?q?=20z=20wybierakiem=20per-serwer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 8 +++- static/app.js | 109 +++++++++++++++++++++++++++++------------- static/index.html | 44 ++++++++++++----- static/style.css | 118 ++++++++++++++++++++++++++++++++++++++++++++++ web.py | 41 ++++++++++++++-- 5 files changed, 269 insertions(+), 51 deletions(-) diff --git a/bot.py b/bot.py index bfec921..e914c70 100644 --- a/bot.py +++ b/bot.py @@ -124,7 +124,7 @@ class MusicBot(commands.Cog): print(f"Error loading config: {e}") return None - def save_channel_id(self, guild_id: int, channel_id: int): + def save_channel_id(self, guild_id: int, channel_id: Optional[int]): config_path = "config/config.json" try: import json @@ -133,7 +133,11 @@ class MusicBot(commands.Cog): if os.path.exists(config_path): with open(config_path, "r") as f: data = json.load(f) - data[str(guild_id)] = channel_id + if channel_id is None: + if str(guild_id) in data: + del data[str(guild_id)] + else: + data[str(guild_id)] = channel_id with open(config_path, "w") as f: json.dump(data, f, indent=4) except Exception as e: diff --git a/static/app.js b/static/app.js index f8f81e8..a6b348e 100644 --- a/static/app.js +++ b/static/app.js @@ -16,8 +16,10 @@ let wsConnectedOnce = false; // DOM Elements const statusDot = document.getElementById('status-dot'); const statusText = document.getElementById('status-text'); -const voiceSelect = document.getElementById('voice-channel-select'); -const textChannelSelect = document.getElementById('text-channel-select'); +const btnOpenSettings = document.getElementById('btn-open-settings'); +const btnCloseSettings = document.getElementById('btn-close-settings'); +const settingsModal = document.getElementById('settings-modal'); +const settingsGuildsList = document.getElementById('settings-guilds-list'); const btnConnect = document.getElementById('btn-connect'); const trackTitle = document.getElementById('track-title'); const trackArtist = document.getElementById('track-artist'); @@ -162,8 +164,14 @@ function bindEvents() { // Add Song Form addForm.addEventListener('submit', addTrack); - // Text Channel Select - textChannelSelect.addEventListener('change', changeTextChannel); + // Settings Modal Events + if (btnOpenSettings) btnOpenSettings.addEventListener('click', openSettingsModal); + if (btnCloseSettings) btnCloseSettings.addEventListener('click', closeSettingsModal); + if (settingsModal) { + settingsModal.addEventListener('click', (e) => { + if (e.target === settingsModal) closeSettingsModal(); + }); + } // Upload Events if (btnSelectFile && fileInput) { @@ -296,30 +304,9 @@ function updateUI(status) { } } - // Text selector population (only if select doesn't have focus/interaction) - if (document.activeElement !== textChannelSelect) { - const currentSelectedText = textChannelSelect.value; - textChannelSelect.innerHTML = ''; - - if (status.text_channels && status.text_channels.length > 0) { - status.text_channels.forEach(channel => { - const opt = document.createElement('option'); - opt.value = channel.channel_id; - opt.textContent = `${channel.guild_name} - #${channel.channel_name}`; - textChannelSelect.appendChild(opt); - }); - - if (status.active_text_channel_id) { - textChannelSelect.value = status.active_text_channel_id; - } else if (currentSelectedText && status.text_channels.some(c => c.channel_id === currentSelectedText)) { - textChannelSelect.value = currentSelectedText; - } - } else { - const opt = document.createElement('option'); - opt.value = ""; - opt.textContent = "Brak dostępnych kanałów"; - textChannelSelect.appendChild(opt); - } + // Settings Guilds Population + if (status.guilds) { + renderSettingsGuilds(status.guilds); } // 2. Playback State @@ -753,19 +740,73 @@ async function setVolume(vol) { } } -// Change Text Notification Channel -async function changeTextChannel() { - const channelId = textChannelSelect.value; - if (!channelId) return; +// Settings Modal Functions +function openSettingsModal() { + if (settingsModal) settingsModal.style.display = 'flex'; +} + +function closeSettingsModal() { + if (settingsModal) settingsModal.style.display = 'none'; +} + +function renderSettingsGuilds(guilds) { + if (!settingsGuildsList) return; + // Don't re-render if user is currently interacting with a select inside settings + if (settingsModal && settingsModal.style.display !== 'none' && document.activeElement && document.activeElement.classList.contains('guild-log-select')) { + return; + } + + if (!guilds || guilds.length === 0) { + settingsGuildsList.innerHTML = '

Brak serwerów Discord w pamięci bota.

'; + lucide.createIcons(); + return; + } + + let html = ''; + guilds.forEach(guild => { + let optionsHtml = ''; + if (guild.text_channels && guild.text_channels.length > 0) { + guild.text_channels.forEach(tc => { + const isSelected = guild.active_notification_channel_id === tc.channel_id ? 'selected' : ''; + optionsHtml += ``; + }); + } else { + optionsHtml = ''; + } + + html += ` +
+
+
+
+
${guild.guild_name}
+
ID: ${guild.guild_id}
+
+
+
+ +
+
+ `; + }); + + settingsGuildsList.innerHTML = html; + lucide.createIcons(); +} + +async function saveGuildLogChannel(guildId, channelId) { try { const res = await fetch('/api/settings/text_channel', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ channel_id: channelId }) + body: JSON.stringify({ guild_id: guildId, channel_id: channelId || "" }) }); if (!res.ok) throw new Error('Settings change failed'); - showToast('Zapisano kanał powiadomień na Discordzie!', 'success'); + showToast('Zapisano kanał powiadomień dla serwera!', 'success'); } catch (err) { showToast('Nie udało się zapisać kanału powiadomień', 'error'); } diff --git a/static/index.html b/static/index.html index b276b99..c8b78bd 100644 --- a/static/index.html +++ b/static/index.html @@ -27,10 +27,14 @@

ŻyrarBOT

Kontroler Web - -
- - Rozłączony +
+ +
+ + Rozłączony +
@@ -41,7 +45,7 @@

Kanał Głosowy

-
+
@@ -49,13 +53,6 @@ Połącz
- -

Kanał Tekstowy Logów

-
- -
@@ -217,6 +214,29 @@
+ + +
diff --git a/static/style.css b/static/style.css index 600a532..e8f40fa 100644 --- a/static/style.css +++ b/static/style.css @@ -923,3 +923,121 @@ body { .toast.info i { color: var(--secondary); } + +/* Settings Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(8px); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + animation: fadeIn 0.2s ease-out; +} + +.modal-card { + width: 100%; + max-width: 650px; + max-height: 85vh; + display: flex; + flex-direction: column; + background: var(--bg-card); + border: 1px solid var(--border-glass); + border-radius: 24px; + box-shadow: var(--shadow-premium); + overflow: hidden; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 24px; + border-bottom: 1px solid var(--border-glass); + background: rgba(255, 255, 255, 0.02); +} + +.modal-body { + padding: 24px; + overflow-y: auto; + flex: 1; +} + +.settings-section { + margin-bottom: 24px; +} + +.settings-section h3 { + font-size: 1.1rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + color: var(--text-main); + margin-bottom: 8px; +} + +.settings-desc { + font-size: 0.9rem; + color: var(--text-muted); + margin-bottom: 16px; + line-height: 1.5; +} + +.guild-setting-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--border-glass); + margin-bottom: 12px; + gap: 16px; +} + +.guild-info { + display: flex; + align-items: center; + gap: 12px; + flex: 1; + min-width: 0; +} + +.guild-icon { + width: 42px; + height: 42px; + border-radius: 12px; + background: rgba(139, 92, 246, 0.15); + color: var(--primary); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + flex-shrink: 0; +} + +.guild-name { + font-weight: 600; + color: var(--text-main); + font-size: 0.95rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.guild-id { + font-size: 0.75rem; + color: var(--text-muted); +} + +.guild-control { + width: 240px; + flex-shrink: 0; +} diff --git a/web.py b/web.py index f95fd9c..8221823 100644 --- a/web.py +++ b/web.py @@ -35,6 +35,7 @@ class ConnectRequest(BaseModel): class TextChannelRequest(BaseModel): channel_id: str + guild_id: Optional[str] = None class PlayFileRequest(BaseModel): filename: str @@ -77,19 +78,39 @@ async def get_status(): if not music_cog: return {"error": "MusicBot cog not loaded"} + all_guilds = [] + for g in bot.guilds: + g_tc = [] + for tc in g.text_channels: + if tc.permissions_for(g.me).send_messages: + g_tc.append({ + "channel_id": str(tc.id), + "channel_name": tc.name + }) + active_notif = music_cog.get_notification_channel(g.id) + all_guilds.append({ + "guild_id": str(g.id), + "guild_name": g.name, + "text_channels": g_tc, + "active_notification_channel_id": str(active_notif.id) if active_notif else None + }) + guild = get_active_guild() if not guild: return { "playing": False, "paused": False, "loop_single": False, + "loop_playlist": False, "volume": 100, "current": None, "queue": [], + "history": [], "voice_channels": [], "active_voice_channel": None, "text_channels": [], - "active_text_channel_id": None + "active_text_channel_id": None, + "guilds": all_guilds } guild_id = guild.id @@ -161,6 +182,7 @@ async def get_status(): "active_voice_channel": active_vc, "text_channels": text_channels, "active_text_channel_id": active_tc_id, + "guilds": all_guilds, "history": [ { "id": item["id"], @@ -590,22 +612,35 @@ async def api_set_text_channel(req: TextChannelRequest): if not music_cog: raise HTTPException(status_code=500, detail="MusicBot cog not loaded") + if not req.channel_id or req.channel_id == "0" or req.channel_id == "": + if not req.guild_id: + raise HTTPException(status_code=400, detail="guild_id required to reset channel") + guild_id = int(req.guild_id) + if guild_id in music_cog.last_channels: + del music_cog.last_channels[guild_id] + music_cog.save_channel_id(guild_id, None) + await broadcast_status() + return {"status": "success", "channel_id": None, "guild_id": str(guild_id)} + channel = bot.get_channel(int(req.channel_id)) if not channel: raise HTTPException(status_code=404, detail="Text channel not found") guild_id = channel.guild.id + if req.guild_id and int(req.guild_id) != guild_id: + raise HTTPException(status_code=400, detail="Channel does not belong to the specified guild") + music_cog.last_channels[guild_id] = channel music_cog.save_channel_id(guild_id, int(req.channel_id)) # Send test message to confirm try: - await channel.send(f"🔔 Ten kanał został ustawiony jako główny kanał powiadomień dla ŻyrarBOT z panelu webowego.") + await channel.send(f"🔔 Ten kanał został ustawiony jako główny kanał powiadomień dla ŻyrarBOT na serwerze **{channel.guild.name}** z panelu webowego.") except Exception as e: print(f"Failed to send test message: {e}") await broadcast_status() - return {"status": "success", "channel_id": req.channel_id} + return {"status": "success", "channel_id": req.channel_id, "guild_id": str(guild_id)} # WebSocket Background Loop and Startup Hook async def status_broadcast_loop():