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 ' ;
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 ' ;
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-02-14 14:06:46 +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 = {
" ERROR " : - 1 ,
" SENDING " : 0 ,
" SENT " : 1 ,
" TIMELINE " : 2 ,
" ROOM_STATE " : 3 ,
} ;
/// Optional. The event that redacted this event, if any. Otherwise null.
Event get redactedBecause = >
unsigned ! = null & & unsigned . containsKey ( " redacted_because " )
? Event . fromJson ( unsigned [ " redacted_because " ] , room )
: 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 ) {
2019-08-07 07:52:36 +00:00
final Map < String , dynamic > content =
2020-01-02 14:09:49 +00:00
Event . getMapFromPayload ( jsonPayload [ ' content ' ] ) ;
2019-08-07 07:52:36 +00:00
final Map < String , dynamic > unsigned =
2020-01-02 14:09:49 +00:00
Event . getMapFromPayload ( jsonPayload [ ' unsigned ' ] ) ;
2019-08-08 10:29:09 +00:00
final Map < String , dynamic > prevContent =
2020-01-02 14:09:49 +00:00
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-01-02 14:33:26 +00:00
final Map < String , dynamic > data = Map < String , dynamic > ( ) ;
2020-01-02 14:09:49 +00:00
if ( this . stateKey ! = null ) data [ ' state_key ' ] = this . stateKey ;
2020-01-02 14:33:26 +00:00
if ( this . prevContent ! = null & & this . prevContent . isNotEmpty ) {
2020-01-02 14:09:49 +00:00
data [ ' prev_content ' ] = this . prevContent ;
2020-01-02 14:33:26 +00:00
}
2020-01-02 14:09:49 +00:00
data [ ' content ' ] = this . content ;
data [ ' type ' ] = this . typeKey ;
data [ ' event_id ' ] = this . eventId ;
data [ ' room_id ' ] = this . roomId ;
data [ ' sender ' ] = this . senderId ;
data [ ' origin_server_ts ' ] = this . time . millisecondsSinceEpoch ;
2020-01-02 14:33:26 +00:00
if ( this . unsigned ! = null & & this . unsigned . isNotEmpty ) {
2020-01-02 14:09:49 +00:00
data [ ' unsigned ' ] = this . 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 ) {
case " m.room.avatar " :
return EventTypes . RoomAvatar ;
case " m.room.name " :
return EventTypes . RoomName ;
case " m.room.topic " :
return EventTypes . RoomTopic ;
2020-01-29 12:11:21 +00:00
case " m.room.aliases " :
2020-01-02 14:09:49 +00:00
return EventTypes . RoomAliases ;
case " m.room.canonical_alias " :
return EventTypes . RoomCanonicalAlias ;
case " m.room.create " :
return EventTypes . RoomCreate ;
case " m.room.redaction " :
return EventTypes . Redaction ;
case " m.room.join_rules " :
return EventTypes . RoomJoinRules ;
case " m.room.member " :
return EventTypes . RoomMember ;
case " m.room.power_levels " :
return EventTypes . RoomPowerLevels ;
case " m.room.guest_access " :
return EventTypes . GuestAccess ;
case " m.room.history_visibility " :
return EventTypes . HistoryVisibility ;
2020-01-04 09:31:27 +00:00
case " m.sticker " :
return EventTypes . Sticker ;
2020-01-02 14:09:49 +00:00
case " m.room.message " :
2020-01-04 09:31:27 +00:00
return EventTypes . Message ;
2020-02-04 13:41:13 +00:00
case " m.room.encrypted " :
2020-01-04 18:36:17 +00:00
return EventTypes . Encrypted ;
2020-02-04 13:41:13 +00:00
case " m.room.encryption " :
2020-01-04 18:36:17 +00:00
return EventTypes . Encryption ;
case " m.call.invite " :
return EventTypes . CallInvite ;
case " m.call.answer " :
return EventTypes . CallAnswer ;
case " m.call.candidates " :
return EventTypes . CallCandidates ;
case " m.call.hangup " :
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 {
switch ( content [ " msgtype " ] ? ? " m.text " ) {
case " m.text " :
if ( content . containsKey ( " m.relates_to " ) ) {
return MessageTypes . Reply ;
}
return MessageTypes . Text ;
case " m.notice " :
return MessageTypes . Notice ;
case " m.emote " :
return MessageTypes . Emote ;
case " m.image " :
return MessageTypes . Image ;
case " m.video " :
return MessageTypes . Video ;
case " m.audio " :
return MessageTypes . Audio ;
case " m.file " :
return MessageTypes . File ;
case " m.sticker " :
return MessageTypes . Sticker ;
case " m.location " :
return MessageTypes . Location ;
2020-02-18 07:02:17 +00:00
case " m.bad.encrypted " :
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 = {
" redacted_because " : redactedBecause . toJson ( ) ,
} ;
prevContent = null ;
List < String > contentKeyWhiteList = [ ] ;
switch ( type ) {
case EventTypes . RoomMember:
contentKeyWhiteList . add ( " membership " ) ;
break ;
case EventTypes . RoomCreate:
contentKeyWhiteList . add ( " creator " ) ;
break ;
case EventTypes . RoomJoinRules:
contentKeyWhiteList . add ( " join_rule " ) ;
break ;
case EventTypes . RoomPowerLevels:
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 " ) ;
break ;
case EventTypes . RoomAliases:
contentKeyWhiteList . add ( " aliases " ) ;
break ;
case EventTypes . HistoryVisibility:
contentKeyWhiteList . add ( " history_visibility " ) ;
break ;
default :
break ;
}
List < String > toRemoveList = [ ] ;
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.
String get text = > content [ " body " ] ? ? " " ;
/// Returns the formatted boy of this event if it has a formatted body.
String get formattedText = > content [ " formatted_body " ] ? ? " " ;
2020-01-14 11:27:26 +00:00
@ Deprecated ( " Use [body] instead. " )
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 {
2019-12-12 12:19:18 +00:00
if ( redacted ) return " Redacted " ;
2019-06-12 06:22:30 +00:00
if ( text ! = " " ) return text ;
2019-06-12 07:07:07 +00:00
if ( formattedText ! = " " ) return formattedText ;
2019-08-07 07:52:36 +00:00
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 {
2019-10-25 08:02:56 +00:00
if ( ! ( room . roomAccountData . containsKey ( " m.receipt " ) ) ) return [ ] ;
2019-10-20 09:44:14 +00:00
List < Receipt > receiptsList = [ ] ;
2019-10-25 08:02:56 +00:00
for ( var entry in room . roomAccountData [ " m.receipt " ] . content . entries ) {
2020-01-02 14:33:26 +00:00
if ( entry . value [ " event_id " ] = = eventId ) {
2020-01-02 14:09:49 +00:00
receiptsList . add ( Receipt ( room . getUserByMXIDSync ( entry . key ) ,
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 ,
type: " timeline " ,
2019-08-07 07:52:36 +00:00
eventType: typeKey ,
2019-06-27 08:20:47 +00:00
content: {
2019-08-07 07:52:36 +00:00
" event_id " : eventId ,
2019-06-27 08:20:47 +00:00
" status " : - 2 ,
" content " : { " body " : " Removed... " }
} ) ) ;
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 ( ) ;
2019-06-28 08:38:21 +00:00
final String eventID = await room . sendTextEvent ( text , txid: txid ) ;
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 ) ;
}
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 ,
}