using DysonNetwork.Sphere.Permission; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; namespace DysonNetwork.Sphere.Connection.WebReader; /// /// Controller for web scraping and link preview services /// [ApiController] [Route("/scrap")] [EnableRateLimiting("fixed")] public class WebReaderController(WebReaderService reader, ILogger logger) : ControllerBase { /// /// Retrieves a preview for the provided URL /// /// URL-encoded link to generate preview for /// Link preview data including title, description, and image [HttpGet("link")] public async Task> ScrapLink([FromQuery] string url) { if (string.IsNullOrEmpty(url)) { return BadRequest(new { error = "URL parameter is required" }); } try { // Ensure URL is properly decoded var decodedUrl = UrlDecoder.Decode(url); // Validate URL format if (!Uri.TryCreate(decodedUrl, UriKind.Absolute, out _)) { return BadRequest(new { error = "Invalid URL format" }); } var linkEmbed = await reader.GetLinkPreviewAsync(decodedUrl); return Ok(linkEmbed); } catch (WebReaderException ex) { logger.LogWarning(ex, "Error scraping link: {Url}", url); return BadRequest(new { error = ex.Message }); } catch (Exception ex) { logger.LogError(ex, "Unexpected error scraping link: {Url}", url); return StatusCode(StatusCodes.Status500InternalServerError, new { error = "An unexpected error occurred while processing the link" }); } } /// /// Force invalidates the cache for a specific URL /// [HttpDelete("link/cache")] [Authorize] [RequiredPermission("maintenance", "cache.scrap")] public async Task InvalidateCache([FromQuery] string url) { if (string.IsNullOrEmpty(url)) { return BadRequest(new { error = "URL parameter is required" }); } await reader.InvalidateCacheForUrlAsync(url); return Ok(new { message = "Cache invalidated for URL" }); } /// /// Force invalidates all cached link previews /// [HttpDelete("cache/all")] [Authorize] [RequiredPermission("maintenance", "cache.scrap")] public async Task InvalidateAllCache() { await reader.InvalidateAllCachedPreviewsAsync(); return Ok(new { message = "All link preview caches invalidated" }); } } /// /// Helper class for URL decoding /// public static class UrlDecoder { public static string Decode(string url) { // First check if URL is already decoded if (!url.Contains('%') && !url.Contains('+')) { return url; } try { return System.Net.WebUtility.UrlDecode(url); } catch { // If decoding fails, return the original string return url; } } }