LCOV - code coverage report
Current view: top level - lib/msc_extensions/msc_3381_polls - poll_event_extension.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 69.4 % 62 43
Test Date: 2025-11-10 08:03:53 Functions: - 0 0

            Line data    Source code
       1              : import 'package:collection/collection.dart';
       2              : 
       3              : import 'package:matrix/matrix.dart';
       4              : 
       5              : extension PollEventExtension on Event {
       6            2 :   PollEventContent get parsedPollEventContent {
       7            4 :     assert(type == PollEventContent.startType);
       8            4 :     return PollEventContent.fromJson(content);
       9              :   }
      10              : 
      11              :   /// Returns a Map of user IDs to a Set of answer IDs.
      12            2 :   Map<String, Set<String>> getPollResponses(Timeline timeline) {
      13            4 :     assert(type == PollEventContent.startType);
      14            6 :     final aggregatedEvents = timeline.aggregatedEvents[eventId]
      15            2 :             ?[RelationshipTypes.reference]
      16            2 :         ?.toList();
      17            4 :     if (aggregatedEvents == null || aggregatedEvents.isEmpty) return {};
      18              :     aggregatedEvents
      19            8 :         .removeWhere((event) => event.type != PollEventContent.responseType);
      20              : 
      21            2 :     final responses = <String, Event>{};
      22              : 
      23            2 :     final endPollEvent = _getEndPollEvent(timeline);
      24              : 
      25            4 :     for (final event in aggregatedEvents) {
      26              :       // Ignore older responses if we already have a newer one:
      27            4 :       final existingEvent = responses[event.senderId];
      28              :       if (existingEvent != null &&
      29            0 :           existingEvent.originServerTs.isAfter(event.originServerTs)) {
      30              :         continue;
      31              :       }
      32              :       // Ignore all responses sent **after** the poll end event:
      33              :       if (endPollEvent != null &&
      34            0 :           event.originServerTs.isAfter(endPollEvent.originServerTs)) {
      35              :         continue;
      36              :       }
      37            4 :       responses[event.senderId] = event;
      38              :     }
      39            2 :     return responses.map(
      40            4 :       (userId, event) => MapEntry(
      41              :         userId,
      42            2 :         event.content
      43            2 :                 .tryGetMap<String, Object?>(PollEventContent.responseType)
      44            2 :                 ?.tryGetList<String>('answers')
      45            2 :                 ?.toSet() ??
      46              :             {},
      47              :       ),
      48              :     );
      49              :   }
      50              : 
      51            2 :   Event? _getEndPollEvent(Timeline timeline) {
      52            4 :     assert(type == PollEventContent.startType);
      53              :     final aggregatedEvents =
      54            8 :         timeline.aggregatedEvents[eventId]?[RelationshipTypes.reference];
      55            2 :     if (aggregatedEvents == null || aggregatedEvents.isEmpty) return null;
      56              : 
      57            2 :     final redactPowerLevel = (room
      58            2 :             .getState(EventTypes.RoomPowerLevels)
      59            0 :             ?.content
      60            0 :             .tryGet<int>('redact') ??
      61              :         50);
      62              : 
      63            2 :     return aggregatedEvents.firstWhereOrNull(
      64            2 :       (event) {
      65            2 :         if (event.content
      66            2 :                 .tryGetMap<String, Object?>(PollEventContent.endType) ==
      67              :             null) {
      68              :           return false;
      69              :         }
      70              : 
      71              :         // If a m.poll.end event is received from someone other than the poll
      72              :         //creator or user with permission to redact other's messages in the
      73              :         //room, the event must be ignored by clients due to being invalid.
      74            6 :         if (event.senderId == senderId ||
      75            0 :             event.senderFromMemoryOrFallback.powerLevel >= redactPowerLevel) {
      76              :           return true;
      77              :         }
      78            0 :         Logs().w(
      79            0 :           'Ignore poll end event form user without permission ${event.senderId}',
      80              :         );
      81              :         return false;
      82              :       },
      83              :     );
      84              :   }
      85              : 
      86            2 :   bool getPollHasBeenEnded(Timeline timeline) =>
      87            2 :       _getEndPollEvent(timeline) != null;
      88              : 
      89            2 :   Future<String?> answerPoll(
      90              :     List<String> answerIds, {
      91              :     String? txid,
      92              :   }) {
      93            4 :     if (type != PollEventContent.startType) {
      94            0 :       throw Exception('Event is not a poll.');
      95              :     }
      96            4 :     if (answerIds.length >
      97            6 :         parsedPollEventContent.pollStartContent.maxSelections) {
      98            0 :       throw Exception('Selected more answers than allowed in this poll.');
      99              :     }
     100            4 :     return room.sendEvent(
     101            2 :       {
     102            2 :         'm.relates_to': {
     103              :           'rel_type': RelationshipTypes.reference,
     104            2 :           'event_id': eventId,
     105              :         },
     106            2 :         PollEventContent.responseType: {'answers': answerIds},
     107              :       },
     108              :       type: PollEventContent.responseType,
     109              :       txid: txid,
     110              :     );
     111              :   }
     112              : 
     113            0 :   Future<String?> endPoll({String? txid}) {
     114            0 :     if (type != PollEventContent.startType) {
     115            0 :       throw Exception('Event is not a poll.');
     116              :     }
     117            0 :     if (senderId != room.client.userID) {
     118            0 :       throw Exception('You can not end a poll created by someone else.');
     119              :     }
     120            0 :     return room.sendEvent(
     121            0 :       {
     122            0 :         'm.relates_to': {
     123              :           'rel_type': RelationshipTypes.reference,
     124            0 :           'event_id': eventId,
     125              :         },
     126            0 :         PollEventContent.endType: {},
     127              :       },
     128              :       type: PollEventContent.endType,
     129              :       txid: txid,
     130              :     );
     131              :   }
     132              : }
        

Generated by: LCOV version 2.0-1