import 'dart:convert'; import 'package:html/parser.dart'; import 'package:http/http.dart' as http; import 'package:result_monad/result_monad.dart'; import '../models/exec_error.dart'; import '../models/link_preview_data.dart'; const ogTitleKey = 'og:title'; const ogDescriptionKey = 'og:description'; const ogSiteNameKey = 'og:site_name'; const ogImageKey = 'og:image'; FutureResult getLinkPreview(String url) async { final result = await _getOpenGraphData(url); return result.andThenSuccess((ogData) { final title = ogData.getValue(ogTitleKey); final description = ogData.getValue(ogDescriptionKey); final siteName = ogData.getValue(ogSiteNameKey); final availableImageUrls = ogData.getValues(ogImageKey); final selectedImageUrl = availableImageUrls.isEmpty ? '' : availableImageUrls.first; return LinkPreviewData( link: url, title: title, description: description, siteName: siteName, availableImageUrls: availableImageUrls, selectedImageUrl: selectedImageUrl, ); }).execErrorCast(); } FutureResult>, dynamic> _getOpenGraphData( String url) async { return runCatchingAsync>>(() async { final response = await http.get(Uri.parse(url)); if (response.statusCode != 200) { return buildErrorResult( type: ErrorType.serverError, message: 'Error getting link preview: ${response.statusCode}', ); } final rawHtml = utf8.decode(response.bodyBytes); final htmlDoc = parse(rawHtml); final openGraphTags = htmlDoc.head ?.querySelectorAll("[property*='og:']") .map((p) { final key = p.attributes['property'] ?? ''; final value = p.attributes['content'] ?? ''; return MapEntry(key, value); }) .where((e) => e.key.isNotEmpty) .toList(); return Result.ok(openGraphTags ?? []); }); } extension OpenGraphFinders on List> { String getValue(String key) { return firstWhere( (e) => e.key == key, orElse: () => const MapEntry('', ''), ).value; } List getValues(String key) { return where((e) => e.key == key).map((e) => e.value).toList(); } }