diff --git a/lib/src/client.dart b/lib/src/client.dart index 0de7da3..d41fb84 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -449,7 +449,7 @@ class Client { /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { - final uploadResp = await api.upload(file.bytes, file.path); + final uploadResp = await api.upload(file.bytes, file.name); await api.setAvatarUrl(userID, Uri.parse(uploadResp)); return; } diff --git a/lib/src/event.dart b/lib/src/event.dart index 4beeede..c82e7d7 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -424,7 +424,7 @@ class Event extends MatrixEvent { encryptedFile.sha256 = fileMap['hashes']['sha256']; uint8list = await decryptFile(encryptedFile); } - return MatrixFile(bytes: uint8list, path: '/$body'); + return MatrixFile(bytes: uint8list, name: body); } /// Returns a localized String representation of this event. For a diff --git a/lib/src/room.dart b/lib/src/room.dart index 3cfe836..565b9e4 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -25,9 +25,7 @@ import 'package:famedlysdk/src/event.dart'; import 'package:famedlysdk/src/utils/event_update.dart'; import 'package:famedlysdk/src/utils/room_update.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart'; -import 'package:image/image.dart'; import 'package:matrix_file_e2ee/matrix_file_e2ee.dart'; -import 'package:mime/mime.dart'; import 'package:html_unescape/html_unescape.dart'; import './user.dart'; @@ -504,79 +502,53 @@ class Room { return sendEvent(event, txid: txid, inReplyTo: inReplyTo); } - /// Sends a [file] to this room after uploading it. The [msgType] is optional - /// and will be detected by the mimetype of the file. Returns the mxc uri of + /// Sends a [file] to this room after uploading it. Returns the mxc uri of /// the uploaded file. If [waitUntilSent] is true, the future will wait until /// the message event has received the server. Otherwise the future will only /// wait until the file has been uploaded. Future sendFileEvent( MatrixFile file, { - String msgType, String txid, Event inReplyTo, - Map info, bool waitUntilSent = false, - MatrixFile thumbnail, + MatrixImageFile thumbnail, }) async { - Image fileImage; - Image thumbnailImage; - EncryptedFile encryptedThumbnail; - String thumbnailUploadResp; - - var fileName = file.path.split('/').last; - final mimeType = lookupMimeType(file.path, headerBytes: file.bytes) ?? ''; - if (msgType == null) { - final metaType = (mimeType).split('/')[0]; - switch (metaType) { - case 'image': - case 'audio': - case 'video': - msgType = 'm.$metaType'; - break; - default: - msgType = 'm.file'; - break; - } - } - - if (msgType == 'm.image') { - fileImage = decodeImage(file.bytes.toList()); - if (thumbnail != null) { - thumbnailImage = decodeImage(thumbnail.bytes.toList()); - } - } - - final sendEncrypted = encrypted && client.fileEncryptionEnabled; + MatrixFile uploadFile = file; // ignore: omit_local_variable_types + MatrixFile uploadThumbnail = thumbnail; // ignore: omit_local_variable_types EncryptedFile encryptedFile; - if (sendEncrypted) { + EncryptedFile encryptedThumbnail; + if (encrypted && client.fileEncryptionEnabled) { encryptedFile = await file.encrypt(); + uploadFile = encryptedFile.toMatrixFile(); + if (thumbnail != null) { encryptedThumbnail = await thumbnail.encrypt(); + uploadThumbnail = encryptedThumbnail.toMatrixFile(); } } final uploadResp = await client.api.upload( - file.bytes, - file.path, - contentType: sendEncrypted ? 'application/octet-stream' : null, + uploadFile.bytes, + uploadFile.name, + contentType: uploadFile.mimeType, ); - if (thumbnail != null) { - thumbnailUploadResp = await client.api.upload( - thumbnail.bytes, - thumbnail.path, - contentType: sendEncrypted ? 'application/octet-stream' : null, - ); - } + final thumbnailUploadResp = uploadThumbnail != null + ? await client.api.upload( + uploadThumbnail.bytes, + uploadThumbnail.name, + contentType: uploadThumbnail.mimeType, + ) + : null; // Send event var content = { - 'msgtype': msgType, - 'body': fileName, - 'filename': fileName, - if (!sendEncrypted) 'url': uploadResp, - if (sendEncrypted) + 'msgtype': file.msgType, + 'body': file.name, + 'filename': file.name, + if (encryptedFile == null) 'url': uploadResp, + if (encryptedFile != null) 'file': { 'url': uploadResp, - 'mimetype': mimeType, + 'mimetype': file.mimeType, 'v': 'v2', 'key': { 'alg': 'A256CTR', @@ -588,37 +560,27 @@ class Room { 'iv': encryptedFile.iv, 'hashes': {'sha256': encryptedFile.sha256} }, - 'info': info ?? - { - 'mimetype': mimeType, - 'size': file.size, - if (fileImage != null) 'h': fileImage.height, - if (fileImage != null) 'w': fileImage.width, - if (thumbnailUploadResp != null && !sendEncrypted) - 'thumbnail_url': thumbnailUploadResp, - if (thumbnailUploadResp != null && sendEncrypted) - 'thumbnail_file': { - 'url': thumbnailUploadResp, - 'mimetype': mimeType, - 'v': 'v2', - 'key': { - 'alg': 'A256CTR', - 'ext': true, - 'k': encryptedThumbnail.k, - 'key_ops': ['encrypt', 'decrypt'], - 'kty': 'oct' - }, - 'iv': encryptedThumbnail.iv, - 'hashes': {'sha256': encryptedThumbnail.sha256} - }, - if (thumbnailImage != null) - 'thumbnail_info': { - 'h': thumbnailImage.height, - 'mimetype': mimeType, - 'size': thumbnail.size, - 'w': thumbnailImage.width, - } - } + 'info': { + ...file.info, + if (thumbnail != null && encryptedThumbnail == null) + 'thumbnail_url': thumbnailUploadResp, + if (thumbnail != null && encryptedThumbnail != null) + 'thumbnail_file': { + 'url': thumbnailUploadResp, + 'mimetype': thumbnail.mimeType, + 'v': 'v2', + 'key': { + 'alg': 'A256CTR', + 'ext': true, + 'k': encryptedThumbnail.k, + 'key_ops': ['encrypt', 'decrypt'], + 'kty': 'oct' + }, + 'iv': encryptedThumbnail.iv, + 'hashes': {'sha256': encryptedThumbnail.sha256} + }, + if (thumbnail != null) 'thumbnail_info': thumbnail.info, + } }; final sendResponse = sendEvent( content, @@ -631,79 +593,6 @@ class Room { return uploadResp; } - /// Sends an audio file to this room and returns the mxc uri. - Future sendAudioEvent(MatrixFile file, - {String txid, Event inReplyTo}) async { - return await sendFileEvent(file, - msgType: 'm.audio', txid: txid, inReplyTo: inReplyTo); - } - - /// Sends an image to this room and returns the mxc uri. - Future sendImageEvent(MatrixFile file, - {String txid, int width, int height, Event inReplyTo}) async { - return await sendFileEvent(file, - msgType: 'm.image', - txid: txid, - inReplyTo: inReplyTo, - info: { - 'size': file.size, - 'mimetype': lookupMimeType(file.path, headerBytes: file.bytes), - 'w': width, - 'h': height, - }); - } - - /// Sends an video to this room and returns the mxc uri. - Future sendVideoEvent(MatrixFile file, - {String txid, - int videoWidth, - int videoHeight, - int duration, - MatrixFile thumbnail, - int thumbnailWidth, - int thumbnailHeight, - Event inReplyTo}) async { - var info = { - 'size': file.size, - 'mimetype': lookupMimeType(file.path, headerBytes: file.bytes), - }; - if (videoWidth != null) { - info['w'] = videoWidth; - } - if (thumbnailHeight != null) { - info['h'] = thumbnailHeight; - } - if (duration != null) { - info['duration'] = duration; - } - if (thumbnail != null && !(encrypted && client.encryptionEnabled)) { - final thumbnailUploadResp = await client.api.upload( - thumbnail.bytes, - thumbnail.path, - ); - info['thumbnail_url'] = thumbnailUploadResp; - info['thumbnail_info'] = { - 'size': thumbnail.size, - 'mimetype': - lookupMimeType(thumbnail.path, headerBytes: thumbnail.bytes), - }; - if (thumbnailWidth != null) { - info['thumbnail_info']['w'] = thumbnailWidth; - } - if (thumbnailHeight != null) { - info['thumbnail_info']['h'] = thumbnailHeight; - } - } - - return await sendFileEvent( - file, - msgType: 'm.video', - txid: txid, - inReplyTo: inReplyTo, - info: info, - ); - } - /// Sends an event to this room with this json as a content. Returns the /// event ID generated from the server. Future sendEvent(Map content, @@ -1217,7 +1106,7 @@ class Room { /// Uploads a new user avatar for this room. Returns the event ID of the new /// m.room.avatar event. Future setAvatar(MatrixFile file) async { - final uploadResp = await client.api.upload(file.bytes, file.path); + final uploadResp = await client.api.upload(file.bytes, file.name); return await client.api.sendState( id, EventTypes.RoomAvatar, diff --git a/lib/src/utils/matrix_file.dart b/lib/src/utils/matrix_file.dart index b16a838..1f72c95 100644 --- a/lib/src/utils/matrix_file.dart +++ b/lib/src/utils/matrix_file.dart @@ -2,20 +2,100 @@ import 'dart:typed_data'; import 'package:matrix_file_e2ee/matrix_file_e2ee.dart'; +import 'package:mime/mime.dart'; class MatrixFile { Uint8List bytes; - String path; + String name; + String mimeType; - /// Encrypts this file, changes the [bytes] and returns the + /// Encrypts this file and returns the /// encryption information as an [EncryptedFile]. Future encrypt() async { - var encryptFile2 = encryptFile(bytes); - final encryptedFile = await encryptFile2; - bytes = encryptedFile.data; - return encryptedFile; + return await encryptFile(bytes); + } + + MatrixFile({this.bytes, this.name, this.mimeType}) { + mimeType ??= lookupMimeType(name, headerBytes: bytes); + name = name.split('/').last.toLowerCase(); } - MatrixFile({this.bytes, String path}) : path = path.toLowerCase(); int get size => bytes.length; + + String get msgType => 'm.file'; + + Map get info => ({ + 'mimetype': mimeType, + 'size': size, + }); +} + +class MatrixImageFile extends MatrixFile { + int width; + int height; + String blurhash; + + MatrixImageFile( + {Uint8List bytes, + String name, + String mimeType, + this.width, + this.height, + this.blurhash}) + : super(bytes: bytes, name: name, mimeType: mimeType); + @override + String get msgType => 'm.image'; + @override + Map get info => ({ + ...super.info, + if (width != null) 'w': width, + if (height != null) 'h': height, + if (blurhash != null) 'xyz.amorgan.blurhash': blurhash, + }); +} + +class MatrixVideoFile extends MatrixFile { + int width; + int height; + int duration; + + MatrixVideoFile( + {Uint8List bytes, + String name, + String mimeType, + this.width, + this.height, + this.duration}) + : super(bytes: bytes, name: name, mimeType: mimeType); + @override + String get msgType => 'm.video'; + @override + Map get info => ({ + ...super.info, + if (width != null) 'w': width, + if (height != null) 'h': height, + if (duration != null) 'duration': duration, + }); +} + +class MatrixAudioFile extends MatrixFile { + int duration; + + MatrixAudioFile( + {Uint8List bytes, String name, String mimeType, this.duration}) + : super(bytes: bytes, name: name, mimeType: mimeType); + @override + String get msgType => 'm.audio'; + @override + Map get info => ({ + ...super.info, + if (duration != null) 'duration': duration, + }); +} + +extension ToMatrixFile on EncryptedFile { + MatrixFile toMatrixFile() { + return MatrixFile( + bytes: data, name: 'crypt', mimeType: 'application/octet-stream'); + } } diff --git a/pubspec.lock b/pubspec.lock index 0584b3f..e532c62 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -274,13 +274,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" - image: - dependency: "direct main" - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.12" io: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9050d1d..c75844a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,6 @@ dependencies: http: ^0.12.1 mime: ^0.9.6 canonical_json: ^1.0.0 - image: ^2.1.4 markdown: ^2.1.3 html_unescape: ^1.0.1+3 moor: ^3.0.2 diff --git a/test/client_test.dart b/test/client_test.dart index b0c8c91..98caadc 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -343,8 +343,7 @@ void main() { }); test('setAvatar', () async { - final testFile = - MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg'); + final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg'); await matrix.setAvatar(testFile); }); diff --git a/test/matrix_file_test.dart b/test/matrix_file_test.dart index 4be7556..7722851 100644 --- a/test/matrix_file_test.dart +++ b/test/matrix_file_test.dart @@ -28,7 +28,7 @@ void main() { test('Decrypt', () async { final text = 'hello world'; final file = MatrixFile( - path: '/path/to/file.txt', + name: 'file.txt', bytes: Uint8List.fromList(text.codeUnits), ); var olmEnabled = true; diff --git a/test/room_test.dart b/test/room_test.dart index a4ac925..590f0ec 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -333,8 +333,7 @@ void main() { }); test('setAvatar', () async { - final testFile = - MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg'); + final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg'); final dynamic resp = await room.setAvatar(testFile); expect(resp, 'YUwRidLecu:example.com'); }); @@ -361,10 +360,8 @@ void main() { });*/ test('sendFileEvent', () async { - final testFile = - MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg'); - final dynamic resp = await room.sendFileEvent(testFile, - msgType: 'm.file', txid: 'testtxid'); + final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg'); + final dynamic resp = await room.sendFileEvent(testFile, txid: 'testtxid'); expect(resp, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); });