62 lines
1.8 KiB
Dart
62 lines
1.8 KiB
Dart
import 'dart:convert';
|
|
import 'package:html/parser.dart' as html_parser;
|
|
import 'package:http/http.dart' as http;
|
|
|
|
class ScrapedRecipe {
|
|
final String title;
|
|
final List<String> ingredients;
|
|
final List<String> instructions;
|
|
final double? proteinGrams;
|
|
|
|
ScrapedRecipe({
|
|
required this.title,
|
|
required this.ingredients,
|
|
required this.instructions,
|
|
this.proteinGrams,
|
|
});
|
|
}
|
|
|
|
class RecipeScraperService {
|
|
Future<ScrapedRecipe?> extractFromUrl(String url) async {
|
|
final response = await http.get(Uri.parse(url));
|
|
if (response.statusCode != 200) return null;
|
|
|
|
final document = html_parser.parse(response.body);
|
|
final scriptTags =
|
|
document.querySelectorAll('script[type="application/ld+json"]');
|
|
|
|
for (final tag in scriptTags) {
|
|
try {
|
|
final data = jsonDecode(tag.text);
|
|
if (data is Map<String, dynamic> && data['@type'] == 'Recipe') {
|
|
return _parseJsonLd(data);
|
|
} else if (data is List) {
|
|
final recipe = data.firstWhere((e) => e['@type'] == 'Recipe',
|
|
orElse: () => null);
|
|
if (recipe != null) return _parseJsonLd(recipe);
|
|
}
|
|
} catch (_) {
|
|
continue;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ScrapedRecipe _parseJsonLd(Map<String, dynamic> json) {
|
|
return ScrapedRecipe(
|
|
title: json['name'] ?? 'Untitled Recipe',
|
|
ingredients: List<String>.from(json['recipeIngredient'] ?? []),
|
|
instructions: (json['recipeInstructions'] as List? ?? [])
|
|
.map((step) =>
|
|
step is Map ? step['text']?.toString() ?? '' : step.toString())
|
|
.toList(),
|
|
proteinGrams: double.tryParse(
|
|
json['nutrition']?['proteinContent']
|
|
?.toString()
|
|
.replaceAll(RegExp(r'[^0-9.]'), '') ??
|
|
'',
|
|
),
|
|
);
|
|
}
|
|
}
|