// ===== Server Management Functions ===== async function loadServers() { try { console.log('🎭 Loading servers...'); const data = await apiCall('/servers'); console.log('🎭 Servers response:', data); if (data.servers) { servers = data.servers; console.log(`🎭 Loaded ${servers.length} servers:`, servers); // Debug: Log each server's guild_id servers.forEach((server, index) => { console.log(`🎭 Server ${index}: guild_id = ${server.guild_id}, name = ${server.guild_name}`); }); // Debug: Show raw response data console.log('🎭 Raw API response data:', JSON.stringify(data, null, 2)); // Display servers displayServers(); populateServerDropdowns(); populateMoodDropdowns(); // Populate mood dropdowns after servers are loaded } else { console.warn('🎭 No servers found in response'); servers = []; } } catch (error) { console.error('🎭 Failed to load servers:', error); servers = []; } } function displayServers() { const container = document.getElementById('servers-list'); if (servers.length === 0) { container.innerHTML = '

No servers configured

'; return; } container.innerHTML = servers.map(server => `
${server.guild_name}
Guild ID: ${server.guild_id}
Autonomous Channel: #${server.autonomous_channel_name} (${server.autonomous_channel_id})
Bedtime Channels: ${server.bedtime_channel_ids.join(', ')}
Features: ${server.enabled_features.map(feature => `${feature}`).join('')}
Autonomous Interval: ${server.autonomous_interval_minutes} minutes
Conversation Detection: ${server.conversation_detection_interval_minutes} minutes
Bedtime Range: ${String(server.bedtime_hour || 21).padStart(2, '0')}:${String(server.bedtime_minute || 0).padStart(2, '0')} - ${String(server.bedtime_hour_end || 23).padStart(2, '0')}:${String(server.bedtime_minute_end || 59).padStart(2, '0')}

Bedtime Settings

Server Mood

Current Mood: ${server.current_mood_name || 'neutral'} ${MOOD_EMOJIS[server.current_mood_name] || ''}
Sleeping: ${server.is_sleeping ? 'Yes' : 'No'}
`).join(''); // Debug: Log what element IDs were created console.log('🎭 Server cards rendered. Checking for mood-select elements:'); document.querySelectorAll('[id^="mood-select-"]').forEach(el => { console.log(`🎭 Found mood-select element: ${el.id}`); }); // Populate mood dropdowns after server cards are created populateMoodDropdowns(); } async function populateServerDropdowns() { const serverSelect = document.getElementById('server-select'); const manualServerSelect = document.getElementById('manual-server-select'); const customPromptServerSelect = document.getElementById('custom-prompt-server-select'); // Clear existing options except "All Servers" serverSelect.innerHTML = ''; manualServerSelect.innerHTML = ''; customPromptServerSelect.innerHTML = ''; console.log('🎭 Populating server dropdowns with', servers.length, 'servers'); // Add server options servers.forEach(server => { console.log(`🎭 Adding server to dropdown: ${server.guild_name} (guild_id: ${server.guild_id}, type: ${typeof server.guild_id})`); const option = document.createElement('option'); option.value = server.guild_id; option.textContent = server.guild_name; serverSelect.appendChild(option.cloneNode(true)); manualServerSelect.appendChild(option); customPromptServerSelect.appendChild(option.cloneNode(true)); }); // Debug: Check what's actually in the manual-server-select dropdown console.log('🎭 manual-server-select options:'); Array.from(manualServerSelect.options).forEach((opt, idx) => { console.log(` [${idx}] value="${opt.value}" text="${opt.textContent}"`); }); // Populate autonomous stats dropdown populateAutonomousServerDropdown(); } // Figurine subscribers UI functions (must be global for onclick handlers) async function refreshFigurineSubscribers() { try { console.log('🔄 Figurines: Fetching subscribers...'); const data = await apiCall('/figurines/subscribers'); console.log('📋 Figurines: Received subscribers:', data); displayFigurineSubscribers(data.subscribers || []); showNotification('Subscribers refreshed'); } catch (e) { console.error('❌ Figurines: Failed to fetch subscribers:', e); } } function displayFigurineSubscribers(subscribers) { const container = document.getElementById('figurine-subscribers-list'); if (!container) return; if (!subscribers.length) { container.innerHTML = '

No subscribers yet.

'; return; } let html = ''; container.innerHTML = html; } async function addFigurineSubscriber() { try { console.log('➕ Figurines: Adding subscriber...'); const uid = document.getElementById('figurine-user-id').value.trim(); if (!uid) { showNotification('Enter a user ID', 'error'); return; } const form = new FormData(); form.append('user_id', uid); const res = await fetch('/figurines/subscribers', { method: 'POST', body: form }); const data = await res.json(); console.log('➕ Figurines: Add subscriber response:', data); if (data.status === 'ok') { showNotification('Subscriber added'); document.getElementById('figurine-user-id').value = ''; refreshFigurineSubscribers(); } else { showNotification(data.message || 'Failed to add subscriber', 'error'); } } catch (e) { console.error('❌ Figurines: Failed to add subscriber:', e); showNotification('Failed to add subscriber', 'error'); } } async function removeFigurineSubscriber(uid) { try { console.log(`🗑️ Figurines: Removing subscriber ${uid}...`); const data = await apiCall(`/figurines/subscribers/${uid}`, 'DELETE'); console.log('🗑️ Figurines: Remove subscriber response:', data); if (data.status === 'ok') { showNotification('Subscriber removed'); refreshFigurineSubscribers(); } else { showNotification(data.message || 'Failed to remove subscriber', 'error'); } } catch (e) { console.error('❌ Figurines: Failed to remove subscriber:', e); } } async function sendFigurineNowToAll() { try { console.log('📨 Figurines: Triggering send to all subscribers...'); const tweetUrl = document.getElementById('figurine-tweet-url-all').value.trim(); const statusDiv = document.getElementById('figurine-all-status'); statusDiv.textContent = 'Sending...'; statusDiv.style.color = evilMode ? '#ff4444' : '#007bff'; const formData = new FormData(); if (tweetUrl) { formData.append('tweet_url', tweetUrl); } const res = await fetch('/figurines/send_now', { method: 'POST', body: formData }); const data = await res.json(); console.log('📨 Figurines: Send to all response:', data); if (data.status === 'ok') { showNotification('Figurine DMs queued for all subscribers'); statusDiv.textContent = 'Queued successfully'; statusDiv.style.color = '#28a745'; document.getElementById('figurine-tweet-url-all').value = ''; // Clear input } else { showNotification(data.message || 'Bot not ready', 'error'); statusDiv.textContent = 'Failed: ' + (data.message || 'Unknown error'); statusDiv.style.color = '#dc3545'; } } catch (e) { console.error('❌ Figurines: Failed to queue figurine DMs for all:', e); showNotification('Failed to queue figurine DMs', 'error'); document.getElementById('figurine-all-status').textContent = 'Error: ' + e.message; document.getElementById('figurine-all-status').style.color = '#dc3545'; } } async function sendFigurineToSingleUser() { try { const userId = document.getElementById('figurine-single-user-id').value.trim(); const tweetUrl = document.getElementById('figurine-tweet-url-single').value.trim(); const statusDiv = document.getElementById('figurine-single-status'); if (!userId) { showNotification('Enter a user ID', 'error'); return; } console.log(`📨 Figurines: Sending to single user ${userId}, tweet: ${tweetUrl || 'random'}`); statusDiv.textContent = 'Sending...'; statusDiv.style.color = evilMode ? '#ff4444' : '#007bff'; const formData = new FormData(); formData.append('user_id', userId); if (tweetUrl) { formData.append('tweet_url', tweetUrl); } const res = await fetch('/figurines/send_to_user', { method: 'POST', body: formData }); const data = await res.json(); console.log('📨 Figurines: Send to single user response:', data); if (data.status === 'ok') { showNotification(`Figurine DM queued for user ${userId}`); statusDiv.textContent = 'Queued successfully'; statusDiv.style.color = '#28a745'; document.getElementById('figurine-single-user-id').value = ''; // Clear inputs document.getElementById('figurine-tweet-url-single').value = ''; } else { showNotification(data.message || 'Failed to queue DM', 'error'); statusDiv.textContent = 'Failed: ' + (data.message || 'Unknown error'); statusDiv.style.color = '#dc3545'; } } catch (e) { console.error('❌ Figurines: Failed to queue figurine DM for single user:', e); showNotification('Failed to queue figurine DM', 'error'); document.getElementById('figurine-single-status').textContent = 'Error: ' + e.message; document.getElementById('figurine-single-status').style.color = '#dc3545'; } } // Keep the old function for backward compatibility async function sendFigurineNow() { return sendFigurineNowToAll(); } async function addServer() { // Don't use parseInt() for Discord IDs - they're too large for JS integers const guildId = document.getElementById('new-guild-id').value.trim(); const guildName = document.getElementById('new-guild-name').value; const autonomousChannelId = document.getElementById('new-autonomous-channel-id').value.trim(); const autonomousChannelName = document.getElementById('new-autonomous-channel-name').value; const bedtimeChannelIds = document.getElementById('new-bedtime-channel-ids').value .split(',').map(id => id.trim()).filter(id => id.length > 0); const enabledFeatures = []; if (document.getElementById('feature-autonomous').checked) enabledFeatures.push('autonomous'); if (document.getElementById('feature-bedtime').checked) enabledFeatures.push('bedtime'); if (document.getElementById('feature-monday-video').checked) enabledFeatures.push('monday_video'); if (!guildId || !guildName || !autonomousChannelId || !autonomousChannelName) { showNotification('Please fill in all required fields', 'error'); return; } try { await apiCall('/servers', 'POST', { guild_id: guildId, guild_name: guildName, autonomous_channel_id: autonomousChannelId, autonomous_channel_name: autonomousChannelName, bedtime_channel_ids: bedtimeChannelIds.length > 0 ? bedtimeChannelIds : [autonomousChannelId], enabled_features: enabledFeatures }); showNotification('Server added successfully'); loadServers(); // Clear form document.getElementById('new-guild-id').value = ''; document.getElementById('new-guild-name').value = ''; document.getElementById('new-autonomous-channel-id').value = ''; document.getElementById('new-autonomous-channel-name').value = ''; document.getElementById('new-bedtime-channel-ids').value = ''; } catch (error) { console.error('Failed to add server:', error); } } async function removeServer(guildId) { if (!confirm('Are you sure you want to remove this server?')) { return; } try { await apiCall(`/servers/${guildId}`, 'DELETE'); showNotification('Server removed successfully'); loadServers(); } catch (error) { console.error('Failed to remove server:', error); } } async function editServer(guildId) { // For now, just show a notification - you can implement a full edit form later showNotification('Edit functionality coming soon!'); } async function repairConfig() { if (!confirm('This will attempt to repair corrupted server configurations. Are you sure?')) { return; } try { await apiCall('/servers/repair', 'POST'); showNotification('Configuration repair initiated. Please refresh the page to see updated server list.'); loadServers(); // Reload servers to reflect potential changes } catch (error) { console.error('Failed to repair config:', error); showNotification(error.message || 'Failed to repair configuration', 'error'); } } // Populate mood dropdowns with available moods async function populateMoodDropdowns() { try { console.log('🎭 Loading available moods...'); const data = await apiCall('/moods/available'); console.log('🎭 Available moods response:', data); if (data.moods) { console.log(`🎭 Found ${data.moods.length} moods:`, data.moods); const emojiMap = evilMode ? EVIL_MOOD_EMOJIS : MOOD_EMOJIS; // Populate the DM mood dropdown (#mood on tab1) const dmMoodSelect = document.getElementById('mood'); if (dmMoodSelect) { dmMoodSelect.innerHTML = ''; data.moods.forEach(mood => { const opt = document.createElement('option'); opt.value = mood; opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim(); if (mood === 'neutral') opt.selected = true; dmMoodSelect.appendChild(opt); }); } // Populate the chat mood dropdown (#chat-mood-select on tab7) const chatMoodSelect = document.getElementById('chat-mood-select'); if (chatMoodSelect) { chatMoodSelect.innerHTML = ''; data.moods.forEach(mood => { const opt = document.createElement('option'); opt.value = mood; opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim(); if (mood === 'neutral') opt.selected = true; chatMoodSelect.appendChild(opt); }); } // Populate per-server mood dropdowns (mood-select-{guildId}) document.querySelectorAll('[id^="mood-select-"]').forEach(select => { // Keep only the first option ("Select Mood...") while (select.children.length > 1) { select.removeChild(select.lastChild); } }); data.moods.forEach(mood => { const moodOption = document.createElement('option'); moodOption.value = mood; moodOption.textContent = `${mood} ${emojiMap[mood] || ''}`; document.querySelectorAll('[id^="mood-select-"]').forEach(select => { select.appendChild(moodOption.cloneNode(true)); }); }); console.log('🎭 All mood dropdowns populated successfully'); } else { console.warn('🎭 No moods found in response'); } } catch (error) { console.error('🎭 Failed to load available moods:', error); } } // Per-Server Mood Management async function setServerMood(guildId) { console.log(`🎭 setServerMood called with guildId: ${guildId} (type: ${typeof guildId})`); // Ensure guildId is a string for consistency const guildIdStr = String(guildId); console.log(`🎭 Using guildId as string: ${guildIdStr}`); // Debug: Check what elements exist const elementId = `mood-select-${guildIdStr}`; console.log(`🎭 Looking for element with ID: ${elementId}`); const moodSelect = document.getElementById(elementId); console.log(`🎭 Found element:`, moodSelect); if (!moodSelect) { console.error(`🎭 ERROR: Element with ID '${elementId}' not found!`); console.log(`🎭 Available mood-select elements:`, document.querySelectorAll('[id^="mood-select-"]')); showNotification(`Error: Mood selector not found for server ${guildIdStr}`, 'error'); return; } const selectedMood = moodSelect.value; console.log(`🎭 Setting mood for server ${guildIdStr} to ${selectedMood}`); if (!selectedMood) { showNotification('Please select a mood', 'error'); return; } // Get the button and store original text before any changes const button = moodSelect.nextElementSibling; const originalText = button.textContent; try { // Show loading state button.textContent = 'Changing...'; button.disabled = true; console.log(`🎭 Making API call to /servers/${guildIdStr}/mood with mood: ${selectedMood}`); const response = await apiCall(`/servers/${guildIdStr}/mood`, 'POST', { mood: selectedMood }); console.log(`🎭 API response:`, response); if (response.status === 'ok') { showNotification(`Server mood changed to ${selectedMood} ${MOOD_EMOJIS[selectedMood] || ''}`); // Reset dropdown selection moodSelect.value = ''; // Reload servers to show updated mood loadServers(); } else { showNotification(`Failed to change mood: ${response.message}`, 'error'); } } catch (error) { console.error(`🎭 Error setting mood:`, error); showNotification(`Failed to change mood: ${error}`, 'error'); } finally { // Restore button state button.textContent = originalText; button.disabled = false; } } async function resetServerMood(guildId) { console.log(`🎭 resetServerMood called with guildId: ${guildId} (type: ${typeof guildId})`); // Ensure guildId is a string for consistency const guildIdStr = String(guildId); console.log(`🎭 Using guildId as string: ${guildIdStr}`); const button = document.querySelector(`button[onclick="resetServerMood('${guildIdStr}')"]`); const originalText = button ? button.textContent : 'Reset'; try { // Show loading state if (button) { button.textContent = 'Resetting...'; button.disabled = true; } await apiCall(`/servers/${guildIdStr}/mood/reset`, 'POST'); showNotification(`Server mood reset to neutral`); // Reload servers to show updated mood loadServers(); } catch (error) { showNotification(`Failed to reset mood: ${error}`, 'error'); } finally { // Restore button state if (button) { button.textContent = originalText; button.disabled = false; } } } async function updateBedtimeRange(guildId) { console.log(`⏰ updateBedtimeRange called with guildId: ${guildId}`); // Ensure guildId is a string for consistency const guildIdStr = String(guildId); // Get the time values from the inputs const startTimeInput = document.getElementById(`bedtime-start-${guildIdStr}`); const endTimeInput = document.getElementById(`bedtime-end-${guildIdStr}`); if (!startTimeInput || !endTimeInput) { showNotification('Could not find bedtime time inputs', 'error'); return; } const startTime = startTimeInput.value; // Format: "HH:MM" const endTime = endTimeInput.value; // Format: "HH:MM" if (!startTime || !endTime) { showNotification('Please enter both start and end times', 'error'); return; } // Parse the times const [startHour, startMinute] = startTime.split(':').map(Number); const [endHour, endMinute] = endTime.split(':').map(Number); const button = document.querySelector(`button[onclick="updateBedtimeRange('${guildIdStr}')"]`); const originalText = button ? button.textContent : 'Update Bedtime Range'; try { // Show loading state if (button) { button.textContent = 'Updating...'; button.disabled = true; } // Send the update request await apiCall(`/servers/${guildIdStr}/bedtime-range`, 'POST', { bedtime_hour: startHour, bedtime_minute: startMinute, bedtime_hour_end: endHour, bedtime_minute_end: endMinute }); showNotification(`Bedtime range updated: ${startTime} - ${endTime}`); // Reload servers to show updated configuration loadServers(); } catch (error) { console.error('Failed to update bedtime range:', error); } finally { // Restore button state if (button) { button.textContent = originalText; button.disabled = false; } } } // Mood Management async function setMood() { const mood = document.getElementById('mood').value; try { // Use different endpoint for evil mode const endpoint = evilMode ? '/evil-mode/mood' : '/mood'; await apiCall(endpoint, 'POST', { mood: mood }); showNotification(`Mood set to ${mood}`); currentMood = mood; } catch (error) { console.error('Failed to set mood:', error); } } async function resetMood() { try { if (evilMode) { await apiCall('/evil-mode/mood', 'POST', { mood: 'evil_neutral' }); showNotification('Evil mood reset to evil_neutral'); currentMood = 'evil_neutral'; document.getElementById('mood').value = 'evil_neutral'; } else { await apiCall('/mood/reset', 'POST'); showNotification('Mood reset to neutral'); currentMood = 'neutral'; document.getElementById('mood').value = 'neutral'; } } catch (error) { console.error('Failed to reset mood:', error); } } async function calmMiku() { try { if (evilMode) { await apiCall('/evil-mode/mood', 'POST', { mood: 'evil_neutral' }); showNotification('Evil Miku has been calmed down'); currentMood = 'evil_neutral'; document.getElementById('mood').value = 'evil_neutral'; } else { await apiCall('/mood/calm', 'POST'); showNotification('Miku has been calmed down'); } } catch (error) { console.error('Failed to calm Miku:', error); } } // ===== Language Mode Functions ===== async function refreshLanguageStatus() { try { const result = await apiCall('/language'); document.getElementById('current-language-display').textContent = result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English'; document.getElementById('status-language').textContent = result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English'; document.getElementById('status-model').textContent = result.current_model; console.log('Language status:', result); } catch (error) { console.error('Failed to get language status:', error); showNotification('Failed to load language status', 'error'); } } async function toggleLanguageMode() { try { const result = await apiCall('/language/toggle', 'POST'); // Update UI document.getElementById('current-language-display').textContent = result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English'; document.getElementById('status-language').textContent = result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English'; document.getElementById('status-model').textContent = result.model_now_using; // Show notification showNotification(result.message, 'success'); console.log('Language toggled:', result); } catch (error) { console.error('Failed to toggle language mode:', error); showNotification('Failed to toggle language mode', 'error'); } }