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: 70.7 % 58 41
Test Date: 2025-01-14 12:37:39 Functions: - 0 0

            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            6 :     final maxSelection = parsedPollEventContent.pollStartContent.maxSelections;
      20              : 
      21            4 :     aggregatedEvents.removeWhere((event) {
      22            4 :       if (event.type != PollEventContent.responseType) return true;
      23              : 
      24              :       // Votes with timestamps after the poll has closed are ignored, as if they
      25              :       // never happened.
      26            6 :       if (originServerTs.isAfter(event.originServerTs)) {
      27            0 :         Logs().d('Ignore poll answer which came after poll was closed.');
      28              :         return true;
      29              :       }
      30              : 
      31            2 :       final answers = event.content
      32            2 :           .tryGetMap<String, Object?>(PollEventContent.responseType)
      33            2 :           ?.tryGetList<String>('answers');
      34              :       if (answers == null) {
      35            0 :         Logs().d('Ignore poll answer with now valid answer IDs');
      36              :         return true;
      37              :       }
      38            4 :       if (answers.length > maxSelection) {
      39            0 :         Logs().d(
      40            0 :           'Ignore poll answer with ${answers.length} while only $maxSelection are allowed.',
      41              :         );
      42              :         return true;
      43              :       }
      44              :       return false;
      45              :     });
      46              : 
      47              :     // Sort by date so only the users most recent vote is used in the end, even
      48              :     // if it is invalid.
      49              :     aggregatedEvents
      50            2 :         .sort((a, b) => a.originServerTs.compareTo(b.originServerTs));
      51              : 
      52            4 :     for (final event in aggregatedEvents) {
      53            2 :       final answers = event.content
      54            2 :               .tryGetMap<String, Object?>(PollEventContent.responseType)
      55            2 :               ?.tryGetList<String>('answers') ??
      56            0 :           [];
      57            6 :       responses[event.senderId] = answers.toSet();
      58              :     }
      59              :     return responses;
      60              :   }
      61              : 
      62            2 :   bool getPollHasBeenEnded(Timeline timeline) {
      63            4 :     assert(type == PollEventContent.startType);
      64            8 :     final aggregatedEvents = timeline.aggregatedEvents[eventId]?['m.reference'];
      65            2 :     if (aggregatedEvents == null || aggregatedEvents.isEmpty) return false;
      66              : 
      67            2 :     final redactPowerLevel = (room
      68            2 :             .getState(EventTypes.RoomPowerLevels)
      69            0 :             ?.content
      70            0 :             .tryGet<int>('redact') ??
      71              :         50);
      72              : 
      73            2 :     return aggregatedEvents.any(
      74            2 :       (event) {
      75            2 :         if (event.content
      76            2 :                 .tryGetMap<String, Object?>(PollEventContent.endType) ==
      77              :             null) {
      78              :           return false;
      79              :         }
      80              : 
      81              :         // If a m.poll.end event is received from someone other than the poll
      82              :         //creator or user with permission to redact other's messages in the
      83              :         //room, the event must be ignored by clients due to being invalid.
      84            6 :         if (event.senderId == senderId ||
      85            0 :             event.senderFromMemoryOrFallback.powerLevel >= redactPowerLevel) {
      86              :           return true;
      87              :         }
      88            0 :         Logs().w(
      89            0 :           'Ignore poll end event form user without permission ${event.senderId}',
      90              :         );
      91              :         return false;
      92              :       },
      93              :     );
      94              :   }
      95              : 
      96            2 :   Future<String?> answerPoll(
      97              :     List<String> answerIds, {
      98              :     String? txid,
      99              :   }) {
     100            6 :     final maxSelection = parsedPollEventContent.pollStartContent.maxSelections;
     101            4 :     if (answerIds.length > maxSelection) {
     102            0 :       throw Exception(
     103            0 :         'Can not add ${answerIds.length} answers while max selection is $maxSelection',
     104              :       );
     105              :     }
     106            4 :     return room.sendEvent(
     107            2 :       {
     108            2 :         'm.relates_to': {
     109              :           'rel_type': 'm.reference',
     110            2 :           'event_id': eventId,
     111              :         },
     112            2 :         PollEventContent.responseType: {'answers': answerIds},
     113              :       },
     114              :       type: PollEventContent.responseType,
     115              :       txid: txid,
     116              :     );
     117              :   }
     118              : 
     119            0 :   Future<String?> endPoll({String? txid}) => room.sendEvent(
     120            0 :         {
     121            0 :           'm.relates_to': {
     122              :             'rel_type': 'm.reference',
     123            0 :             'event_id': eventId,
     124              :           },
     125            0 :           PollEventContent.endType: {},
     126              :         },
     127              :         type: PollEventContent.endType,
     128              :         txid: txid,
     129              :       );
     130              : }
        

Generated by: LCOV version 2.0-1