Implement Diaspora Contact Importing

This commit is contained in:
Hank Grabowski 2022-03-01 17:21:42 -05:00
parent 521ed49947
commit e051a3ea52
8 changed files with 238 additions and 26 deletions

View file

@ -0,0 +1,41 @@
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
Contact friendicaContactFromDiasporaJson(Map<String, dynamic> json) {
const network = "Diaspora";
final accountId = json['account_id'] ?? '';
final profileUrl = _profileUrlFromAccountId(accountId);
final name = json['person_name'] ?? '';
final id = json['person_guid'] ?? '';
final following = json['following'] ?? false;
final followed = json['followed'] ?? false;
var status = ConnectionStatus.none;
if (following && followed) {
status = ConnectionStatus.mutual;
} else if (following) {
status = ConnectionStatus.youFollowThem;
} else if (followed) {
status = ConnectionStatus.theyFollowYou;
}
return Contact(
status: status,
name: name,
id: id,
profileUrl: profileUrl,
network: network);
}
Uri _profileUrlFromAccountId(String accountId) {
if (accountId.isEmpty) {
return Uri();
}
final accountIdPieces = accountId.split('@');
if (accountIdPieces.length != 2) {
return Uri();
}
final userName = accountIdPieces[0];
final server = accountIdPieces[1];
return Uri.parse('https://$server/u/$userName');
}

View file

@ -1,5 +1,5 @@
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
import 'package:friendica_archive_browser/src/friendica/services/path_mapping_service.dart';
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
import 'package:friendica_archive_browser/src/utils/offsetdatetime_utils.dart';
import 'package:intl/intl.dart';
@ -44,9 +44,9 @@ class FriendicaTimelineEntry {
final List<Uri> links;
final List<FriendicaContact> likes;
final List<Contact> likes;
final List<FriendicaContact> dislikes;
final List<Contact> dislikes;
FriendicaTimelineEntry(
{this.id = '',
@ -63,8 +63,8 @@ class FriendicaTimelineEntry {
this.parentAuthorId = '',
this.externalLink = '',
this.locationData = const LocationData(),
this.likes = const <FriendicaContact>[],
this.dislikes = const <FriendicaContact>[],
this.likes = const <Contact>[],
this.dislikes = const <Contact>[],
List<FriendicaMediaAttachment>? mediaAttachments,
List<Uri>? links})
: mediaAttachments = mediaAttachments ?? <FriendicaMediaAttachment>[],
@ -85,8 +85,8 @@ class FriendicaTimelineEntry {
parentAuthor = 'Random parent author ${randomId()}',
parentAuthorId = 'Random parent author id ${randomId()}',
locationData = LocationData.randomBuilt(),
likes = const <FriendicaContact>[],
dislikes = const <FriendicaContact>[],
likes = const <Contact>[],
dislikes = const <Contact>[],
links = [
Uri.parse('http://localhost/${randomId()}'),
Uri.parse('http://localhost/${randomId()}')
@ -112,8 +112,8 @@ class FriendicaTimelineEntry {
String? parentAuthorId,
LocationData? locationData,
List<FriendicaMediaAttachment>? mediaAttachments,
List<FriendicaContact>? likes,
List<FriendicaContact>? dislikes,
List<Contact>? likes,
List<Contact>? dislikes,
List<Uri>? links}) {
return FriendicaTimelineEntry(
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
@ -207,15 +207,15 @@ class FriendicaTimelineEntry {
.toList();
final likes =
(json['friendica_activities']?['like'] as List<dynamic>? ?? [])
.map((json) => FriendicaContact.fromJson(json))
.map((json) => Contact.fromJson(json))
.toList();
final dislikes =
(json['friendica_activities']?['dislike'] as List<dynamic>? ?? [])
.map((json) => FriendicaContact.fromJson(json))
.map((json) => Contact.fromJson(json))
.toList();
final announce =
(json['friendica_activities']?['announce'] as List<dynamic>? ?? [])
.map((json) => FriendicaContact.fromJson(json))
.map((json) => Contact.fromJson(json))
.toList();
for (final contact in [...likes, ...dislikes, ...announce]) {

View file

@ -1,4 +1,4 @@
class FriendicaContact {
class Contact {
final ConnectionStatus status;
final String name;
@ -9,14 +9,14 @@ class FriendicaContact {
final String network;
FriendicaContact(
Contact(
{required this.status,
required this.name,
required this.id,
required this.profileUrl,
required this.network});
static FriendicaContact fromJson(Map<String, dynamic> json) {
static Contact fromJson(Map<String, dynamic> json) {
final status = (json['following'] ?? '') == 'true'
? ConnectionStatus.youFollowThem
: ConnectionStatus.none;
@ -25,13 +25,18 @@ class FriendicaContact {
final profileUrl = Uri.parse(json['url'] ?? '');
final network = json['network'] ?? 'unkn';
return FriendicaContact(
return Contact(
status: status,
name: name,
id: id,
profileUrl: profileUrl,
network: network);
}
@override
String toString() {
return 'FriendicaContact{status: $status, name: $name, id: $id, profileUrl: $profileUrl, network: $network}';
}
}
enum ConnectionStatus {

View file

@ -0,0 +1,127 @@
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:result_monad/result_monad.dart';
import '../friendica/models/friendica_entry_tree_item.dart';
import '../friendica/models/friendica_timeline_entry.dart';
import '../friendica/services/path_mapping_service.dart';
import '../models/local_image_archive_entry.dart';
import '../utils/exec_error.dart';
import 'friendica_connections.dart';
class DiasporaArchiveService {
final PathMappingService pathMappingService;
final Map<String, ImageEntry> _imagesByRequestUrl = {};
final List<FriendicaEntryTreeItem> _postEntries = [];
final List<FriendicaEntryTreeItem> _orphanedCommentEntries = [];
final List<FriendicaEntryTreeItem> _allComments = [];
final FriendicaConnections connections = FriendicaConnections();
String _ownersName = '';
DiasporaArchiveService({required this.pathMappingService});
String get ownersName => _ownersName;
void clearCaches() {
connections.clearCaches();
_imagesByRequestUrl.clear();
_orphanedCommentEntries.clear();
_allComments.clear();
_postEntries.clear();
}
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getPosts() async {
if (_postEntries.isEmpty && _allComments.isEmpty) {
_loadEntries();
}
return Result.ok(_postEntries);
}
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getAllComments() async {
if (_postEntries.isEmpty && _allComments.isEmpty) {
_loadEntries();
}
return Result.ok(_allComments);
}
FutureResult<List<FriendicaEntryTreeItem>, ExecError>
getOrphanedComments() async {
if (_postEntries.isEmpty && _allComments.isEmpty) {
_loadEntries();
}
return Result.ok(_orphanedCommentEntries);
}
Result<ImageEntry, ExecError> getImageByUrl(String url) {
if (_imagesByRequestUrl.isEmpty) {
_loadImages();
}
final result = _imagesByRequestUrl[url];
return result == null
? Result.error(ExecError(errorMessage: '$url not found'))
: Result.ok(result);
}
String get _baseArchiveFolder => pathMappingService.rootFolder;
void _loadEntries() {
final entriesJsonPath = p.join(_baseArchiveFolder, 'postsAndComments.json');
final jsonFile = File(entriesJsonPath);
if (jsonFile.existsSync()) {
final json = jsonDecode(jsonFile.readAsStringSync()) as List<dynamic>;
final entries =
json.map((j) => FriendicaTimelineEntry.fromJson(j, connections));
final topLevelEntries =
entries.where((element) => element.parentId.isEmpty);
final commentEntries =
entries.where((element) => element.parentId.isNotEmpty).toList();
final entryTrees = <String, FriendicaEntryTreeItem>{};
final postTreeEntries = <FriendicaEntryTreeItem>[];
for (final entry in topLevelEntries) {
final treeEntry = FriendicaEntryTreeItem(entry, false);
entryTrees[entry.id] = treeEntry;
postTreeEntries.add(treeEntry);
}
final commentTreeEntries = <FriendicaEntryTreeItem>[];
commentEntries.sort(
(c1, c2) => c1.creationTimestamp.compareTo(c2.creationTimestamp));
for (final entry in commentEntries) {
final parent = entryTrees[entry.parentId];
final treeEntry = FriendicaEntryTreeItem(entry, parent == null);
parent?.addChild(treeEntry);
entryTrees[entry.id] = treeEntry;
commentTreeEntries.add(treeEntry);
}
_postEntries.clear();
_postEntries.addAll(postTreeEntries);
_allComments.clear();
_allComments.addAll(commentTreeEntries);
_orphanedCommentEntries.clear();
_orphanedCommentEntries
.addAll(entryTrees.values.where((element) => element.isOrphaned));
}
}
void _loadImages() {
final imageJsonPath = p.join(_baseArchiveFolder, 'images.json');
final jsonFile = File(imageJsonPath);
if (jsonFile.existsSync()) {
final json = jsonDecode(jsonFile.readAsStringSync()) as List<dynamic>;
final imageEntries = json.map((j) => ImageEntry.fromJson(j));
for (final entry in imageEntries) {
_imagesByRequestUrl[entry.url] = entry;
}
}
}
}

View file

@ -0,0 +1,25 @@
import 'dart:convert';
import 'dart:io';
import 'package:friendica_archive_browser/src/diaspora/serializers/diaspora_contact_serializer.dart';
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
class DiasporaProfileJsonReader {
final String jsonFilePath;
DiasporaProfileJsonReader(this.jsonFilePath);
List<Contact> readContacts() {
final jsonFile = File(jsonFilePath);
if (jsonFile.existsSync()) {
final json =
jsonDecode(jsonFile.readAsStringSync()) as Map<String, dynamic>;
final contactsJson = json['user']?['contacts'] as List<dynamic>;
final contacts =
contactsJson.map((j) => friendicaContactFromDiasporaJson(j)).toList();
return contacts;
}
return [];
}
}

View file

@ -1,16 +1,16 @@
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
import 'package:result_monad/result_monad.dart';
class FriendicaConnections {
final _connectionsById = <String, FriendicaContact>{};
final _connectionsByName = <String, FriendicaContact>{};
final _connectionsById = <String, Contact>{};
final _connectionsByName = <String, Contact>{};
void clearCaches() {
_connectionsById.clear();
_connectionsByName.clear();
}
bool addConnection(FriendicaContact contact) {
bool addConnection(Contact contact) {
if (_connectionsById.containsKey(contact.id)) {
return false;
}
@ -20,13 +20,13 @@ class FriendicaConnections {
return true;
}
Result<FriendicaContact, String> getById(String id) {
Result<Contact, String> getById(String id) {
final result = _connectionsById[id];
return result != null ? Result.ok(result) : Result.error('$id not found');
}
Result<FriendicaContact, String> getByName(String name) {
Result<Contact, String> getByName(String name) {
final result = _connectionsByName[name];
return result != null ? Result.ok(result) : Result.error('$name not found');

View file

@ -1,5 +1,5 @@
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
class TopInteractorsGenerator {
@ -66,7 +66,7 @@ class TopInteractorsGenerator {
final contact = contacts.getById(id).fold(
onSuccess: (contact) => contact,
onError: (error) => FriendicaContact(
onError: (error) => Contact(
status: ConnectionStatus.none,
name: '',
id: id,
@ -77,7 +77,7 @@ class TopInteractorsGenerator {
}
class InteractorItem {
final FriendicaContact contact;
final Contact contact;
final int resharedOrCommentedOn;
final int likeCount;
final int dislikeCount;
@ -94,7 +94,7 @@ class InteractorItem {
}
InteractorItem copy(
{FriendicaContact? contact,
{Contact? contact,
int? resharedOrCommentedOn,
int? likeCount,
int? dislikeCount}) {

View file

@ -0,0 +1,14 @@
// ignore_for_file: avoid_print
import 'package:flutter_test/flutter_test.dart';
import 'package:friendica_archive_browser/src/services/diaspora_profile_json_reader.dart';
void main() {
test('Diaspora Connections Test', () {
final reader = DiasporaProfileJsonReader(
'/Users/hankdev/Desktop/diaspora_pretty.json');
final contacts = reader.readContacts();
print(contacts.length);
print(contacts.first);
});
}