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 : }
|