Line data Source code
1 : import 'package:matrix/matrix.dart'; 2 : import 'package:matrix/msc_extensions/msc_3381_polls/models/poll_event_content.dart'; 3 : 4 : extension PollEventExtension on Event { 5 2 : PollEventContent get parsedPollEventContent { 6 4 : assert(type == PollEventContent.startType); 7 4 : return PollEventContent.fromJson(content); 8 : } 9 : 10 : /// Returns a Map of answer IDs to a Set of user IDs. 11 2 : Map<String, Set<String>> getPollResponses(Timeline timeline) { 12 4 : assert(type == PollEventContent.startType); 13 : final aggregatedEvents = 14 10 : timeline.aggregatedEvents[eventId]?['m.reference']?.toList(); 15 4 : if (aggregatedEvents == null || aggregatedEvents.isEmpty) return {}; 16 : 17 2 : final responses = <String, Set<String>>{}; 18 : 19 : // Votes with timestamps after the poll has closed are ignored, as if they 20 : // never happened. 21 : aggregatedEvents 22 10 : .removeWhere((event) => originServerTs.isAfter(event.originServerTs)); 23 : 24 : // Sort by date so only the users most recent vote is used in the end, even 25 : // if it is invalid. 26 : aggregatedEvents 27 2 : .sort((a, b) => a.originServerTs.compareTo(b.originServerTs)); 28 : 29 4 : for (final event in aggregatedEvents) { 30 2 : final answers = event.content 31 2 : .tryGetMap<String, Object?>(PollEventContent.responseType) 32 2 : ?.tryGetList<String>('answers') ?? 33 0 : []; 34 6 : responses[event.senderId] = answers.toSet(); 35 : } 36 : return responses; 37 : } 38 : 39 2 : bool getPollHasBeenEnded(Timeline timeline) { 40 4 : assert(type == PollEventContent.startType); 41 8 : final aggregatedEvents = timeline.aggregatedEvents[eventId]?['m.reference']; 42 2 : if (aggregatedEvents == null || aggregatedEvents.isEmpty) return false; 43 : 44 2 : final redactPowerLevel = (room 45 2 : .getState(EventTypes.RoomPowerLevels) 46 0 : ?.content 47 0 : .tryGet<int>('redact') ?? 48 : 50); 49 : 50 2 : return aggregatedEvents.any( 51 2 : (event) { 52 2 : if (event.content 53 2 : .tryGetMap<String, Object?>(PollEventContent.endType) == 54 : null) { 55 : return false; 56 : } 57 : 58 : // If a m.poll.end event is received from someone other than the poll 59 : //creator or user with permission to redact other's messages in the 60 : //room, the event must be ignored by clients due to being invalid. 61 6 : if (event.senderId == senderId || 62 0 : event.senderFromMemoryOrFallback.powerLevel >= redactPowerLevel) { 63 : return true; 64 : } 65 0 : Logs().w( 66 0 : 'Ignore poll end event form user without permission ${event.senderId}', 67 : ); 68 : return false; 69 : }, 70 : ); 71 : } 72 : 73 2 : Future<String?> answerPoll( 74 : List<String> answerIds, { 75 : String? txid, 76 : }) => 77 4 : room.sendEvent( 78 2 : { 79 2 : 'm.relates_to': { 80 : 'rel_type': 'm.reference', 81 2 : 'event_id': eventId, 82 : }, 83 2 : PollEventContent.responseType: {'answers': answerIds}, 84 : }, 85 : type: PollEventContent.responseType, 86 : txid: txid, 87 : ); 88 : 89 0 : Future<String?> endPoll({String? txid}) => room.sendEvent( 90 0 : { 91 0 : 'm.relates_to': { 92 : 'rel_type': 'm.reference', 93 0 : 'event_id': eventId, 94 : }, 95 0 : PollEventContent.endType: {}, 96 : }, 97 : type: PollEventContent.endType, 98 : txid: txid, 99 : ); 100 : }