relatica/lib/riverpod_controllers/fediverse_server_validator_...

137 wiersze
3.6 KiB
Dart

import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:result_monad/result_monad.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../models/exec_error.dart';
import '../models/server_data.dart';
import 'networking/network_services.dart';
part 'fediverse_server_validator_services.g.dart';
final _blueSkyData = ServerData(
domainName: 'bsky.app',
isFediverse: true,
protocols: [
'ATProto',
],
);
final _threadsData = ServerData(
domainName: 'threads.net',
isFediverse: true,
protocols: [
'activitypub',
],
);
final _threadsWwwData = ServerData(
domainName: 'www.threads.net',
isFediverse: true,
protocols: [
'activitypub',
],
);
final _knownServers = <String, ServerData>{
threadsDomain: _threadsData,
threadsWwwDomain: _threadsData,
blueskyDomain: _blueSkyData,
};
const blueskyDomain = 'bsky.app';
const threadsDomain = 'threads.net';
const threadsWwwDomain = 'www.threads.net';
const softwareTypeDiaspora = 'diaspora';
@riverpod
Future<Result<bool, ExecError>> checkIfFromFediverse(
Ref ref,
String url,
) async {
final dataResult = await ref.watch(serverDataProvider(url).future);
return dataResult.mapValue((d) => d.isFediverse);
}
@riverpod
Future<Result<ServerData, ExecError>> serverData(Ref ref, String url) async {
final uri = Uri.tryParse(url);
if (uri == null || uri.scheme != 'https') {
return buildErrorResult(
type: ErrorType.parsingError,
message: 'Invalid URL: $url',
);
}
final domain = uri.host;
ServerData? data = _knownServers[domain];
if (data != null) {
return Result.ok(data);
}
final serverData =
await ref.read(serverDataFromServerProvider(domain).future);
return Result.ok(serverData);
}
@riverpod
Future<ServerData> serverDataFromServer(Ref ref, String domainName) async {
if (domainName == threadsDomain) {
return _threadsData;
}
if (domainName == threadsWwwDomain) {
return _threadsWwwData;
}
final uri = Uri.https(
domainName,
'/.well-known/nodeinfo',
);
final result = await ref
.read(httpGetProvider(NetworkRequest(uri)).future)
.transform((page) => jsonDecode(page.data))
.andThen(
(json) =>
json is Map<String, dynamic> ? Result.ok(json) : Result.error(''),
)
.transform((json) => json['links'] ?? [])
.andThen(
(nodeInfos) =>
nodeInfos.isNotEmpty ? Result.ok(nodeInfos.last) : Result.error(''),
)
.andThenAsync((nodeInfo) async {
final rel = nodeInfo['rel']?.toString() ?? '';
if (!rel.startsWith('http://nodeinfo.diaspora.software/ns/schema/')) {
return Result.error('');
}
final nodeInfoUrl = Uri.tryParse(nodeInfo['href'] ?? '');
if (nodeInfoUrl == null) {
return Result.error('');
}
return await ref
.read(httpGetProvider(NetworkRequest(nodeInfoUrl)).future);
})
.transform((nodeInfoData) => jsonDecode(nodeInfoData.data))
.transform((nodeInfoJson) {
final softwareName =
nodeInfoJson['software']?['name']?.toString() ?? '';
final softwareVersion =
nodeInfoJson['software']?['version']?.toString() ?? '';
final isFediverse =
softwareName.isNotEmpty && softwareVersion.isNotEmpty;
return ServerData(
domainName: domainName,
isFediverse: isFediverse,
softwareName: softwareName,
softwareVersion: softwareVersion,
);
});
return result.getValueOrElse(
() => ServerData(domainName: domainName, isFediverse: false));
}