mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 21:43:31 +00:00
228 lines
7.9 KiB
Dart
228 lines
7.9 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:logging/logging.dart';
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
import 'models/TimelineIdentifiers.dart';
|
|
import 'models/credentials.dart';
|
|
import 'models/exec_error.dart';
|
|
import 'models/timeline_entry.dart';
|
|
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
|
|
|
class FriendicaClient {
|
|
static final _logger = Logger('$FriendicaClient');
|
|
final Credentials _credentials;
|
|
late final String _authHeader;
|
|
|
|
String get serverName => _credentials.serverName;
|
|
|
|
Credentials get credentials => _credentials;
|
|
|
|
FriendicaClient({required Credentials credentials})
|
|
: _credentials = credentials {
|
|
final authenticationString =
|
|
"${_credentials.username}:${_credentials.password}";
|
|
final encodedAuthString = base64Encode(utf8.encode(authenticationString));
|
|
_authHeader = "Basic $encodedAuthString";
|
|
}
|
|
|
|
FutureResult<List<TimelineEntry>, ExecError> getUserTimeline(
|
|
{String userId = '', int page = 1, int count = 10}) async {
|
|
_logger.finest(() => 'Getting user timeline for $userId');
|
|
final baseUrl = 'https://$serverName/api/statuses/user_timeline?';
|
|
final pagingData = 'count=$count&page=$page';
|
|
final url = userId.isEmpty
|
|
? '$baseUrl$pagingData'
|
|
: '${baseUrl}screen_name=$userId$pagingData';
|
|
final request = Uri.parse(url);
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(postsJson) async => postsJson
|
|
.map((json) => TimelineEntryMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error as ExecError);
|
|
}
|
|
|
|
FutureResult<List<TimelineEntry>, ExecError> getTimeline(
|
|
{required TimelineIdentifiers type,
|
|
int sinceId = 0,
|
|
int limit = 100}) async {
|
|
final String timelinePath = _typeToTimelinePath(type);
|
|
final String timelineQPs = _typeToTimelineQueryParameters(type);
|
|
final baseUrl = 'https://$serverName/api/v1/timelines/$timelinePath';
|
|
final pagingData =
|
|
sinceId == 0 ? 'limit=$limit' : 'since_id=$sinceId&limit=$limit';
|
|
final url = '$baseUrl?$pagingData&$timelineQPs';
|
|
final request = Uri.parse(url);
|
|
_logger.finest(
|
|
() => 'Getting home timeline limit $limit since $sinceId : $url');
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(postsJson) async => postsJson
|
|
.map((json) => TimelineEntryMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error as ExecError);
|
|
}
|
|
|
|
FutureResult<List<TimelineEntry>, ExecError> getPostOrComment(String id,
|
|
{bool fullContext = false}) async {
|
|
_logger.finest(
|
|
() => 'Getting entry for status $id, full context? $fullContext');
|
|
return (await runCatchingAsync(() async {
|
|
final baseUrl = 'https://$serverName/api/v1/statuses/$id';
|
|
final url = fullContext ? '$baseUrl/context' : baseUrl;
|
|
final request = Uri.parse(url);
|
|
return (await _getApiRequest(request).andThenSuccessAsync((json) async {
|
|
if (fullContext) {
|
|
final ancestors = json['ancestors'] as List<dynamic>;
|
|
final descendants = json['descendants'] as List<dynamic>;
|
|
final items = [
|
|
...ancestors
|
|
.map((a) => TimelineEntryMastodonExtensions.fromJson(a)),
|
|
...descendants
|
|
.map((d) => TimelineEntryMastodonExtensions.fromJson(d))
|
|
];
|
|
return items;
|
|
} else {
|
|
return [TimelineEntryMastodonExtensions.fromJson(json)];
|
|
}
|
|
}));
|
|
}))
|
|
.mapError((error) => ExecError(
|
|
type: ErrorType.parsingError,
|
|
message: error.toString(),
|
|
));
|
|
}
|
|
|
|
FutureResult<TimelineEntry, ExecError> createNewPost(String text) async {
|
|
_logger.finest(() => 'Creating post');
|
|
final url = Uri.parse('https://$serverName/api/v1/statuses');
|
|
final body = {'status': text, 'spoiler_text': 'For Testing Only'};
|
|
print(body);
|
|
final result = await _postUrl(url, body);
|
|
if (result.isFailure) {
|
|
return result.errorCast();
|
|
}
|
|
|
|
final responseText = result.value;
|
|
|
|
return runCatching<TimelineEntry>(() {
|
|
final json = jsonDecode(responseText);
|
|
return Result.ok(TimelineEntryMastodonExtensions.fromJson(json));
|
|
}).mapError((error) {
|
|
return ExecError(type: ErrorType.parsingError, message: error.toString());
|
|
});
|
|
}
|
|
|
|
FutureResult<TimelineEntry, ExecError> changeFavoriteStatus(
|
|
String id, bool status) async {
|
|
final action = status ? 'favourite' : 'unfavourite';
|
|
final url = Uri.parse('https://$serverName/api/v1/statuses/$id/$action');
|
|
final result = await _postUrl(url, {});
|
|
if (result.isFailure) {
|
|
return result.errorCast();
|
|
}
|
|
|
|
final responseText = result.value;
|
|
|
|
return runCatching<TimelineEntry>(() {
|
|
final json = jsonDecode(responseText);
|
|
return Result.ok(TimelineEntryMastodonExtensions.fromJson(json));
|
|
}).mapError((error) {
|
|
return ExecError(type: ErrorType.parsingError, message: error.toString());
|
|
});
|
|
}
|
|
|
|
FutureResult<String, ExecError> getMyProfile() async {
|
|
_logger.finest(() => 'Getting logged in user profile');
|
|
final request = Uri.parse('https://$serverName/api/friendica/profile/show');
|
|
return (await _getApiRequest(request))
|
|
.mapValue((value) => value.toString());
|
|
}
|
|
|
|
FutureResult<String, ExecError> _getUrl(Uri url) async {
|
|
try {
|
|
final response = await http.get(
|
|
url,
|
|
headers: {
|
|
'Authorization': _authHeader,
|
|
'Content-Type': 'application/json; charset=UTF-8'
|
|
},
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(response.body);
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> _postUrl(
|
|
Uri url, Map<String, dynamic> body) async {
|
|
try {
|
|
final response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Authorization': _authHeader,
|
|
'Content-Type': 'application/json; charset=UTF-8'
|
|
},
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(response.body);
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<List<dynamic>, ExecError> _getApiListRequest(Uri url) async {
|
|
return (await _getUrl(url).andThenSuccessAsync(
|
|
(jsonText) async => jsonDecode(jsonText) as List<dynamic>))
|
|
.mapError((error) => error as ExecError);
|
|
}
|
|
|
|
FutureResult<dynamic, ExecError> _getApiRequest(Uri url) async {
|
|
return (await _getUrl(url)
|
|
.andThenSuccessAsync((jsonText) async => jsonDecode(jsonText)))
|
|
.mapError((error) => error as ExecError);
|
|
}
|
|
|
|
String _typeToTimelinePath(TimelineIdentifiers type) {
|
|
switch (type.timeline) {
|
|
case TimelineType.home:
|
|
return 'home';
|
|
case TimelineType.global:
|
|
return 'public';
|
|
case TimelineType.local:
|
|
return 'public';
|
|
case TimelineType.tag:
|
|
case TimelineType.profile:
|
|
case TimelineType.self:
|
|
throw UnimplementedError('These types are not supported yet');
|
|
}
|
|
}
|
|
|
|
String _typeToTimelineQueryParameters(TimelineIdentifiers type) {
|
|
switch (type.timeline) {
|
|
case TimelineType.home:
|
|
case TimelineType.global:
|
|
return '';
|
|
case TimelineType.local:
|
|
return 'local=true';
|
|
case TimelineType.tag:
|
|
case TimelineType.profile:
|
|
case TimelineType.self:
|
|
throw UnimplementedError('These types are not supported yet');
|
|
}
|
|
}
|
|
}
|