549 lines
21 KiB
JavaScript
549 lines
21 KiB
JavaScript
|
|
// ============================================================================
|
|||
|
|
// Miku Control Panel — DM Management Module
|
|||
|
|
// ============================================================================
|
|||
|
|
|
|||
|
|
async function loadDMUsers() {
|
|||
|
|
try {
|
|||
|
|
const result = await apiCall('/dms/users');
|
|||
|
|
displayDMUsers(result.users);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to load DM users:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function displayDMUsers(users) {
|
|||
|
|
const container = document.getElementById('dm-users-list');
|
|||
|
|
|
|||
|
|
if (!users || users.length === 0) {
|
|||
|
|
container.innerHTML = '<p>No DM conversations found.</p>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let html = '<div class="dm-users-grid">';
|
|||
|
|
|
|||
|
|
users.forEach(user => {
|
|||
|
|
console.log(`👤 Processing user: ${user.username} (ID: ${user.user_id})`);
|
|||
|
|
|
|||
|
|
const lastMessage = user.last_message ?
|
|||
|
|
`Last: ${user.last_message.content}` :
|
|||
|
|
'No messages yet';
|
|||
|
|
|
|||
|
|
const lastTime = user.last_message ?
|
|||
|
|
new Date(user.last_message.timestamp).toLocaleString() :
|
|||
|
|
'Never';
|
|||
|
|
|
|||
|
|
html += `
|
|||
|
|
<div class="dm-user-card">
|
|||
|
|
<h4>👤 ${user.username}</h4>
|
|||
|
|
<p><strong>ID:</strong> ${user.user_id}</p>
|
|||
|
|
<p><strong>Total Messages:</strong> ${user.total_messages}</p>
|
|||
|
|
<p><strong>User Messages:</strong> ${user.user_messages}</p>
|
|||
|
|
<p><strong>Bot Messages:</strong> ${user.bot_messages}</p>
|
|||
|
|
<p><strong>Last Activity:</strong> ${lastTime}</p>
|
|||
|
|
<p><strong>Last Message:</strong> ${lastMessage}</p>
|
|||
|
|
<div class="dm-user-actions">
|
|||
|
|
<button class="view-chat-btn" data-user-id="${user.user_id}">💬 View Chat</button>
|
|||
|
|
<button class="analyze-user-btn" data-user-id="${user.user_id}" data-username="${user.username}" style="background: #9c27b0;">📊 Analyze</button>
|
|||
|
|
<button class="export-dms-btn" data-user-id="${user.user_id}">📤 Export</button>
|
|||
|
|
<button class="block-user-btn" data-user-id="${user.user_id}" data-username="${user.username}" style="background: #ff9800;">🚫 Block</button>
|
|||
|
|
<button class="delete-all-dms-btn" data-user-id="${user.user_id}" data-username="${user.username}" style="background: #f44336;">🗑️ Delete All</button>
|
|||
|
|
<button class="delete-user-completely-btn" data-user-id="${user.user_id}" data-username="${user.username}" style="background: #d32f2f;">💀 Delete User</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
html += '</div>';
|
|||
|
|
container.innerHTML = html;
|
|||
|
|
|
|||
|
|
// Add event listeners after HTML is inserted
|
|||
|
|
addDMUserEventListeners();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function addDMUserEventListeners() {
|
|||
|
|
// Add event listeners for view chat buttons
|
|||
|
|
document.querySelectorAll('.view-chat-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
console.log(`🎯 View chat clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
viewUserConversations(userId);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Add event listeners for export buttons
|
|||
|
|
document.querySelectorAll('.export-dms-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
console.log(`🎯 Export clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
exportUserDMs(userId);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Add event listeners for analyze buttons
|
|||
|
|
document.querySelectorAll('.analyze-user-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
const username = this.getAttribute('data-username');
|
|||
|
|
console.log(`🎯 Analyze clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
analyzeUserInteraction(userId, username);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Add event listeners for block buttons
|
|||
|
|
document.querySelectorAll('.block-user-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
const username = this.getAttribute('data-username');
|
|||
|
|
console.log(`🎯 Block clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
blockUser(userId, username);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Add event listeners for delete all DMs buttons
|
|||
|
|
document.querySelectorAll('.delete-all-dms-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
const username = this.getAttribute('data-username');
|
|||
|
|
console.log(`🎯 Delete all DMs clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
deleteAllUserConversations(userId, username);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Add event listeners for delete user completely buttons
|
|||
|
|
document.querySelectorAll('.delete-user-completely-btn').forEach(button => {
|
|||
|
|
button.addEventListener('click', function() {
|
|||
|
|
const userId = this.getAttribute('data-user-id');
|
|||
|
|
const username = this.getAttribute('data-username');
|
|||
|
|
console.log(`🎯 Delete user completely clicked for user ID: ${userId} (type: ${typeof userId})`);
|
|||
|
|
deleteUserCompletely(userId, username);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function viewUserConversations(userId) {
|
|||
|
|
try {
|
|||
|
|
// Ensure userId is always treated as a string
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
console.log(`🔍 Loading conversations for user ${userIdStr} (type: ${typeof userIdStr})`);
|
|||
|
|
console.log(`🔍 Original userId: ${userId} (type: ${typeof userId})`);
|
|||
|
|
console.log(`🔍 userIdStr: ${userIdStr} (type: ${typeof userIdStr})`);
|
|||
|
|
|
|||
|
|
const result = await apiCall(`/dms/users/${userIdStr}/conversations?limit=100`);
|
|||
|
|
|
|||
|
|
console.log('📡 API Response:', result);
|
|||
|
|
console.log('📡 API URL called:', `/dms/users/${userIdStr}/conversations?limit=100`);
|
|||
|
|
|
|||
|
|
if (result.conversations && result.conversations.length > 0) {
|
|||
|
|
console.log(`✅ Found ${result.conversations.length} conversations`);
|
|||
|
|
displayUserConversations(userIdStr, result.conversations);
|
|||
|
|
} else {
|
|||
|
|
console.log('⚠️ No conversations found in response');
|
|||
|
|
showNotification('No conversations found for this user', 'info');
|
|||
|
|
// Go back to user list
|
|||
|
|
loadDMUsers();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to load user conversations:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function displayUserConversations(userId, conversations) {
|
|||
|
|
console.log(`🎨 Displaying conversations for user ${userId}:`, conversations);
|
|||
|
|
|
|||
|
|
// Create a modal or expand the user card to show conversations
|
|||
|
|
const container = document.getElementById('dm-users-list');
|
|||
|
|
|
|||
|
|
let html = `
|
|||
|
|
<div class="conversation-view">
|
|||
|
|
<button onclick="loadDMUsers()" style="margin-bottom: 1rem;">← Back to DM Users</button>
|
|||
|
|
<h4>💬 Conversations with User ${userId}</h4>
|
|||
|
|
<div class="conversations-list">
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
if (!conversations || conversations.length === 0) {
|
|||
|
|
html += '<p>No conversations found for this user.</p>';
|
|||
|
|
} else {
|
|||
|
|
conversations.forEach((msg, index) => {
|
|||
|
|
console.log(`📝 Processing message ${index}:`, msg);
|
|||
|
|
const timestamp = new Date(msg.timestamp).toLocaleString();
|
|||
|
|
const sender = msg.is_bot_message ? '🤖 Miku' : '👤 User';
|
|||
|
|
const content = msg.content || '[No text content]';
|
|||
|
|
|
|||
|
|
const messageId = msg.message_id || msg.timestamp; // Use message_id or timestamp as identifier
|
|||
|
|
const escapedContent = content.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|||
|
|
|
|||
|
|
// Debug: Log message details
|
|||
|
|
console.log(`📝 Message ${index}: id=${messageId}, is_bot=${msg.is_bot_message}, content="${content.substring(0, 30)}..."`);
|
|||
|
|
|
|||
|
|
// Only show delete button for bot messages (Miku can only delete her own messages)
|
|||
|
|
const deleteButton = msg.is_bot_message ?
|
|||
|
|
`<button class="delete-message-btn" onclick="deleteConversation('${userId}', '${messageId}', '${escapedContent}')"
|
|||
|
|
style="background: #f44336; color: white; border: none; padding: 2px 6px; font-size: 12px; border-radius: 3px; margin-left: 10px;"
|
|||
|
|
title="Delete this Miku message (ID: ${messageId})">
|
|||
|
|
🗑️ Delete
|
|||
|
|
</button>` : '';
|
|||
|
|
|
|||
|
|
html += `
|
|||
|
|
<div class="conversation-message ${msg.is_bot_message ? 'bot-message' : 'user-message'}">
|
|||
|
|
<div class="message-header">
|
|||
|
|
<span class="sender">${sender}</span>
|
|||
|
|
<span class="timestamp">${timestamp}</span>
|
|||
|
|
${deleteButton}
|
|||
|
|
</div>
|
|||
|
|
<div class="message-content">${content}</div>
|
|||
|
|
${msg.attachments && msg.attachments.length > 0 ? `
|
|||
|
|
<div class="message-attachments">
|
|||
|
|
<strong>📎 Attachments:</strong>
|
|||
|
|
${msg.attachments.map(att => `
|
|||
|
|
<div class="attachment">
|
|||
|
|
- ${att.filename} (${att.size} bytes)
|
|||
|
|
<a href="${att.url}" target="_blank">🔗 View</a>
|
|||
|
|
</div>
|
|||
|
|
`).join('')}
|
|||
|
|
</div>
|
|||
|
|
` : ''}
|
|||
|
|
${msg.reactions && msg.reactions.length > 0 ? `
|
|||
|
|
<div class="message-reactions">
|
|||
|
|
${msg.reactions.map(reaction => {
|
|||
|
|
const reactionTime = new Date(reaction.added_at).toLocaleString();
|
|||
|
|
const reactorType = reaction.is_bot ? 'bot-reaction' : 'user-reaction';
|
|||
|
|
const reactorLabel = reaction.is_bot ? '🤖 Miku' : `👤 ${reaction.reactor_name}`;
|
|||
|
|
return `
|
|||
|
|
<div class="reaction-item" title="${reactorLabel} reacted at ${reactionTime}">
|
|||
|
|
<span class="reaction-emoji">${reaction.emoji}</span>
|
|||
|
|
<span class="reaction-by ${reactorType}">${reactorLabel}</span>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join('')}
|
|||
|
|
</div>
|
|||
|
|
` : ''}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
html += `
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
console.log('🎨 Generated HTML:', html);
|
|||
|
|
container.innerHTML = html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function exportUserDMs(userId) {
|
|||
|
|
try {
|
|||
|
|
// Ensure userId is always treated as a string
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/export?format=txt`);
|
|||
|
|
showNotification(`DM export completed for user ${userIdStr}`);
|
|||
|
|
// You could trigger a download here if the file is accessible
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to export user DMs:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function deleteUserDMs(userId) {
|
|||
|
|
// Ensure userId is always treated as a string
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`Are you sure you want to delete all DM logs for user ${userIdStr}? This action cannot be undone.`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}`, 'DELETE');
|
|||
|
|
showNotification(`Deleted DM logs for user ${userIdStr}`);
|
|||
|
|
loadDMUsers(); // Refresh the list
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to delete user DMs:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ========== User Blocking & Advanced Deletion Functions ==========
|
|||
|
|
|
|||
|
|
async function blockUser(userId, username) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`Are you sure you want to block ${username} (${userIdStr}) from sending DMs to Miku?`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/block`, 'POST');
|
|||
|
|
showNotification(`${username} has been blocked from sending DMs`);
|
|||
|
|
loadDMUsers(); // Refresh the list
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to block user:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function unblockUser(userId, username) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/unblock`, 'POST');
|
|||
|
|
showNotification(`${username} has been unblocked`);
|
|||
|
|
loadBlockedUsers(); // Refresh blocked users list
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to unblock user:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function deleteAllUserConversations(userId, username) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`⚠️ DELETE ALL CONVERSATIONS with ${username} (${userIdStr})?\n\nThis will:\n• Delete ALL Miku messages from Discord DM\n• Clear all conversation logs\n• Keep the user record\n\nThis action CANNOT be undone!\n\nClick OK to confirm deletion.`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/conversations/delete-all`, 'POST');
|
|||
|
|
showNotification(`Bulk deletion queued for ${username} (deleting all Miku messages from Discord and logs)`);
|
|||
|
|
setTimeout(() => {
|
|||
|
|
loadDMUsers(); // Refresh after a delay to allow deletion to process
|
|||
|
|
}, 2000);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to delete conversations:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function deleteUserCompletely(userId, username) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`🚨 COMPLETELY DELETE USER ${username} (${userIdStr})?\n\nThis will:\n• Delete ALL conversation history\n• Delete the entire user log file\n• Remove ALL traces of this user\n\nThis action is PERMANENT and CANNOT be undone!\n\nType "${username}" below to confirm:`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const confirmName = prompt(`Type the username "${username}" to confirm complete deletion:`);
|
|||
|
|
if (confirmName !== username) {
|
|||
|
|
showNotification('Deletion cancelled - username did not match', 'error');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/delete-completely`, 'POST');
|
|||
|
|
showNotification(`${username} has been completely deleted from the system`);
|
|||
|
|
loadDMUsers(); // Refresh the list
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to delete user completely:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function deleteConversation(userId, conversationId, messageContent) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`Delete this Miku message from Discord and logs?\n\n"${messageContent.substring(0, 100)}${messageContent.length > 100 ? '...' : ''}"\n\nThis will:\n• Delete the message from Discord DM\n• Remove it from conversation logs\n\nNote: Only Miku's messages can be deleted.\nThis action cannot be undone.`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await apiCall(`/dms/users/${userIdStr}/conversations/${conversationId}/delete`, 'POST');
|
|||
|
|
showNotification('Miku message deletion queued (deleting from both Discord and logs)');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
viewUserConversations(userId); // Refresh after a short delay to allow deletion to process
|
|||
|
|
}, 1000);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to delete conversation:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function analyzeUserInteraction(userId, username) {
|
|||
|
|
const userIdStr = String(userId);
|
|||
|
|
|
|||
|
|
if (!confirm(`Run DM interaction analysis for ${username}?\n\nThis will:\n• Analyze their messages from the last 24 hours\n• Generate a sentiment report\n• Send report to bot owner\n\nMinimum 3 messages required for analysis.`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
showNotification(`Analyzing ${username}'s interactions...`, 'info');
|
|||
|
|
|
|||
|
|
const result = await apiCall(`/dms/users/${userIdStr}/analyze`, 'POST');
|
|||
|
|
|
|||
|
|
if (result.reported) {
|
|||
|
|
showNotification(`✅ Analysis complete! Report sent to bot owner for ${username}`);
|
|||
|
|
} else {
|
|||
|
|
showNotification(`📊 Analysis complete for ${username} (not enough messages or already reported today)`);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to analyze user:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function runDailyAnalysis() {
|
|||
|
|
if (!confirm('Run the daily DM interaction analysis now?\n\nThis will:\n• Analyze all DM users from the last 24 hours\n• Report one significant interaction to the bot owner\n• Skip users already reported today\n\nNote: This runs automatically at 2 AM daily.')) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
showNotification('Starting DM interaction analysis...', 'info');
|
|||
|
|
|
|||
|
|
await apiCall('/dms/analysis/run', 'POST');
|
|||
|
|
showNotification('✅ DM analysis completed! Check bot owner\'s DMs for any reports.');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to run DM analysis:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function viewAnalysisReports() {
|
|||
|
|
try {
|
|||
|
|
showNotification('Loading analysis reports...', 'info');
|
|||
|
|
|
|||
|
|
const result = await apiCall('/dms/analysis/reports?limit=50');
|
|||
|
|
displayAnalysisReports(result.reports);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to load reports:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function displayAnalysisReports(reports) {
|
|||
|
|
const container = document.getElementById('dm-users-list');
|
|||
|
|
|
|||
|
|
if (!reports || reports.length === 0) {
|
|||
|
|
container.innerHTML = `
|
|||
|
|
<div style="text-align: center; padding: 2rem;">
|
|||
|
|
<p>No analysis reports found yet.</p>
|
|||
|
|
<button onclick="loadDMUsers()" style="margin-top: 1rem;">← Back to DM Users</button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let html = `
|
|||
|
|
<div style="margin-bottom: 1rem;">
|
|||
|
|
<button onclick="loadDMUsers()">← Back to DM Users</button>
|
|||
|
|
<span style="margin-left: 1rem; color: #aaa;">${reports.length} reports found</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="display: grid; gap: 1rem;">
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
reports.forEach(report => {
|
|||
|
|
const sentimentColor =
|
|||
|
|
report.sentiment_score >= 5 ? '#4caf50' :
|
|||
|
|
report.sentiment_score <= -3 ? '#f44336' :
|
|||
|
|
'#2196f3';
|
|||
|
|
|
|||
|
|
const sentimentEmoji =
|
|||
|
|
report.sentiment_score >= 5 ? '😊' :
|
|||
|
|
report.sentiment_score <= -3 ? '😢' :
|
|||
|
|
'😐';
|
|||
|
|
|
|||
|
|
const timestamp = new Date(report.analyzed_at).toLocaleString();
|
|||
|
|
|
|||
|
|
html += `
|
|||
|
|
<div style="background: #2a2a2a; border-left: 4px solid ${sentimentColor}; padding: 1rem; border-radius: 4px;">
|
|||
|
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.5rem;">
|
|||
|
|
<div>
|
|||
|
|
<h4 style="margin: 0 0 0.25rem 0;">${sentimentEmoji} ${report.username}</h4>
|
|||
|
|
<p style="margin: 0; font-size: 0.85rem; color: #aaa;">User ID: ${report.user_id}</p>
|
|||
|
|
</div>
|
|||
|
|
<div style="text-align: right;">
|
|||
|
|
<div style="font-size: 1.2rem; font-weight: bold; color: ${sentimentColor};">
|
|||
|
|
${report.sentiment_score > 0 ? '+' : ''}${report.sentiment_score}/10
|
|||
|
|
</div>
|
|||
|
|
<div style="font-size: 0.75rem; color: #aaa; text-transform: uppercase;">
|
|||
|
|
${report.overall_sentiment}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div style="margin: 0.75rem 0; padding: 0.75rem; background: #1e1e1e; border-radius: 4px;">
|
|||
|
|
<strong>Miku's Feelings:</strong>
|
|||
|
|
<p style="margin: 0.5rem 0 0 0; font-style: italic;">"${report.your_feelings}"</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
${report.notable_moment ? `
|
|||
|
|
<div style="margin: 0.75rem 0; padding: 0.75rem; background: #1e1e1e; border-radius: 4px;">
|
|||
|
|
<strong>Notable Moment:</strong>
|
|||
|
|
<p style="margin: 0.5rem 0 0 0; font-style: italic;">"${report.notable_moment}"</p>
|
|||
|
|
</div>
|
|||
|
|
` : ''}
|
|||
|
|
|
|||
|
|
${report.key_behaviors && report.key_behaviors.length > 0 ? `
|
|||
|
|
<div style="margin: 0.75rem 0;">
|
|||
|
|
<strong>Key Behaviors:</strong>
|
|||
|
|
<ul style="margin: 0.5rem 0 0 0; padding-left: 1.5rem;">
|
|||
|
|
${report.key_behaviors.slice(0, 5).map(b => `<li>${b}</li>`).join('')}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
` : ''}
|
|||
|
|
|
|||
|
|
<div style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #444; font-size: 0.8rem; color: #aaa;">
|
|||
|
|
<span>📅 ${timestamp}</span>
|
|||
|
|
<span style="margin-left: 1rem;">💬 ${report.message_count} messages analyzed</span>
|
|||
|
|
<span style="margin-left: 1rem;">📄 ${report.filename}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
html += '</div>';
|
|||
|
|
container.innerHTML = html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadBlockedUsers() {
|
|||
|
|
try {
|
|||
|
|
const result = await apiCall('/dms/blocked-users');
|
|||
|
|
// Hide DM users list and show blocked users section
|
|||
|
|
document.getElementById('dm-users-list').style.display = 'none';
|
|||
|
|
document.getElementById('blocked-users-section').style.display = 'block';
|
|||
|
|
displayBlockedUsers(result.blocked_users);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to load blocked users:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function hideBlockedUsers() {
|
|||
|
|
// Show DM users list and hide blocked users section
|
|||
|
|
document.getElementById('dm-users-list').style.display = 'block';
|
|||
|
|
document.getElementById('blocked-users-section').style.display = 'none';
|
|||
|
|
loadDMUsers(); // Refresh DM users
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function displayBlockedUsers(blockedUsers) {
|
|||
|
|
const container = document.getElementById('blocked-users-list');
|
|||
|
|
|
|||
|
|
if (!blockedUsers || blockedUsers.length === 0) {
|
|||
|
|
container.innerHTML = '<p>No blocked users.</p>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let html = '<div class="blocked-users-grid">';
|
|||
|
|
|
|||
|
|
blockedUsers.forEach(user => {
|
|||
|
|
html += `
|
|||
|
|
<div class="blocked-user-card">
|
|||
|
|
<h4>🚫 ${user.username}</h4>
|
|||
|
|
<p><strong>ID:</strong> ${user.user_id}</p>
|
|||
|
|
<p><strong>Blocked:</strong> ${new Date(user.blocked_at).toLocaleString()}</p>
|
|||
|
|
<p><strong>Blocked by:</strong> ${user.blocked_by}</p>
|
|||
|
|
<div class="blocked-user-actions">
|
|||
|
|
<button onclick="unblockUser('${user.user_id}', '${user.username}')" style="background: #4caf50;">✅ Unblock</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
html += '</div>';
|
|||
|
|
container.innerHTML = html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function exportAllDMs() {
|
|||
|
|
try {
|
|||
|
|
const result = await apiCall('/dms/users');
|
|||
|
|
|
|||
|
|
let exportCount = 0;
|
|||
|
|
for (const user of (result.users || [])) {
|
|||
|
|
try {
|
|||
|
|
await exportUserDMs(user.user_id);
|
|||
|
|
exportCount++;
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error(`Failed to export DMs for user ${user.user_id}:`, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
showNotification(`Exported DMs for ${exportCount} users`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Failed to export all DMs:', error);
|
|||
|
|
}
|
|||
|
|
}
|