# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Site Discovery helper for SharePoint operations. Handles SharePoint site discovery, filtering, and resolution. """ import logging import urllib.parse from typing import Dict, Any, List, Optional logger = logging.getLogger(__name__) class SiteDiscoveryHelper: """Helper for SharePoint site discovery and resolution""" def __init__(self, methodInstance): """ Initialize site discovery helper. Args: methodInstance: Instance of MethodSharepoint (for access to services) """ self.method = methodInstance self.services = methodInstance.services async def discoverSharePointSites(self, limit: Optional[int] = None) -> List[Dict[str, Any]]: """ Discover SharePoint sites accessible to the user via Microsoft Graph API. Args: limit: Optional limit on number of sites to return Returns: List of site information dictionaries """ try: # Query Microsoft Graph to get sites the user has access to endpoint = "sites?search=*" if limit: endpoint += f"&$top={limit}" result = await self.method.apiClient.makeGraphApiCall(endpoint) if "error" in result: logger.error(f"Error discovering SharePoint sites: {result['error']}") return [] sites = result.get("value", []) if limit: sites = sites[:limit] logger.info(f"Discovered {len(sites)} SharePoint sites" + (f" (limited to {limit})" if limit else "")) # Process and return site information processedSites = [] for site in sites: siteInfo = { "id": site.get("id"), "displayName": site.get("displayName"), "name": site.get("name"), "webUrl": site.get("webUrl"), "description": site.get("description"), "createdDateTime": site.get("createdDateTime"), "lastModifiedDateTime": site.get("lastModifiedDateTime") } processedSites.append(siteInfo) logger.debug(f"Site: {siteInfo['displayName']} - {siteInfo['webUrl']}") return processedSites except Exception as e: logger.error(f"Error discovering SharePoint sites: {str(e)}") return [] def extractHostnameFromWebUrl(self, webUrl: str) -> Optional[str]: """Extract hostname from SharePoint webUrl (e.g., https://pcuster.sharepoint.com)""" try: if not webUrl: return None parsed = urllib.parse.urlparse(webUrl) return parsed.hostname except Exception as e: logger.error(f"Error extracting hostname from webUrl '{webUrl}': {str(e)}") return None def extractSiteFromStandardPath(self, pathQuery: str) -> Optional[Dict[str, str]]: """ Extract site name from Microsoft-standard server-relative path. Delegates to SharePoint service. """ return self.services.sharepoint.extractSiteFromStandardPath(pathQuery) async def getSiteByStandardPath(self, sitePath: str) -> Optional[Dict[str, Any]]: """ Get SharePoint site directly by Microsoft-standard path. Delegates to SharePoint service. """ return await self.services.sharepoint.getSiteByStandardPath(sitePath) def filterSitesByHint(self, sites: List[Dict[str, Any]], siteHint: str) -> List[Dict[str, Any]]: """ Filter discovered sites by a human-entered site hint. Delegates to SharePoint service. """ return self.services.sharepoint.filterSitesByHint(sites, siteHint) async def getSiteId(self, hostname: str, sitePath: str) -> str: """ Get SharePoint site ID from hostname and site path. Args: hostname: SharePoint hostname sitePath: Site path Returns: Site ID string """ try: endpoint = f"sites/{hostname}:/{sitePath}" result = await self.method.apiClient.makeGraphApiCall(endpoint) if "error" in result: logger.error(f"Error getting site ID: {result['error']}") return "" return result.get("id", "") except Exception as e: logger.error(f"Error getting site ID: {str(e)}") return "" async def resolveSitesFromPathQuery(self, pathQuery: str) -> tuple[List[Dict[str, Any]], Optional[str]]: """ Resolve sites from pathQuery using SharePoint service helper methods. Args: pathQuery: Path query string Returns: Tuple of (sites list, error message) """ try: # Validate pathQuery format isValid, errorMsg = self.services.sharepoint.validatePathQuery(pathQuery) if not isValid: return [], errorMsg # Resolve sites using service helper sites = await self.services.sharepoint.resolveSitesFromPathQuery(pathQuery) if not sites: return [], "No SharePoint sites found or accessible" return sites, None except Exception as e: logger.error(f"Error resolving sites from pathQuery '{pathQuery}': {str(e)}") return [], f"Error resolving sites from pathQuery: {str(e)}" def parseSiteUrl(self, siteUrl: str) -> Dict[str, str]: """Parse SharePoint site URL to extract hostname and site path""" try: parsed = urllib.parse.urlparse(siteUrl) hostname = parsed.hostname path = parsed.path.strip('/') return { "hostname": hostname, "sitePath": path } except Exception as e: logger.error(f"Error parsing site URL {siteUrl}: {str(e)}") return {"hostname": "", "sitePath": ""}