2019-06-09 11:57:33 +00:00
/ *
2020-06-03 10:16:01 +00:00
* Famedly Matrix SDK
* Copyright ( C ) 2019 , 2020 Famedly GmbH
2019-06-09 11:57:33 +00:00
*
2020-06-03 10:16:01 +00:00
* This program is free software: you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
2019-06-09 11:57:33 +00:00
*
2020-06-03 10:16:01 +00:00
* This program 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 Affero General Public License for more details .
2019-06-09 11:57:33 +00:00
*
2020-06-03 10:16:01 +00:00
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https: //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 ' ;
2020-06-04 11:39:51 +00:00
import ' package:famedlysdk/encryption.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 ' ;
2020-06-03 10:16:01 +00:00
import ' ../matrix_api.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 ' ;
2020-05-06 10:13:30 +00:00
import ' utils/matrix_localizations.dart ' ;
2020-05-15 18:40:17 +00:00
import ' ./database/database.dart ' show DbRoomState , DbEvent ;
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-06-03 10:16:01 +00:00
class Event extends MatrixEvent {
2020-03-30 09:08:38 +00:00
User get sender = > room . getUserByMXIDSync ( senderId ? ? ' @unknown ' ) ;
2020-01-02 14:09:49 +00:00
2020-06-03 10:16:01 +00:00
@ Deprecated ( ' Use [originServerTs] instead ' )
DateTime get time = > originServerTs ;
2020-01-02 14:09:49 +00:00
2020-06-03 10:16:01 +00:00
@ Deprecated ( ' Use [type] instead ' )
String get typeKey = > type ;
2020-01-02 14:09:49 +00:00
/// The room this event belongs to. May be null.
final Room room ;
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
2020-05-15 18:40:17 +00:00
double sortOrder ;
2019-06-21 07:46:53 +00:00
Event (
2019-08-29 07:12:55 +00:00
{ this . status = defaultStatus ,
2020-06-03 10:16:01 +00:00
Map < String , dynamic > content ,
String type ,
String eventId ,
String roomId ,
String senderId ,
DateTime originServerTs ,
Map < String , dynamic > unsigned ,
Map < String , dynamic > prevContent ,
String stateKey ,
2020-05-15 18:40:17 +00:00
this . room ,
2020-06-03 10:16:01 +00:00
this . sortOrder = 0.0 } ) {
this . content = content ;
this . type = type ;
this . eventId = eventId ;
this . roomId = roomId ? ? room ? . id ;
this . senderId = senderId ;
this . unsigned = unsigned ;
this . prevContent = prevContent ;
this . stateKey = stateKey ;
this . originServerTs = originServerTs ;
}
2020-01-02 14:09:49 +00:00
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
2020-06-03 10:16:01 +00:00
factory Event . fromMatrixEvent (
MatrixEvent matrixEvent ,
Room room , {
double sortOrder ,
int status ,
} ) = >
Event (
status: status ,
content: matrixEvent . content ,
type: matrixEvent . type ,
eventId: matrixEvent . eventId ,
roomId: room . id ,
senderId: matrixEvent . senderId ,
originServerTs: matrixEvent . originServerTs ,
unsigned: matrixEvent . unsigned ,
prevContent: matrixEvent . prevContent ,
stateKey: matrixEvent . stateKey ,
room: room ,
sortOrder: sortOrder ,
) ;
2019-08-07 07:52:36 +00:00
/// Get a State event from a table row or from the event stream.
2020-05-16 07:47:19 +00:00
factory Event . fromJson ( Map < String , dynamic > jsonPayload , Room room ,
[ double sortOrder ] ) {
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 ,
2020-06-03 10:16:01 +00:00
type: jsonPayload [ ' type ' ] ,
2020-01-02 14:09:49 +00:00
eventId: jsonPayload [ ' event_id ' ] ,
roomId: jsonPayload [ ' room_id ' ] ,
senderId: jsonPayload [ ' sender ' ] ,
2020-06-03 10:16:01 +00:00
originServerTs: jsonPayload . containsKey ( ' origin_server_ts ' )
2020-01-02 14:09:49 +00:00
? DateTime . fromMillisecondsSinceEpoch ( jsonPayload [ ' origin_server_ts ' ] )
: DateTime . now ( ) ,
unsigned: unsigned ,
room: room ,
2020-05-15 18:40:17 +00:00
sortOrder: sortOrder ? ? 0.0 ,
) ;
}
/// Get an event from either DbRoomState or DbEvent
factory Event . fromDb ( dynamic dbEntry , Room room ) {
if ( ! ( dbEntry is DbRoomState | | dbEntry is DbEvent ) ) {
2020-05-16 07:47:19 +00:00
throw ( ' Unknown db type ' ) ;
2020-05-15 18:40:17 +00:00
}
final content = Event . getMapFromPayload ( dbEntry . content ) ;
final unsigned = Event . getMapFromPayload ( dbEntry . unsigned ) ;
final prevContent = Event . getMapFromPayload ( dbEntry . prevContent ) ;
return Event (
status: ( dbEntry is DbEvent ? dbEntry . status : null ) ? ? defaultStatus ,
stateKey: dbEntry . stateKey ,
prevContent: prevContent ,
content: content ,
2020-06-03 10:16:01 +00:00
type: dbEntry . type ,
2020-05-15 18:40:17 +00:00
eventId: dbEntry . eventId ,
roomId: dbEntry . roomId ,
senderId: dbEntry . sender ,
2020-06-03 10:16:01 +00:00
originServerTs: dbEntry . originServerTs ? ? DateTime . now ( ) ,
2020-05-15 18:40:17 +00:00
unsigned: unsigned ,
room: room ,
sortOrder: dbEntry . sortOrder ? ? 0.0 ,
2020-01-02 14:09:49 +00:00
) ;
}
2020-06-03 10:16:01 +00:00
@ override
2020-01-02 14:09:49 +00:00
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 ;
2020-06-03 10:16:01 +00:00
data [ ' type ' ] = type ;
2020-03-30 09:08:38 +00:00
data [ ' event_id ' ] = eventId ;
data [ ' room_id ' ] = roomId ;
data [ ' sender ' ] = senderId ;
2020-06-03 10:16:01 +00:00
data [ ' origin_server_ts ' ] = originServerTs . millisecondsSinceEpoch ;
2020-03-30 09:08:38 +00:00
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 ;
}
User get asUser = > User . fromState (
stateKey: stateKey ,
prevContent: prevContent ,
content: content ,
2020-06-03 10:16:01 +00:00
typeKey: type ,
2020-01-02 14:09:49 +00:00
eventId: eventId ,
roomId: roomId ,
senderId: senderId ,
2020-06-03 10:16:01 +00:00
originServerTs: originServerTs ,
2020-01-02 14:09:49 +00:00
unsigned: unsigned ,
room: room ) ;
2020-06-03 10:16:01 +00:00
String get messageType = > ( content . containsKey ( ' m.relates_to ' ) & &
content [ ' m.relates_to ' ] [ ' m.in_reply_to ' ] ! = null )
? MessageTypes . Reply
: content [ ' msgtype ' ] ? ? MessageTypes . Text ;
2020-01-04 09:31:27 +00:00
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
/// 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-05-15 18:40:17 +00:00
await room . client . database ? . removeEvent ( room . client . id , eventId , room . id ) ;
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 ' ,
2020-06-03 10:16:01 +00:00
eventType: type ,
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... ' }
2020-05-15 18:40:17 +00:00
} ,
sortOrder: sortOrder ) ) ;
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-06-19 12:05:53 +00:00
final eventID = await room . sendEvent (
content ,
txid: txid ? ? unsigned [ ' transaction_id ' ] ,
) ;
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
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 | |
2020-06-10 08:44:22 +00:00
content [ ' can_request_session ' ] ! = true ) {
throw ( ' Session key not requestable ' ) ;
2020-02-21 15:05:19 +00:00
}
2020-05-19 09:34:11 +00:00
await room . requestSessionKey ( content [ ' session_id ' ] , content [ ' sender_key ' ] ) ;
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-06-03 10:16:01 +00:00
throw ( " This event has the type ' $ type ' and so it can't contain an attachment. " ) ;
2020-03-16 10:38:03 +00:00
}
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-15 18:40:17 +00:00
final storeable = room . client . database ! = null & &
2020-04-17 14:11:13 +00:00
infoMap is Map < String , dynamic > & &
infoMap [ ' size ' ] is int & &
2020-05-16 07:47:19 +00:00
infoMap [ ' size ' ] < = room . client . database . maxFileSize ;
2020-03-16 10:38:03 +00:00
if ( storeable ) {
2020-05-15 18:40:17 +00:00
uint8list = await room . client . database . 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-05-16 07:47:19 +00:00
await room . client . database
. storeFile ( mxContent . toString ( ) , uint8list , DateTime . now ( ) ) ;
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
}
2020-05-06 10:13:30 +00:00
/// Returns a localized String representation of this event. For a
/// room list you may find [withSenderNamePrefix] useful. Set [hideReply] to
/// crop all lines starting with '>'.
String getLocalizedBody ( MatrixLocalizations i18n ,
{ bool withSenderNamePrefix = false , bool hideReply = false } ) {
if ( redacted ) {
return i18n . removedBy ( redactedBecause . sender . calcDisplayname ( ) ) ;
}
var localizedBody = body ;
final senderName = sender . calcDisplayname ( ) ;
switch ( type ) {
case EventTypes . Sticker:
localizedBody = i18n . sentASticker ( senderName ) ;
break ;
case EventTypes . Redaction:
localizedBody = i18n . redactedAnEvent ( senderName ) ;
break ;
case EventTypes . RoomAliases:
localizedBody = i18n . changedTheRoomAliases ( senderName ) ;
break ;
case EventTypes . RoomCanonicalAlias:
localizedBody = i18n . changedTheRoomInvitationLink ( senderName ) ;
break ;
case EventTypes . RoomCreate:
localizedBody = i18n . createdTheChat ( senderName ) ;
break ;
2020-05-16 07:47:19 +00:00
case EventTypes . RoomTombstone:
localizedBody = i18n . roomHasBeenUpgraded ;
break ;
2020-05-06 10:13:30 +00:00
case EventTypes . RoomJoinRules:
var joinRules = JoinRules . values . firstWhere (
( r ) = >
r . toString ( ) . replaceAll ( ' JoinRules. ' , ' ' ) = =
content [ ' join_rule ' ] ,
orElse: ( ) = > null ) ;
if ( joinRules = = null ) {
localizedBody = i18n . changedTheJoinRules ( senderName ) ;
} else {
localizedBody = i18n . changedTheJoinRulesTo (
senderName , joinRules . getLocalizedString ( i18n ) ) ;
}
break ;
case EventTypes . RoomMember:
var text = ' Failed to parse member event ' ;
final targetName = stateKeyUser . calcDisplayname ( ) ;
// Has the membership changed?
final newMembership = content [ ' membership ' ] ? ? ' ' ;
final oldMembership = unsigned [ ' prev_content ' ] is Map < String , dynamic >
? unsigned [ ' prev_content ' ] [ ' membership ' ] ? ? ' '
: ' ' ;
if ( newMembership ! = oldMembership ) {
if ( oldMembership = = ' invite ' & & newMembership = = ' join ' ) {
text = i18n . acceptedTheInvitation ( targetName ) ;
} else if ( oldMembership = = ' invite ' & & newMembership = = ' leave ' ) {
if ( stateKey = = senderId ) {
text = i18n . rejectedTheInvitation ( targetName ) ;
} else {
text = i18n . hasWithdrawnTheInvitationFor ( senderName , targetName ) ;
}
} else if ( oldMembership = = ' leave ' & & newMembership = = ' join ' ) {
text = i18n . joinedTheChat ( targetName ) ;
} else if ( oldMembership = = ' join ' & & newMembership = = ' ban ' ) {
text = i18n . kickedAndBanned ( senderName , targetName ) ;
} else if ( oldMembership = = ' join ' & &
newMembership = = ' leave ' & &
stateKey ! = senderId ) {
text = i18n . kicked ( senderName , targetName ) ;
} else if ( oldMembership = = ' join ' & &
newMembership = = ' leave ' & &
stateKey = = senderId ) {
text = i18n . userLeftTheChat ( targetName ) ;
} else if ( oldMembership = = ' invite ' & & newMembership = = ' ban ' ) {
text = i18n . bannedUser ( senderName , targetName ) ;
} else if ( oldMembership = = ' leave ' & & newMembership = = ' ban ' ) {
text = i18n . bannedUser ( senderName , targetName ) ;
} else if ( oldMembership = = ' ban ' & & newMembership = = ' leave ' ) {
text = i18n . unbannedUser ( senderName , targetName ) ;
} else if ( newMembership = = ' invite ' ) {
text = i18n . invitedUser ( senderName , targetName ) ;
} else if ( newMembership = = ' join ' ) {
text = i18n . joinedTheChat ( targetName ) ;
}
} else if ( newMembership = = ' join ' ) {
final newAvatar = content [ ' avatar_url ' ] ? ? ' ' ;
final oldAvatar = unsigned [ ' prev_content ' ] is Map < String , dynamic >
? unsigned [ ' prev_content ' ] [ ' avatar_url ' ] ? ? ' '
: ' ' ;
final newDisplayname = content [ ' displayname ' ] ? ? ' ' ;
final oldDisplayname =
unsigned [ ' prev_content ' ] is Map < String , dynamic >
? unsigned [ ' prev_content ' ] [ ' displayname ' ] ? ? ' '
: ' ' ;
// Has the user avatar changed?
if ( newAvatar ! = oldAvatar ) {
text = i18n . changedTheProfileAvatar ( targetName ) ;
}
// Has the user avatar changed?
else if ( newDisplayname ! = oldDisplayname ) {
text = i18n . changedTheDisplaynameTo ( targetName , newDisplayname ) ;
}
}
localizedBody = text ;
break ;
case EventTypes . RoomPowerLevels:
localizedBody = i18n . changedTheChatPermissions ( senderName ) ;
break ;
case EventTypes . RoomName:
localizedBody = i18n . changedTheChatNameTo ( senderName , content [ ' name ' ] ) ;
break ;
case EventTypes . RoomTopic:
localizedBody =
i18n . changedTheChatDescriptionTo ( senderName , content [ ' topic ' ] ) ;
break ;
case EventTypes . RoomAvatar:
localizedBody = i18n . changedTheChatAvatar ( senderName ) ;
break ;
case EventTypes . GuestAccess:
var guestAccess = GuestAccess . values . firstWhere (
( r ) = >
r . toString ( ) . replaceAll ( ' GuestAccess. ' , ' ' ) = =
content [ ' guest_access ' ] ,
orElse: ( ) = > null ) ;
if ( guestAccess = = null ) {
localizedBody = i18n . changedTheGuestAccessRules ( senderName ) ;
} else {
localizedBody = i18n . changedTheGuestAccessRulesTo (
senderName , guestAccess . getLocalizedString ( i18n ) ) ;
}
break ;
case EventTypes . HistoryVisibility:
var historyVisibility = HistoryVisibility . values . firstWhere (
( r ) = >
r . toString ( ) . replaceAll ( ' HistoryVisibility. ' , ' ' ) = =
content [ ' history_visibility ' ] ,
orElse: ( ) = > null ) ;
if ( historyVisibility = = null ) {
localizedBody = i18n . changedTheHistoryVisibility ( senderName ) ;
} else {
localizedBody = i18n . changedTheHistoryVisibilityTo (
senderName , historyVisibility . getLocalizedString ( i18n ) ) ;
}
break ;
case EventTypes . Encryption:
localizedBody = i18n . activatedEndToEndEncryption ( senderName ) ;
if ( ! room . client . encryptionEnabled ) {
localizedBody + = ' . ' + i18n . needPantalaimonWarning ;
}
break ;
case EventTypes . Encrypted:
case EventTypes . Message:
switch ( messageType ) {
case MessageTypes . Image:
localizedBody = i18n . sentAPicture ( senderName ) ;
break ;
case MessageTypes . File:
localizedBody = i18n . sentAFile ( senderName ) ;
break ;
case MessageTypes . Audio:
localizedBody = i18n . sentAnAudio ( senderName ) ;
break ;
case MessageTypes . Video:
localizedBody = i18n . sentAVideo ( senderName ) ;
break ;
case MessageTypes . Location:
localizedBody = i18n . sharedTheLocation ( senderName ) ;
break ;
case MessageTypes . Sticker:
localizedBody = i18n . sentASticker ( senderName ) ;
break ;
case MessageTypes . Emote:
localizedBody = ' * $ body ' ;
break ;
case MessageTypes . BadEncrypted:
String errorText ;
switch ( body ) {
case DecryptError . CHANNEL_CORRUPTED:
errorText = i18n . channelCorruptedDecryptError + ' . ' ;
break ;
case DecryptError . NOT_ENABLED:
errorText = i18n . encryptionNotEnabled + ' . ' ;
break ;
case DecryptError . UNKNOWN_ALGORITHM:
errorText = i18n . unknownEncryptionAlgorithm + ' . ' ;
break ;
case DecryptError . UNKNOWN_SESSION:
errorText = i18n . noPermission + ' . ' ;
break ;
default :
errorText = body ;
break ;
}
localizedBody = i18n . couldNotDecryptMessage ( errorText ) ;
break ;
case MessageTypes . Text:
case MessageTypes . Notice:
case MessageTypes . None:
case MessageTypes . Reply:
localizedBody = body ;
break ;
}
break ;
default :
2020-06-03 10:16:01 +00:00
localizedBody = i18n . unknownEvent ( type ) ;
2020-05-06 10:13:30 +00:00
}
// Hide reply fallback
if ( hideReply ) {
localizedBody = localizedBody . replaceFirst (
RegExp ( r'^>( \*)? <[^>]+>[^\n\r]+\r?\n(> [^\n]*\r?\n)*\r?\n' ) , ' ' ) ;
}
// Add the sender name prefix
if ( withSenderNamePrefix & &
type = = EventTypes . Message & &
textOnlyMessageTypes . contains ( messageType ) ) {
final senderNameOrYou =
senderId = = room . client . userID ? i18n . you : senderName ;
localizedBody = ' $ senderNameOrYou : $ localizedBody ' ;
}
return localizedBody ;
}
2020-06-03 10:16:01 +00:00
static const Set < String > textOnlyMessageTypes = {
2020-05-06 10:13:30 +00:00
MessageTypes . Text ,
MessageTypes . Reply ,
MessageTypes . Notice ,
MessageTypes . Emote ,
MessageTypes . None ,
} ;
2019-06-09 10:16:48 +00:00
}