kopia lustrzana https://gitlab.com/mysocialportal/relatica
667 wiersze
24 KiB
Dart
667 wiersze
24 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http_parser/http_parser.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
import 'globals.dart';
|
|
import 'models/TimelineIdentifiers.dart';
|
|
import 'models/connection.dart';
|
|
import 'models/credentials.dart';
|
|
import 'models/exec_error.dart';
|
|
import 'models/gallery_data.dart';
|
|
import 'models/group_data.dart';
|
|
import 'models/image_entry.dart';
|
|
import 'models/media_attachment_uploads/image_types_enum.dart';
|
|
import 'models/timeline_entry.dart';
|
|
import 'models/user_notification.dart';
|
|
import 'serializers/friendica/connection_friendica_extensions.dart';
|
|
import 'serializers/friendica/gallery_data_friendica_extensions.dart';
|
|
import 'serializers/friendica/image_entry_friendica_extensions.dart';
|
|
import 'serializers/mastodon/connection_mastodon_extensions.dart';
|
|
import 'serializers/mastodon/group_data_mastodon_extensions.dart';
|
|
import 'serializers/mastodon/notification_mastodon_extension.dart';
|
|
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
|
import 'services/auth_service.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<UserNotification>, ExecError> getNotifications() async {
|
|
final url =
|
|
'https://$serverName/api/v1/notifications?include_all=true&limit=200';
|
|
final request = Uri.parse(url);
|
|
_logger.finest(() => 'Getting new notifications');
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(notificationsJson) async => notificationsJson
|
|
.map((json) => NotificationMastodonExtension.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) {
|
|
if (error is ExecError) {
|
|
return error;
|
|
}
|
|
|
|
return ExecError(type: ErrorType.localError, message: error.toString());
|
|
});
|
|
}
|
|
|
|
FutureResult<bool, ExecError> clearNotifications() async {
|
|
final url = 'https://$serverName/api/v1/notifications/clear';
|
|
final request = Uri.parse(url);
|
|
_logger.finest(() => 'Clearing unread notification');
|
|
final response = await _postUrl(request, {});
|
|
return response.mapValue((value) => true);
|
|
}
|
|
|
|
FutureResult<bool, ExecError> clearNotification(
|
|
UserNotification notification) async {
|
|
final url =
|
|
'https://$serverName/api/v1/notifications/${notification.id}/dismiss';
|
|
final request = Uri.parse(url);
|
|
_logger.finest(() => 'Clearing unread notification for $notification');
|
|
final response = await _postUrl(request, {});
|
|
return response.mapValue((value) => true);
|
|
}
|
|
|
|
FutureResult<List<GalleryData>, ExecError> getGalleryData() async {
|
|
_logger.finest(() => 'Getting gallery data');
|
|
final url = 'https://$serverName/api/friendica/photoalbums';
|
|
final request = Uri.parse(url);
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(albumsJson) async => albumsJson
|
|
.map((json) => GalleryDataFriendicaExtensions.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<List<ImageEntry>, ExecError> getGalleryImages(
|
|
String galleryName) async {
|
|
_logger.finest(() => 'Getting gallery data');
|
|
final url =
|
|
'https://$serverName/api/friendica/photoalbum?album=$galleryName';
|
|
final request = Uri.parse(url);
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(imagesJson) async => imagesJson
|
|
.map((json) => ImageEntryFriendicaExtension.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<List<GroupData>, ExecError> getGroups() async {
|
|
_logger.finest(() => 'Getting group (Mastodon List) data');
|
|
final url = 'https://$serverName/api/v1/lists';
|
|
final request = Uri.parse(url);
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(listsJson) async => listsJson
|
|
.map((json) => GroupDataMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<bool, ExecError> addConnectionToGroup(
|
|
GroupData group,
|
|
Connection connection,
|
|
) async {
|
|
_logger.finest(() => 'Adding connection to group');
|
|
final url = 'https://$serverName/api/v1/lists/${group.id}/accounts';
|
|
final request = Uri.parse(url);
|
|
final requestData = {
|
|
'account_ids': [connection.id]
|
|
};
|
|
return (await _postUrl(request, requestData)).mapValue((_) => true);
|
|
}
|
|
|
|
FutureResult<bool, ExecError> removeConnectionFromGroup(
|
|
GroupData group,
|
|
Connection connection,
|
|
) async {
|
|
_logger.finest(() => 'Adding connection to group');
|
|
final url = 'https://$serverName/api/v1/lists/${group.id}/accounts';
|
|
final request = Uri.parse(url);
|
|
final requestData = {
|
|
'account_ids': [connection.id]
|
|
};
|
|
return (await _deleteUrl(request, requestData)).mapValue((_) => true);
|
|
}
|
|
|
|
FutureResult<List<Connection>, ExecError> getMyFollowing(
|
|
{int sinceId = -1, int maxId = -1, int limit = 50}) async {
|
|
_logger.finest(() =>
|
|
'Getting following data since $sinceId, maxId $maxId, limit $limit');
|
|
final myId = getIt<AuthService>().currentId;
|
|
final paging =
|
|
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
|
|
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
|
return (await _getApiListRequest(Uri.parse('$baseUrl/following&$paging'))
|
|
.andThenSuccessAsync((listJson) async => listJson
|
|
.map((json) => ConnectionMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.execErrorCast();
|
|
}
|
|
|
|
FutureResult<List<Connection>, ExecError> getMyFollowers(
|
|
{int sinceId = -1, int maxId = -1, int limit = 50}) async {
|
|
_logger.finest(() =>
|
|
'Getting followers data since $sinceId, maxId $maxId, limit $limit');
|
|
final myId = getIt<AuthService>().currentId;
|
|
final paging =
|
|
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
|
|
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
|
return (await _getApiListRequest(Uri.parse('$baseUrl/followers&$paging'))
|
|
.andThenSuccessAsync((listJson) async => listJson
|
|
.map((json) => ConnectionMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.execErrorCast();
|
|
}
|
|
|
|
FutureResult<Connection, ExecError> getConnectionWithStatus(
|
|
Connection connection) async {
|
|
_logger.finest(() => 'Getting group (Mastodon List) data');
|
|
final myId = getIt<AuthService>().currentId;
|
|
final id = int.parse(connection.id);
|
|
final paging = '?min_id=${id - 1}&max_id=${id + 1}';
|
|
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
|
final following =
|
|
await _getApiListRequest(Uri.parse('$baseUrl/following$paging')).fold(
|
|
onSuccess: (followings) => followings.isNotEmpty,
|
|
onError: (error) {
|
|
_logger.severe('Error getting following list: $error');
|
|
return false;
|
|
});
|
|
final follower =
|
|
await _getApiListRequest(Uri.parse('$baseUrl/followers$paging')).fold(
|
|
onSuccess: (followings) => followings.isNotEmpty,
|
|
onError: (error) {
|
|
_logger.severe('Error getting follower list: $error');
|
|
return false;
|
|
});
|
|
|
|
var status = ConnectionStatus.none;
|
|
if (following && follower) {
|
|
status = ConnectionStatus.mutual;
|
|
} else if (following) {
|
|
status = ConnectionStatus.youFollowThem;
|
|
} else if (follower) {
|
|
status = ConnectionStatus.theyFollowYou;
|
|
}
|
|
return Result.ok(connection.copy(status: status));
|
|
}
|
|
|
|
FutureResult<List<GroupData>, ExecError> getMemberGroupsForConnection(
|
|
String connectionId) async {
|
|
_logger.finest(() =>
|
|
'Getting groups (Mastodon Lists) containing connection: $connectionId');
|
|
final url = 'https://$serverName/api/v1/accounts/$connectionId/lists';
|
|
final request = Uri.parse(url);
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(listsJson) async => listsJson
|
|
.map((json) => GroupDataMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.mapError((error) => error as ExecError);
|
|
}
|
|
|
|
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 maxId = 0,
|
|
int limit = 20}) async {
|
|
final String timelinePath = _typeToTimelinePath(type);
|
|
final String timelineQPs = _typeToTimelineQueryParameters(type);
|
|
final baseUrl = 'https://$serverName/api/v1/$timelinePath';
|
|
final pagingData =
|
|
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
|
|
|
|
final url = '$baseUrl?exclude_replies=true&$pagingData&$timelineQPs';
|
|
final request = Uri.parse(url);
|
|
_logger.finest(() =>
|
|
'Getting ${type.toHumanKey()} limit $limit sinceId: $sinceId maxId: $maxId : $url');
|
|
return (await _getApiListRequest(request).andThenSuccessAsync(
|
|
(postsJson) async => postsJson
|
|
.map((json) => TimelineEntryMastodonExtensions.fromJson(json))
|
|
.toList()))
|
|
.execErrorCast();
|
|
}
|
|
|
|
FutureResult<Uint8List, ExecError> getFileBytes(Uri url) async {
|
|
_logger.finest('GET: $url');
|
|
try {
|
|
final response = await http.get(
|
|
url,
|
|
headers: {
|
|
'Authorization': _authHeader,
|
|
},
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(response.bodyBytes);
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<List<TimelineEntry>, ExecError> getPostOrComment(String id,
|
|
{bool fullContext = false}) async {
|
|
return (await runCatchingAsync(() async {
|
|
final baseUrl = 'https://$serverName/api/v1/statuses/$id';
|
|
final url = fullContext ? '$baseUrl/context' : baseUrl;
|
|
final request = Uri.parse('$url?limit=1000');
|
|
_logger.finest(() =>
|
|
'Getting entry for status $id, full context? $fullContext : $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<bool, ExecError> deleteEntryById(String id) async {
|
|
_logger.finest(() => 'Deleting post/comment $id');
|
|
final url = 'https://$serverName/api/v1/statuses/$id';
|
|
final request = Uri.parse(url);
|
|
return (await _deleteUrl(request, {})).mapValue((_) => true);
|
|
}
|
|
|
|
FutureResult<TimelineEntry, ExecError> createNewStatus({
|
|
required String text,
|
|
String spoilerText = '',
|
|
String inReplyToId = '',
|
|
List<String> mediaIds = const [],
|
|
}) async {
|
|
_logger.finest(() =>
|
|
'Creating status ${inReplyToId.isNotEmpty ? "In Reply to: " : ""} $inReplyToId, with media: $mediaIds');
|
|
final url = Uri.parse('https://$serverName/api/v1/statuses');
|
|
final body = {
|
|
'status': text,
|
|
'visibility': 'public',
|
|
if (spoilerText.isNotEmpty) 'spoiler_text': spoilerText,
|
|
if (inReplyToId.isNotEmpty) 'in_reply_to_id': inReplyToId,
|
|
if (mediaIds.isNotEmpty) 'media_ids': mediaIds,
|
|
};
|
|
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> resharePost(String id) async {
|
|
_logger.finest(() => 'Reshare post $id');
|
|
final url = Uri.parse('https://$serverName/api/v1/statuses/$id/reblog');
|
|
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<TimelineEntry, ExecError> unResharePost(String id) async {
|
|
_logger.finest(() => 'Reshare post $id');
|
|
final url = Uri.parse('https://$serverName/api/v1/statuses/$id/unreblog');
|
|
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<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<Connection, ExecError> acceptFollow(
|
|
Connection connection) async {
|
|
final id = connection.id;
|
|
final url =
|
|
Uri.parse('https://$serverName/api/v1/follow_requests/$id/authorize');
|
|
final result =
|
|
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
|
return _updateConnectionFromFollowRequestResult(connection, jsonString);
|
|
});
|
|
return result.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<Connection, ExecError> rejectFollow(
|
|
Connection connection) async {
|
|
final id = connection.id;
|
|
final url =
|
|
Uri.parse('https://$serverName/api/v1/follow_requests/$id/reject');
|
|
final result =
|
|
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
|
return _updateConnectionFromFollowRequestResult(connection, jsonString);
|
|
});
|
|
return result.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<Connection, ExecError> ignoreFollow(
|
|
Connection connection) async {
|
|
final id = connection.id;
|
|
final url =
|
|
Uri.parse('https://$serverName/api/v1/follow_requests/$id/ignore');
|
|
final result =
|
|
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
|
return _updateConnectionFromFollowRequestResult(connection, jsonString);
|
|
});
|
|
return result.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<Connection, ExecError> followConnection(
|
|
Connection connection) async {
|
|
final id = connection.id;
|
|
final url = Uri.parse('https://$serverName/api/v1/accounts/$id/follow');
|
|
final result =
|
|
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
|
return _updateConnectionFromFollowRequestResult(connection, jsonString);
|
|
});
|
|
return result.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<Connection, ExecError> unFollowConnection(
|
|
Connection connection) async {
|
|
final id = connection.id;
|
|
final url = Uri.parse('https://$serverName/api/v1/accounts/$id/unfollow');
|
|
final result =
|
|
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
|
|
return _updateConnectionFromFollowRequestResult(connection, jsonString);
|
|
});
|
|
return result.mapError((error) => error is ExecError
|
|
? error
|
|
: ExecError(type: ErrorType.localError, message: error.toString()));
|
|
}
|
|
|
|
FutureResult<Connection, 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((json) =>
|
|
ConnectionFriendicaExtensions.fromJson(json['friendica_owner'])
|
|
.copy(status: ConnectionStatus.you, network: 'friendica'));
|
|
}
|
|
|
|
FutureResult<ImageEntry, ExecError> uploadFileAsAttachment({
|
|
required List<int> bytes,
|
|
String description = '',
|
|
String album = '',
|
|
String fileName = '',
|
|
}) async {
|
|
final postUri = Uri.parse('https://$serverName/api/friendica/photo/create');
|
|
final request = http.MultipartRequest('POST', postUri);
|
|
request.headers['Authorization'] = _authHeader;
|
|
request.fields['desc'] = description;
|
|
request.fields['album'] = album;
|
|
request.files.add(await http.MultipartFile.fromBytes(
|
|
'media',
|
|
filename: fileName,
|
|
contentType:
|
|
MediaType.parse('image/${ImageTypes.fromExtension(fileName).name}'),
|
|
bytes));
|
|
final response = await request.send();
|
|
final body = utf8.decode(await response.stream.toBytes());
|
|
if (response.statusCode != 200) {
|
|
return Result.error(
|
|
ExecError(
|
|
type: ErrorType.missingEndpoint,
|
|
message: body,
|
|
),
|
|
);
|
|
}
|
|
|
|
final imageDataJson = jsonDecode(body);
|
|
final newImageData = ImageEntryFriendicaExtension.fromJson(imageDataJson);
|
|
|
|
return Result.ok(newImageData);
|
|
}
|
|
|
|
FutureResult<String, ExecError> _getUrl(Uri url) async {
|
|
_logger.finer('GET: $url');
|
|
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(utf8.decode(response.bodyBytes));
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> _postUrl(
|
|
Uri url, Map<String, dynamic> body) async {
|
|
_logger.finer('POST: $url');
|
|
try {
|
|
final response = await http.post(
|
|
url,
|
|
headers: {
|
|
'Cookies': 'XDEBUG_SESSION=PHPSTORM;path=/;',
|
|
'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(utf8.decode(response.bodyBytes));
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> _deleteUrl(
|
|
Uri url, Map<String, dynamic> body) async {
|
|
_logger.finer('DELETE: $url');
|
|
try {
|
|
final response = await http.delete(
|
|
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(utf8.decode(response.bodyBytes));
|
|
} 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 'timelines/home';
|
|
case TimelineType.global:
|
|
return 'timelines/public';
|
|
case TimelineType.local:
|
|
return 'timelines/public';
|
|
case TimelineType.group:
|
|
return 'timelines/list/${type.auxData}';
|
|
case TimelineType.profile:
|
|
return '/accounts/${type.auxData}/statuses';
|
|
case TimelineType.self:
|
|
final myId = getIt<AuthService>().currentId;
|
|
return '/accounts/$myId/statuses';
|
|
}
|
|
}
|
|
|
|
String _typeToTimelineQueryParameters(TimelineIdentifiers type) {
|
|
switch (type.timeline) {
|
|
case TimelineType.home:
|
|
case TimelineType.global:
|
|
case TimelineType.profile:
|
|
case TimelineType.group:
|
|
case TimelineType.self:
|
|
return '';
|
|
case TimelineType.local:
|
|
return 'local=true';
|
|
}
|
|
}
|
|
|
|
String _buildPagingData(
|
|
{required int sinceId, required int maxId, required int limit}) {
|
|
var pagingData = 'limit=$limit';
|
|
if (maxId > 0) {
|
|
pagingData = '$pagingData&max_id=$maxId';
|
|
}
|
|
|
|
if (sinceId > 0) {
|
|
pagingData = '&since_id=$sinceId';
|
|
}
|
|
|
|
return pagingData;
|
|
}
|
|
|
|
Connection _updateConnectionFromFollowRequestResult(
|
|
Connection connection, String jsonString) {
|
|
final json = jsonDecode(jsonString) as Map<String, dynamic>;
|
|
final theyFollowYou = json['followed_by'] ?? 'false';
|
|
final youFollowThem = json['following'] ?? 'false';
|
|
late final ConnectionStatus newStatus;
|
|
if (theyFollowYou && youFollowThem) {
|
|
newStatus = ConnectionStatus.mutual;
|
|
} else if (theyFollowYou) {
|
|
newStatus = ConnectionStatus.theyFollowYou;
|
|
} else if (youFollowThem) {
|
|
newStatus = ConnectionStatus.youFollowThem;
|
|
} else {
|
|
newStatus = ConnectionStatus.none;
|
|
}
|
|
return connection.copy(status: newStatus);
|
|
}
|
|
}
|