This commit is contained in:
Slipstream 2025-05-10 11:14:46 -06:00
parent 8818d216a3
commit ae3080b0de
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 192 additions and 19 deletions

View File

@ -105,19 +105,121 @@ async def get_user_guilds(
): ):
"""Get all guilds the user is an admin of.""" """Get all guilds the user is an admin of."""
try: try:
# This would normally fetch guilds from Discord API or the bot # First, try to use the real implementation from api_server.py
# For now, we'll return a mock response try:
# TODO: Replace mock data with actual API call to Discord # Import the dashboard_get_user_guilds function from api_server
guilds = [ from api_service.api_server import dashboard_get_user_guilds
Guild(id="123456789", name="My Awesome Server", icon_url="https://cdn.discordapp.com/icons/123456789/abc123def456ghi789jkl012mno345pqr.png"),
Guild(id="987654321", name="Another Great Server", icon_url="https://cdn.discordapp.com/icons/987654321/zyx987wvu654tsr321qpo098mlk765jih.png") # Call the function with the user
] guilds_data = await dashboard_get_user_guilds(user)
return guilds
# Convert the data to Guild objects
guilds = []
for guild in guilds_data:
# Create icon URL if icon is available
icon_url = None
if guild.get('icon'):
icon_url = f"https://cdn.discordapp.com/icons/{guild['id']}/{guild['icon']}.png"
guilds.append(Guild(
id=guild['id'],
name=guild['name'],
icon_url=icon_url
))
log.info(f"Successfully fetched {len(guilds)} guilds for user {user.get('user_id')} using api_server implementation")
return guilds
except ImportError as e:
log.warning(f"Could not import dashboard_get_user_guilds from api_server: {e}")
# Fall back to direct implementation
except Exception as e:
log.warning(f"Error using dashboard_get_user_guilds from api_server: {e}")
# Fall back to direct implementation
# Direct implementation as fallback
from global_bot_accessor import get_bot_instance
import aiohttp
# Check if settings_manager is available
bot = get_bot_instance()
if not bot or not bot.pg_pool:
log.warning("Bot instance or PostgreSQL pool not available for get_user_guilds")
# Fall back to mock data since we can't access the real data
log.info("Using mock data for user guilds as fallback")
return [
Guild(id="123456789", name="My Awesome Server (Mock)", icon_url=None),
Guild(id="987654321", name="Another Great Server (Mock)", icon_url=None)
]
# Get Discord API URLs from environment or use defaults
DISCORD_API_URL = "https://discord.com/api/v10"
DISCORD_USER_GUILDS_URL = f"{DISCORD_API_URL}/users/@me/guilds"
# Get access token from user
access_token = user.get('access_token')
if not access_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Access token not found in user session"
)
# Create headers for Discord API request
user_headers = {'Authorization': f'Bearer {access_token}'}
# Create a temporary aiohttp session
async with aiohttp.ClientSession() as session:
# 1. Fetch guilds user is in from Discord
log.debug(f"Fetching user guilds from {DISCORD_USER_GUILDS_URL}")
async with session.get(DISCORD_USER_GUILDS_URL, headers=user_headers) as resp:
if resp.status == 401:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Discord API authentication failed. Please log in again."
)
resp.raise_for_status()
user_guilds = await resp.json()
log.debug(f"Fetched {len(user_guilds)} guilds for user {user.get('user_id')}")
# 2. Get bot guilds from the bot instance
bot_guild_ids = set()
if bot.guilds:
bot_guild_ids = {guild.id for guild in bot.guilds}
log.debug(f"Bot is in {len(bot_guild_ids)} guilds")
else:
log.warning("Bot has no guilds or bot.guilds is not accessible")
# 3. Filter user guilds
manageable_guilds = []
ADMINISTRATOR_PERMISSION = 0x8
for guild in user_guilds:
guild_id = int(guild['id'])
permissions = int(guild['permissions'])
if (permissions & ADMINISTRATOR_PERMISSION) == ADMINISTRATOR_PERMISSION and guild_id in bot_guild_ids:
# Create icon URL if icon is available
icon_url = None
if guild.get('icon'):
icon_url = f"https://cdn.discordapp.com/icons/{guild['id']}/{guild['icon']}.png"
manageable_guilds.append(Guild(
id=guild['id'],
name=guild['name'],
icon_url=icon_url
))
log.info(f"Found {len(manageable_guilds)} manageable guilds for user {user.get('user_id')}")
return manageable_guilds
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e: except Exception as e:
log.error(f"Error getting user guilds: {e}") log.error(f"Error getting user guilds: {e}")
# Return a more user-friendly error message
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error getting user guilds: {str(e)}" detail="Failed to retrieve your Discord servers. Please try again later."
) )
@router.get("/guilds/{guild_id}/channels", response_model=List[Channel]) @router.get("/guilds/{guild_id}/channels", response_model=List[Channel])

View File

@ -467,7 +467,7 @@ function loadUserGuilds() {
serverListContainer.innerHTML = ''; // Clear loading state serverListContainer.innerHTML = ''; // Clear loading state
if (!guilds || guilds.length === 0) { if (!guilds || guilds.length === 0) {
serverListContainer.innerHTML = '<p>No servers found where you have admin permissions.</p>'; serverListContainer.innerHTML = '<div class="alert alert-info">No servers found where you have admin permissions.</div>';
return; return;
} }
@ -477,18 +477,64 @@ function loadUserGuilds() {
guildElement.style.cursor = 'pointer'; guildElement.style.cursor = 'pointer';
guildElement.dataset.guildId = guild.id; guildElement.dataset.guildId = guild.id;
const iconElement = document.createElement('img'); // Check if we have an icon URL
iconElement.className = 'server-icon'; if (guild.icon_url) {
iconElement.src = guild.icon_url || 'img/default-icon.png'; // Provide a default icon path // Create an image element for the server icon
iconElement.alt = `${guild.name} icon`; const iconElement = document.createElement('img');
iconElement.width = 50; iconElement.className = 'server-icon';
iconElement.height = 50; iconElement.src = guild.icon_url;
iconElement.alt = `${guild.name} icon`;
iconElement.width = 50;
iconElement.height = 50;
// Add error handling for icon loading
iconElement.onerror = function() {
console.log(`Failed to load icon for server ${guild.name}, using text fallback`);
// Replace the img with a text-based fallback
const fallbackIcon = createTextFallbackIcon(guild.name);
this.parentNode.replaceChild(fallbackIcon, this);
};
guildElement.appendChild(iconElement);
} else {
// No icon URL provided, use text-based fallback immediately
const fallbackIcon = createTextFallbackIcon(guild.name);
guildElement.appendChild(fallbackIcon);
}
// Helper function to create a text-based fallback icon
function createTextFallbackIcon(serverName) {
const fallbackDiv = document.createElement('div');
fallbackDiv.className = 'server-icon-fallback';
fallbackDiv.style.width = '50px';
fallbackDiv.style.height = '50px';
fallbackDiv.style.backgroundColor = '#5865F2'; // Discord blue
fallbackDiv.style.borderRadius = '50%';
fallbackDiv.style.display = 'flex';
fallbackDiv.style.alignItems = 'center';
fallbackDiv.style.justifyContent = 'center';
fallbackDiv.style.color = 'white';
fallbackDiv.style.fontWeight = 'bold';
fallbackDiv.style.fontSize = '18px';
// Get the first letter of each word in the server name
const initials = serverName
.split(' ')
.map(word => word.charAt(0))
.join('')
.substring(0, 2)
.toUpperCase();
fallbackDiv.textContent = initials;
return fallbackDiv;
}
const nameElement = document.createElement('span'); const nameElement = document.createElement('span');
nameElement.className = 'server-name'; nameElement.className = 'server-name';
nameElement.textContent = guild.name; nameElement.textContent = guild.name;
guildElement.appendChild(iconElement); // Icon is already appended in the icon creation logic above
guildElement.appendChild(nameElement); guildElement.appendChild(nameElement);
guildElement.addEventListener('click', () => { guildElement.addEventListener('click', () => {
@ -514,8 +560,33 @@ function loadUserGuilds() {
}) })
.catch(error => { .catch(error => {
console.error('Error loading user guilds:', error); console.error('Error loading user guilds:', error);
serverListContainer.innerHTML = '<p class="text-danger">Error loading servers. Please try again.</p>';
Toast.error('Failed to load your servers.'); // Create a more detailed error message with troubleshooting steps
let errorMessage = '<div class="alert alert-danger">';
errorMessage += '<h4>Error Loading Servers</h4>';
errorMessage += '<p>We encountered a problem while trying to load your servers. This could be due to:</p>';
errorMessage += '<ul>';
errorMessage += '<li>Database connection issues</li>';
errorMessage += '<li>Discord API rate limiting</li>';
errorMessage += '<li>Missing bot configuration</li>';
errorMessage += '</ul>';
errorMessage += '<p>Troubleshooting steps:</p>';
errorMessage += '<ol>';
errorMessage += '<li>Refresh the page and try again</li>';
errorMessage += '<li>Check that the bot is properly configured with database access</li>';
errorMessage += '<li>Ensure the Discord bot token is properly set</li>';
errorMessage += '</ol>';
errorMessage += '<button id="retry-load-guilds" class="btn btn-primary">Retry</button>';
errorMessage += '</div>';
serverListContainer.innerHTML = errorMessage;
// Add event listener to the retry button
document.getElementById('retry-load-guilds').addEventListener('click', () => {
loadUserGuilds(); // Retry loading guilds
});
Toast.error('Failed to load your servers. See details on screen.');
}); });
} }