relatica/lib/riverpod_controllers/networking/link_preview_services.dart

83 wiersze
2.7 KiB
Dart

2023-03-19 20:27:57 +00:00
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
2023-03-19 20:27:57 +00:00
import 'package:html/parser.dart';
import 'package:result_monad/result_monad.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
2023-03-19 20:27:57 +00:00
import '../../models/exec_error.dart';
import '../../models/link_preview_data.dart';
import '../rp_provider_extension.dart';
import 'network_services.dart';
part 'link_preview_services.g.dart';
2023-03-19 20:27:57 +00:00
const ogTitleKey = 'og:title';
const ogDescriptionKey = 'og:description';
const ogSiteNameKey = 'og:site_name';
const ogImageKey = 'og:image';
@riverpod
Future<Result<LinkPreviewData, ExecError>> linkPreview(
Ref ref, String url) async {
return await ref.read(_openGraphDataProvider(url).future).transform((ogData) {
2023-03-19 20:27:57 +00:00
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,
);
}).execErrorCastAsync();
2023-03-19 20:27:57 +00:00
}
@riverpod
Future<Result<List<MapEntry<String, String>>, ExecError>> _openGraphData(
Ref ref, String url) async {
ref.cacheFor(const Duration(minutes: 5));
final headers = ref.watch(userAgentHeaderProvider);
final request = NetworkRequest(Uri.parse(url), headers: headers);
return await ref
.read(httpGetRawBytesProvider(request).future)
.transform((bodyBytes) {
final rawHtml = utf8.decode(bodyBytes);
2023-03-19 20:27:57 +00:00
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 openGraphTags ?? [];
}).mapError((error) {
final type = error is ExecError ? error.type : ErrorType.serverError;
final message = error is ExecError
? 'Error getting link preview: ${error.message}'
: 'Error getting link preview';
return ExecError(type: type, message: message);
2023-03-19 20:27:57 +00:00
});
}
extension OpenGraphFinders on List<MapEntry<String, String>> {
String getValue(String key) {
return firstWhere(
(e) => e.key == key,
orElse: () => const MapEntry('', ''),
).value;
}
List<String> getValues(String key) {
return where((e) => e.key == key).map((e) => e.value).toList();
}
}