import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:Envelope/database/repositories/friends_repository.dart'; import 'package:mime/mime.dart'; import 'package:pointycastle/export.dart'; import 'package:uuid/uuid.dart'; import '/database/repositories/conversation_users_repository.dart'; import '/database/models/messages.dart'; import '/database/models/text_messages.dart'; import '/database/models/conversation_users.dart'; import '/database/models/friends.dart'; import '/database/models/my_profile.dart'; import '/utils/encryption/aes_helper.dart'; import '/utils/encryption/crypto_utils.dart'; import '/utils/storage/database.dart'; import '/utils/storage/write_file.dart'; enum ConversationStatus { complete, pending, error, } class Conversation { String id; String userId; String symmetricKey; bool admin; String name; bool twoUser; ConversationStatus status; bool isRead; String messageExpiryDefault = 'no_expiry'; bool adminAddMembers = true; bool adminEditInfo = true; bool adminSendMessages = false; File? icon; Conversation({ required this.id, required this.userId, required this.symmetricKey, required this.admin, required this.name, required this.twoUser, required this.status, required this.isRead, required this.messageExpiryDefault, required this.adminAddMembers, required this.adminEditInfo, required this.adminSendMessages, this.icon, }); factory Conversation.fromJson(Map json, RSAPrivateKey privKey) { var symmetricKeyDecrypted = CryptoUtils.rsaDecrypt( base64.decode(json['symmetric_key']), privKey, ); var id = AesHelper.aesDecrypt( symmetricKeyDecrypted, base64.decode(json['conversation_detail_id']), ); var admin = AesHelper.aesDecrypt( symmetricKeyDecrypted, base64.decode(json['admin']), ); return Conversation( id: id, userId: json['user_id'], symmetricKey: base64.encode(symmetricKeyDecrypted), admin: admin == 'true', name: 'Unknown', twoUser: false, status: ConversationStatus.complete, isRead: true, messageExpiryDefault: 'no_expiry', adminAddMembers: true, adminEditInfo: true, adminSendMessages: false, ); } Future> payloadJson({ bool includeUsers = true }) async { MyProfile profile = await MyProfile.getProfile(); var symKey = base64.decode(symmetricKey); if (!includeUsers) { return { 'id': id, 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), 'users': await ConversationUsersRepository.getEncryptedConversationUsers(this, symKey), }; } List users = await ConversationUsersRepository.getConversationUsers(this); List userConversations = []; for (ConversationUser user in users) { RSAPublicKey pubKey = profile.publicKey!; String newId = id; if (profile.id != user.userId) { Friend friend = await FriendsRepository.getFriendByFriendId(user.userId); pubKey = friend.publicKey; newId = (const Uuid()).v4(); } userConversations.add({ 'id': newId, 'user_id': user.userId, 'conversation_detail_id': AesHelper.aesEncrypt(symKey, Uint8List.fromList(id.codeUnits)), 'admin': AesHelper.aesEncrypt(symKey, Uint8List.fromList((user.admin ? 'true' : 'false').codeUnits)), 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(symKey, pubKey)), }); } Map returnData = { 'id': id, 'name': AesHelper.aesEncrypt(symKey, Uint8List.fromList(name.codeUnits)), 'users': await ConversationUsersRepository.getEncryptedConversationUsers(this, symKey), 'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)), 'message_expiry': messageExpiryDefault, 'admin_add_members': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminAddMembers ? 'true' : 'false').codeUnits)), 'admin_edit_info': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminEditInfo ? 'true' : 'false').codeUnits)), 'admin_send_messages': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminSendMessages ? 'true' : 'false').codeUnits)), 'user_conversations': userConversations, }; if (icon != null) { returnData['attachment'] = { 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(icon!.readAsBytesSync())), 'mimetype': lookupMimeType(icon!.path), 'extension': getExtension(icon!.path), }; } return returnData; } Map payloadImageJson() { if (icon == null) { return {}; } return { 'data': AesHelper.aesEncrypt(symmetricKey, Uint8List.fromList(icon!.readAsBytesSync())), 'mimetype': lookupMimeType(icon!.path), 'extension': getExtension(icon!.path), }; } Map toMap() { return { 'id': id, 'user_id': userId, 'symmetric_key': symmetricKey, 'admin': admin ? 1 : 0, 'name': name, 'two_user': twoUser ? 1 : 0, 'status': status.index, 'is_read': isRead ? 1 : 0, 'file': icon != null ? icon!.path : null, 'message_expiry': messageExpiryDefault, 'admin_add_members': adminAddMembers ? 1 : 0, 'admin_edit_info': adminEditInfo ? 1 : 0, 'admin_send_messages': adminSendMessages ? 1 : 0, }; } @override String toString() { return ''' id: $id userId: $userId name: $name admin: $admin'''; } Future getRecentMessage() async { final db = await getDatabaseConnection(); final List> maps = await db.rawQuery( ''' SELECT * FROM messages WHERE association_key IN ( SELECT association_key FROM conversation_users WHERE conversation_id = ? ) ORDER BY created_at DESC LIMIT 1; ''', [ id ], ); if (maps.isEmpty) { return null; } return TextMessage( id: maps[0]['id'], symmetricKey: maps[0]['symmetric_key'], userSymmetricKey: maps[0]['user_symmetric_key'], text: maps[0]['data'] ?? 'Image', senderId: maps[0]['sender_id'], senderUsername: maps[0]['sender_username'], associationKey: maps[0]['association_key'], createdAt: maps[0]['created_at'], failedToSend: maps[0]['failed_to_send'] == 1, ); } }