2019-06-09 11:57:33 +00:00
/ *
* Copyright ( c ) 2019 Zender & Kurtz GbR .
*
* Authors:
* Christian Pauly < krille @ famedly . com >
* Marcel Radzio < mtrnord @ famedly . com >
*
* This file is part of famedlysdk .
*
* famedlysdk is free software: you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* famedlysdk is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
2019-06-21 07:46:53 +00:00
* along with famedlysdk . If not , see < http: //www.gnu.org/licenses/>.
2019-06-09 11:57:33 +00:00
* /
2020-01-02 14:09:49 +00:00
import ' dart:convert ' ;
2020-03-16 10:38:03 +00:00
import ' dart:typed_data ' ;
2020-01-02 14:09:49 +00:00
import ' package:famedlysdk/famedlysdk.dart ' ;
Update lib/src/client.dart, lib/src/user.dart, lib/src/timeline.dart, lib/src/room.dart, lib/src/presence.dart, lib/src/event.dart, lib/src/utils/profile.dart, lib/src/utils/receipt.dart, test/client_test.dart, test/event_test.dart, test/presence_test.dart, test/room_test.dart, test/timeline_test.dart, test/user_test.dart files
2020-01-04 17:56:17 +00:00
import ' package:famedlysdk/src/utils/receipt.dart ' ;
2020-03-16 10:38:03 +00:00
import ' package:http/http.dart ' as http ;
import ' package:matrix_file_e2ee/matrix_file_e2ee.dart ' ;
Update lib/src/client.dart, lib/src/user.dart, lib/src/timeline.dart, lib/src/room.dart, lib/src/presence.dart, lib/src/event.dart, lib/src/utils/profile.dart, lib/src/utils/receipt.dart, test/client_test.dart, test/event_test.dart, test/presence_test.dart, test/room_test.dart, test/timeline_test.dart, test/user_test.dart files
2020-01-04 17:56:17 +00:00
import ' ./room.dart ' ;
2019-07-11 20:17:40 +00:00
2020-01-04 09:31:27 +00:00
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
2020-01-02 14:09:49 +00:00
class Event {
/// The Matrix ID for this event in the format '$localpart:server.abc'. Please not
/// that account data, presence and other events may not have an eventId.
final String eventId ;
/// The json payload of the content. The content highly depends on the type.
Map < String , dynamic > content ;
/// The type String of this event. For example 'm.room.message'.
final String typeKey ;
/// The ID of the room this event belongs to.
final String roomId ;
/// The user who has sent this event if it is not a global account data event.
final String senderId ;
2020-03-30 09:08:38 +00:00
User get sender = > room . getUserByMXIDSync ( senderId ? ? ' @unknown ' ) ;
2020-01-02 14:09:49 +00:00
/// The time this event has received at the server. May be null for events like
/// account data.
final DateTime time ;
/// Optional additional content for this event.
Map < String , dynamic > unsigned ;
/// The room this event belongs to. May be null.
final Room room ;
/// Optional. The previous content for this state.
/// This will be present only for state events appearing in the timeline.
/// If this is not a state event, or there is no previous content, this key will be null.
Map < String , dynamic > prevContent ;
/// Optional. This key will only be present for state events. A unique key which defines
/// the overwriting semantics for this piece of room state.
final String stateKey ;
2019-06-11 09:23:57 +00:00
/// The status of this event.
/// -1=ERROR
/// 0=SENDING
/// 1=SENT
2020-01-02 14:09:49 +00:00
/// 2=TIMELINE
/// 3=ROOM_STATE
2019-06-11 09:23:57 +00:00
int status ;
2019-08-29 07:12:55 +00:00
static const int defaultStatus = 2 ;
2020-01-02 14:09:49 +00:00
static const Map < String , int > STATUS_TYPE = {
2020-03-30 09:08:38 +00:00
' ERROR ' : - 1 ,
' SENDING ' : 0 ,
' SENT ' : 1 ,
' TIMELINE ' : 2 ,
' ROOM_STATE ' : 3 ,
2020-01-02 14:09:49 +00:00
} ;
/// Optional. The event that redacted this event, if any. Otherwise null.
Event get redactedBecause = >
2020-03-30 09:08:38 +00:00
unsigned ! = null & & unsigned . containsKey ( ' redacted_because ' )
? Event . fromJson ( unsigned [ ' redacted_because ' ] , room )
2020-01-02 14:09:49 +00:00
: null ;
bool get redacted = > redactedBecause ! = null ;
User get stateKeyUser = > room . getUserByMXIDSync ( stateKey ) ;
2019-08-29 07:12:55 +00:00
2019-06-21 07:46:53 +00:00
Event (
2019-08-29 07:12:55 +00:00
{ this . status = defaultStatus ,
2020-01-02 14:09:49 +00:00
this . content ,
this . typeKey ,
this . eventId ,
this . roomId ,
this . senderId ,
this . time ,
this . unsigned ,
this . prevContent ,
this . stateKey ,
this . room } ) ;
static Map < String , dynamic > getMapFromPayload ( dynamic payload ) {
2020-01-02 14:33:26 +00:00
if ( payload is String ) {
2020-01-02 14:09:49 +00:00
try {
return json . decode ( payload ) ;
} catch ( e ) {
return { } ;
}
2020-01-02 14:33:26 +00:00
}
2020-01-02 14:09:49 +00:00
if ( payload is Map < String , dynamic > ) return payload ;
return { } ;
}
2019-08-07 07:52:36 +00:00
/// Get a State event from a table row or from the event stream.
2019-08-07 08:46:59 +00:00
factory Event . fromJson ( Map < String , dynamic > jsonPayload , Room room ) {
2020-03-30 09:08:38 +00:00
final content = Event . getMapFromPayload ( jsonPayload [ ' content ' ] ) ;
final unsigned = Event . getMapFromPayload ( jsonPayload [ ' unsigned ' ] ) ;
final prevContent = Event . getMapFromPayload ( jsonPayload [ ' prev_content ' ] ) ;
2019-08-07 07:52:36 +00:00
return Event (
2020-01-02 14:09:49 +00:00
status: jsonPayload [ ' status ' ] ? ? defaultStatus ,
stateKey: jsonPayload [ ' state_key ' ] ,
prevContent: prevContent ,
content: content ,
typeKey: jsonPayload [ ' type ' ] ,
eventId: jsonPayload [ ' event_id ' ] ,
roomId: jsonPayload [ ' room_id ' ] ,
senderId: jsonPayload [ ' sender ' ] ,
time: jsonPayload . containsKey ( ' origin_server_ts ' )
? DateTime . fromMillisecondsSinceEpoch ( jsonPayload [ ' origin_server_ts ' ] )
: DateTime . now ( ) ,
unsigned: unsigned ,
room: room ,
) ;
}
Map < String , dynamic > toJson ( ) {
2020-03-30 09:08:38 +00:00
final data = < String , dynamic > { } ;
if ( stateKey ! = null ) data [ ' state_key ' ] = stateKey ;
if ( prevContent ! = null & & prevContent . isNotEmpty ) {
data [ ' prev_content ' ] = prevContent ;
2020-01-02 14:33:26 +00:00
}
2020-03-30 09:08:38 +00:00
data [ ' content ' ] = content ;
data [ ' type ' ] = typeKey ;
data [ ' event_id ' ] = eventId ;
data [ ' room_id ' ] = roomId ;
data [ ' sender ' ] = senderId ;
data [ ' origin_server_ts ' ] = time . millisecondsSinceEpoch ;
if ( unsigned ! = null & & unsigned . isNotEmpty ) {
data [ ' unsigned ' ] = unsigned ;
2020-01-02 14:33:26 +00:00
}
2020-01-02 14:09:49 +00:00
return data ;
}
/// The unique key of this event. For events with a [stateKey], it will be the
/// stateKey. Otherwise it will be the [type] as a string.
@ deprecated
String get key = > stateKey = = null | | stateKey . isEmpty ? typeKey : stateKey ;
User get asUser = > User . fromState (
stateKey: stateKey ,
prevContent: prevContent ,
content: content ,
typeKey: typeKey ,
eventId: eventId ,
roomId: roomId ,
senderId: senderId ,
time: time ,
unsigned: unsigned ,
room: room ) ;
/// Get the real type.
EventTypes get type {
switch ( typeKey ) {
2020-03-30 09:08:38 +00:00
case ' m.room.avatar ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomAvatar ;
2020-03-30 09:08:38 +00:00
case ' m.room.name ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomName ;
2020-03-30 09:08:38 +00:00
case ' m.room.topic ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomTopic ;
2020-03-30 09:08:38 +00:00
case ' m.room.aliases ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomAliases ;
2020-03-30 09:08:38 +00:00
case ' m.room.canonical_alias ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomCanonicalAlias ;
2020-03-30 09:08:38 +00:00
case ' m.room.create ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomCreate ;
2020-03-30 09:08:38 +00:00
case ' m.room.redaction ' :
2020-01-02 14:09:49 +00:00
return EventTypes . Redaction ;
2020-03-30 09:08:38 +00:00
case ' m.room.join_rules ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomJoinRules ;
2020-03-30 09:08:38 +00:00
case ' m.room.member ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomMember ;
2020-03-30 09:08:38 +00:00
case ' m.room.power_levels ' :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomPowerLevels ;
2020-03-30 09:08:38 +00:00
case ' m.room.guest_access ' :
2020-01-02 14:09:49 +00:00
return EventTypes . GuestAccess ;
2020-03-30 09:08:38 +00:00
case ' m.room.history_visibility ' :
2020-01-02 14:09:49 +00:00
return EventTypes . HistoryVisibility ;
2020-03-30 09:08:38 +00:00
case ' m.sticker ' :
2020-01-04 09:31:27 +00:00
return EventTypes . Sticker ;
2020-03-30 09:08:38 +00:00
case ' m.room.message ' :
2020-01-04 09:31:27 +00:00
return EventTypes . Message ;
2020-03-30 09:08:38 +00:00
case ' m.room.encrypted ' :
2020-01-04 18:36:17 +00:00
return EventTypes . Encrypted ;
2020-03-30 09:08:38 +00:00
case ' m.room.encryption ' :
2020-01-04 18:36:17 +00:00
return EventTypes . Encryption ;
2020-03-30 09:08:38 +00:00
case ' m.call.invite ' :
2020-01-04 18:36:17 +00:00
return EventTypes . CallInvite ;
2020-03-30 09:08:38 +00:00
case ' m.call.answer ' :
2020-01-04 18:36:17 +00:00
return EventTypes . CallAnswer ;
2020-03-30 09:08:38 +00:00
case ' m.call.candidates ' :
2020-01-04 18:36:17 +00:00
return EventTypes . CallCandidates ;
2020-03-30 09:08:38 +00:00
case ' m.call.hangup ' :
2020-01-04 18:36:17 +00:00
return EventTypes . CallHangup ;
2020-01-02 14:09:49 +00:00
}
return EventTypes . Unknown ;
}
2020-01-04 09:31:27 +00:00
///
MessageTypes get messageType {
2020-03-30 09:08:38 +00:00
switch ( content [ ' msgtype ' ] ? ? ' m.text ' ) {
case ' m.text ' :
if ( content . containsKey ( ' m.relates_to ' ) ) {
2020-01-04 09:31:27 +00:00
return MessageTypes . Reply ;
}
return MessageTypes . Text ;
2020-03-30 09:08:38 +00:00
case ' m.notice ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Notice ;
2020-03-30 09:08:38 +00:00
case ' m.emote ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Emote ;
2020-03-30 09:08:38 +00:00
case ' m.image ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Image ;
2020-03-30 09:08:38 +00:00
case ' m.video ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Video ;
2020-03-30 09:08:38 +00:00
case ' m.audio ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Audio ;
2020-03-30 09:08:38 +00:00
case ' m.file ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . File ;
2020-03-30 09:08:38 +00:00
case ' m.sticker ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Sticker ;
2020-03-30 09:08:38 +00:00
case ' m.location ' :
2020-01-04 09:31:27 +00:00
return MessageTypes . Location ;
2020-03-30 09:08:38 +00:00
case ' m.bad.encrypted ' :
2020-02-18 07:02:17 +00:00
return MessageTypes . BadEncrypted ;
2020-01-04 09:31:27 +00:00
default :
if ( type = = EventTypes . Message ) {
return MessageTypes . Text ;
}
return MessageTypes . None ;
}
}
2020-01-02 14:09:49 +00:00
void setRedactionEvent ( Event redactedBecause ) {
unsigned = {
2020-03-30 09:08:38 +00:00
' redacted_because ' : redactedBecause . toJson ( ) ,
2020-01-02 14:09:49 +00:00
} ;
prevContent = null ;
2020-03-30 09:08:38 +00:00
var contentKeyWhiteList = < String > [ ] ;
2020-01-02 14:09:49 +00:00
switch ( type ) {
case EventTypes . RoomMember:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' membership ' ) ;
2020-01-02 14:09:49 +00:00
break ;
case EventTypes . RoomCreate:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' creator ' ) ;
2020-01-02 14:09:49 +00:00
break ;
case EventTypes . RoomJoinRules:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' join_rule ' ) ;
2020-01-02 14:09:49 +00:00
break ;
case EventTypes . RoomPowerLevels:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' ban ' ) ;
contentKeyWhiteList . add ( ' events ' ) ;
contentKeyWhiteList . add ( ' events_default ' ) ;
contentKeyWhiteList . add ( ' kick ' ) ;
contentKeyWhiteList . add ( ' redact ' ) ;
contentKeyWhiteList . add ( ' state_default ' ) ;
contentKeyWhiteList . add ( ' users ' ) ;
contentKeyWhiteList . add ( ' users_default ' ) ;
2020-01-02 14:09:49 +00:00
break ;
case EventTypes . RoomAliases:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' aliases ' ) ;
2020-01-02 14:09:49 +00:00
break ;
case EventTypes . HistoryVisibility:
2020-03-30 09:08:38 +00:00
contentKeyWhiteList . add ( ' history_visibility ' ) ;
2020-01-02 14:09:49 +00:00
break ;
default :
break ;
}
2020-03-30 09:08:38 +00:00
var toRemoveList = < String > [ ] ;
2020-01-02 14:09:49 +00:00
for ( var entry in content . entries ) {
2020-01-02 14:33:26 +00:00
if ( ! contentKeyWhiteList . contains ( entry . key ) ) {
2020-01-02 14:09:49 +00:00
toRemoveList . add ( entry . key ) ;
}
}
toRemoveList . forEach ( ( s ) = > content . remove ( s ) ) ;
2019-08-07 07:52:36 +00:00
}
2019-06-09 10:16:48 +00:00
2019-06-11 09:23:57 +00:00
/// Returns the body of this event if it has a body.
2020-03-30 09:08:38 +00:00
String get text = > content [ ' body ' ] ? ? ' ' ;
2019-06-11 09:23:57 +00:00
/// Returns the formatted boy of this event if it has a formatted body.
2020-03-30 09:08:38 +00:00
String get formattedText = > content [ ' formatted_body ' ] ? ? ' ' ;
2019-06-11 09:23:57 +00:00
2020-03-30 09:08:38 +00:00
@ Deprecated ( ' Use [body] instead. ' )
2020-01-14 11:27:26 +00:00
String getBody ( ) = > body ;
2019-06-11 09:23:57 +00:00
/// Use this to get the body.
2020-01-14 11:27:26 +00:00
String get body {
2020-03-30 09:08:38 +00:00
if ( redacted ) return ' Redacted ' ;
if ( text ! = ' ' ) return text ;
if ( formattedText ! = ' ' ) return formattedText ;
return ' $ type ' ;
2019-06-09 10:16:48 +00:00
}
2019-10-20 09:44:14 +00:00
/// Returns a list of [Receipt] instances for this event.
List < Receipt > get receipts {
2020-03-30 09:08:38 +00:00
if ( ! ( room . roomAccountData . containsKey ( ' m.receipt ' ) ) ) return [ ] ;
var receiptsList = < Receipt > [ ] ;
for ( var entry in room . roomAccountData [ ' m.receipt ' ] . content . entries ) {
if ( entry . value [ ' event_id ' ] = = eventId ) {
2020-01-02 14:09:49 +00:00
receiptsList . add ( Receipt ( room . getUserByMXIDSync ( entry . key ) ,
2020-03-30 09:08:38 +00:00
DateTime . fromMillisecondsSinceEpoch ( entry . value [ ' ts ' ] ) ) ) ;
2020-01-02 14:33:26 +00:00
}
2019-10-20 09:44:14 +00:00
}
return receiptsList ;
}
2019-06-27 08:12:39 +00:00
/// Removes this event if the status is < 1. This event will just be removed
2019-07-24 08:13:02 +00:00
/// from the database and the timelines. Returns false if not removed.
2019-07-24 08:48:13 +00:00
Future < bool > remove ( ) async {
2019-06-27 08:12:39 +00:00
if ( status < 1 ) {
2020-01-02 14:33:26 +00:00
if ( room . client . store ! = null ) {
2019-10-02 11:33:01 +00:00
await room . client . store . removeEvent ( eventId ) ;
2020-01-02 14:33:26 +00:00
}
2019-06-27 08:33:43 +00:00
2020-01-02 14:09:49 +00:00
room . client . onEvent . add ( EventUpdate (
2019-06-27 08:12:39 +00:00
roomID: room . id ,
2020-03-30 09:08:38 +00:00
type: ' timeline ' ,
2019-08-07 07:52:36 +00:00
eventType: typeKey ,
2019-06-27 08:20:47 +00:00
content: {
2020-03-30 09:08:38 +00:00
' event_id ' : eventId ,
' status ' : - 2 ,
' content ' : { ' body ' : ' Removed... ' }
2019-06-27 08:20:47 +00:00
} ) ) ;
2019-07-24 08:13:02 +00:00
return true ;
2019-06-27 08:12:39 +00:00
}
2019-07-24 08:13:02 +00:00
return false ;
2019-06-27 08:12:39 +00:00
}
/// Try to send this event again. Only works with events of status -1.
2019-06-28 08:38:21 +00:00
Future < String > sendAgain ( { String txid } ) async {
if ( status ! = - 1 ) return null ;
2020-01-02 14:33:26 +00:00
await remove ( ) ;
2020-03-30 09:08:38 +00:00
final eventID = await room . sendTextEvent ( text , txid: txid ) ;
2019-06-28 08:38:21 +00:00
return eventID ;
2019-06-27 08:12:39 +00:00
}
2019-11-26 06:38:44 +00:00
/// Whether the client is allowed to redact this event.
bool get canRedact = > senderId = = room . client . userID | | room . canRedact ;
2019-12-12 12:19:18 +00:00
/// Redacts this event. Returns [ErrorResponse] on error.
Future < dynamic > redact ( { String reason , String txid } ) = >
room . redactEvent ( eventId , reason: reason , txid: txid ) ;
2020-02-11 11:06:54 +00:00
/// Whether this event is in reply to another event.
bool get isReply = >
content [ ' m.relates_to ' ] is Map < String , dynamic > & &
content [ ' m.relates_to ' ] [ ' m.in_reply_to ' ] is Map < String , dynamic > & &
content [ ' m.relates_to ' ] [ ' m.in_reply_to ' ] [ ' event_id ' ] is String & &
( content [ ' m.relates_to ' ] [ ' m.in_reply_to ' ] [ ' event_id ' ] as String )
2020-02-11 11:28:26 +00:00
. isNotEmpty ;
2020-02-11 11:06:54 +00:00
/// Searches for the reply event in the given timeline.
Future < Event > getReplyEvent ( Timeline timeline ) async {
if ( ! isReply ) return null ;
2020-02-18 07:02:17 +00:00
final String replyEventId =
content [ ' m.relates_to ' ] [ ' m.in_reply_to ' ] [ ' event_id ' ] ;
2020-02-11 11:06:54 +00:00
return await timeline . getEventById ( replyEventId ) ;
}
2020-02-21 08:44:05 +00:00
/// Trys to decrypt this event. Returns a m.bad.encrypted event
/// if it fails and does nothing if the event was not encrypted.
Event get decrypted = > room . decryptGroupMessage ( this ) ;
2020-02-21 15:05:19 +00:00
/// If this event is encrypted and the decryption was not successful because
/// the session is unknown, this requests the session key from other devices
/// in the room. If the event is not encrypted or the decryption failed because
/// of a different error, this throws an exception.
Future < void > requestKey ( ) async {
2020-03-30 09:08:38 +00:00
if ( type ! = EventTypes . Encrypted | |
messageType ! = MessageTypes . BadEncrypted | |
content [ ' body ' ] ! = DecryptError . UNKNOWN_SESSION ) {
throw ( ' Session key not unknown ' ) ;
2020-02-21 15:05:19 +00:00
}
2020-03-30 09:08:38 +00:00
final users = await room . requestParticipants ( ) ;
2020-02-21 15:05:19 +00:00
await room . client . sendToDevice (
[ ] ,
2020-03-30 09:08:38 +00:00
' m.room_key_request ' ,
2020-02-21 15:05:19 +00:00
{
2020-03-30 09:08:38 +00:00
' action ' : ' request_cancellation ' ,
' request_id ' : base64 . encode ( utf8 . encode ( content [ ' session_id ' ] ) ) ,
' requesting_device_id ' : room . client . deviceID ,
2020-02-22 08:08:01 +00:00
} ,
2020-03-31 10:24:53 +00:00
encrypted: false ,
2020-02-22 08:08:01 +00:00
toUsers: users ) ;
2020-02-21 15:05:19 +00:00
await room . client . sendToDevice (
[ ] ,
2020-03-30 09:08:38 +00:00
' m.room_key_request ' ,
2020-02-21 15:05:19 +00:00
{
2020-03-30 09:08:38 +00:00
' action ' : ' request ' ,
' body ' : {
' algorithm ' : ' m.megolm.v1.aes-sha2 ' ,
' room_id ' : roomId ,
' sender_key ' : content [ ' sender_key ' ] ,
' session_id ' : content [ ' session_id ' ] ,
2020-02-21 15:05:19 +00:00
} ,
2020-03-30 09:08:38 +00:00
' request_id ' : base64 . encode ( utf8 . encode ( content [ ' session_id ' ] ) ) ,
' requesting_device_id ' : room . client . deviceID ,
2020-02-21 15:05:19 +00:00
} ,
2020-02-22 08:08:01 +00:00
encrypted: false ,
toUsers: users ) ;
2020-02-21 15:05:19 +00:00
return ;
}
2020-03-16 10:38:03 +00:00
2020-04-17 14:11:13 +00:00
bool get hasThumbnail = >
content [ ' info ' ] is Map < String , dynamic > & &
( content [ ' info ' ] . containsKey ( ' thumbnail_url ' ) | |
content [ ' info ' ] . containsKey ( ' thumbnail_file ' ) ) ;
2020-03-16 10:38:03 +00:00
/// Downloads (and decryptes if necessary) the attachment of this
/// event and returns it as a [MatrixFile]. If this event doesn't
2020-04-17 14:11:13 +00:00
/// contain an attachment, this throws an error. Set [getThumbnail] to
/// true to download the thumbnail instead.
Future < MatrixFile > downloadAndDecryptAttachment (
{ bool getThumbnail = false } ) async {
2020-03-30 09:08:38 +00:00
if ( ! [ EventTypes . Message , EventTypes . Sticker ] . contains ( type ) ) {
2020-03-16 10:38:03 +00:00
throw ( " This event has the type ' $ typeKey ' and so it can't contain an attachment. " ) ;
}
2020-04-17 14:11:13 +00:00
if ( ! getThumbnail & &
! content . containsKey ( ' url ' ) & &
! content . containsKey ( ' file ' ) ) {
2020-03-16 10:38:03 +00:00
throw ( " This event hasn't any attachment. " ) ;
}
2020-04-17 14:11:13 +00:00
if ( getThumbnail & & ! hasThumbnail ) {
throw ( " This event hasn't any thumbnail. " ) ;
}
final isEncrypted = getThumbnail
? ! content [ ' info ' ] . containsKey ( ' thumbnail_url ' )
: ! content . containsKey ( ' url ' ) ;
2020-03-16 10:38:03 +00:00
if ( isEncrypted & & ! room . client . encryptionEnabled ) {
2020-03-30 09:08:38 +00:00
throw ( ' Encryption is not enabled in your Client. ' ) ;
2020-03-16 10:38:03 +00:00
}
2020-04-17 14:11:13 +00:00
var mxContent = getThumbnail
2020-04-24 07:24:06 +00:00
? Uri . parse ( isEncrypted
2020-04-17 14:11:13 +00:00
? content [ ' info ' ] [ ' thumbnail_file ' ] [ ' url ' ]
: content [ ' info ' ] [ ' thumbnail_url ' ] )
2020-04-24 07:24:06 +00:00
: Uri . parse ( isEncrypted ? content [ ' file ' ] [ ' url ' ] : content [ ' url ' ] ) ;
2020-03-16 10:38:03 +00:00
Uint8List uint8list ;
// Is this file storeable?
2020-04-17 14:11:13 +00:00
final infoMap =
getThumbnail ? content [ ' info ' ] [ ' thumbnail_info ' ] : content [ ' info ' ] ;
2020-05-05 07:07:09 +00:00
final storeable = ( room . client . storeAPI ? . extended ? ? false ) & &
2020-04-17 14:11:13 +00:00
infoMap is Map < String , dynamic > & &
infoMap [ ' size ' ] is int & &
2020-04-28 11:55:36 +00:00
infoMap [ ' size ' ] < = room . client . store . maxFileSize ;
2020-03-16 10:38:03 +00:00
if ( storeable ) {
2020-04-24 07:24:06 +00:00
uint8list = await room . client . store . getFile ( mxContent . toString ( ) ) ;
2020-03-16 10:38:03 +00:00
}
// Download the file
if ( uint8list = = null ) {
uint8list =
( await http . get ( mxContent . getDownloadLink ( room . client ) ) ) . bodyBytes ;
2020-03-31 15:47:19 +00:00
if ( storeable ) {
2020-04-24 07:24:06 +00:00
await room . client . store . storeFile ( uint8list , mxContent . toString ( ) ) ;
2020-03-31 15:47:19 +00:00
}
2020-03-16 10:38:03 +00:00
}
// Decrypt the file
if ( isEncrypted ) {
2020-04-17 14:11:13 +00:00
final fileMap =
getThumbnail ? content [ ' info ' ] [ ' thumbnail_file ' ] : content [ ' file ' ] ;
if ( ! fileMap [ ' key ' ] [ ' key_ops ' ] . contains ( ' decrypt ' ) ) {
2020-03-16 10:38:03 +00:00
throw ( " Missing 'decrypt' in 'key_ops'. " ) ;
}
2020-03-30 09:08:38 +00:00
final encryptedFile = EncryptedFile ( ) ;
2020-03-16 10:38:03 +00:00
encryptedFile . data = uint8list ;
2020-04-17 14:11:13 +00:00
encryptedFile . iv = fileMap [ ' iv ' ] ;
encryptedFile . k = fileMap [ ' key ' ] [ ' k ' ] ;
encryptedFile . sha256 = fileMap [ ' hashes ' ] [ ' sha256 ' ] ;
2020-03-16 10:38:03 +00:00
uint8list = await decryptFile ( encryptedFile ) ;
}
2020-03-30 09:08:38 +00:00
return MatrixFile ( bytes: uint8list , path: ' / $ body ' ) ;
2020-03-16 10:38:03 +00:00
}
2019-06-09 10:16:48 +00:00
}
2020-01-02 14:09:49 +00:00
2020-01-04 09:31:27 +00:00
enum MessageTypes {
2020-01-02 14:09:49 +00:00
Text ,
Emote ,
Notice ,
Image ,
Video ,
Audio ,
File ,
Location ,
Reply ,
2020-01-04 09:31:27 +00:00
Sticker ,
2020-02-18 07:02:17 +00:00
BadEncrypted ,
2020-01-04 09:31:27 +00:00
None ,
}
enum EventTypes {
Message ,
Sticker ,
Redaction ,
2020-01-02 14:09:49 +00:00
RoomAliases ,
RoomCanonicalAlias ,
RoomCreate ,
RoomJoinRules ,
RoomMember ,
RoomPowerLevels ,
RoomName ,
RoomTopic ,
RoomAvatar ,
GuestAccess ,
HistoryVisibility ,
2020-01-04 18:36:17 +00:00
Encryption ,
Encrypted ,
CallInvite ,
CallAnswer ,
CallCandidates ,
CallHangup ,
2020-01-02 14:09:49 +00:00
Unknown ,
}