Skip to content

Commit b25b8b1

Browse files
committed
message: Implement selfCanDeleteMessage
Related: zulip#1548
1 parent e5b43df commit b25b8b1

File tree

3 files changed

+621
-1
lines changed

3 files changed

+621
-1
lines changed

lib/model/message.dart

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
77

88
import '../api/exception.dart';
99
import '../api/model/events.dart';
10+
import '../api/model/initial_snapshot.dart';
1011
import '../api/model/model.dart';
1112
import '../api/route/messages.dart';
1213
import '../log.dart';
@@ -78,6 +79,118 @@ mixin MessageStore on ChannelStore {
7879
/// Should only be called when there is a failed request,
7980
/// per [getEditMessageErrorStatus].
8081
({String originalRawContent, String newContent}) takeFailedMessageEdit(int messageId);
82+
83+
/// Whether the user has permission to delete a message, as of [atDate].
84+
///
85+
/// For a value of [atDate], use [ZulipBinding.instance.utcNow].
86+
bool selfCanDeleteMessage(int messageId, {required DateTime atDate}) {
87+
// Compare web's message_delete.get_deletability.
88+
89+
final message = messages[messageId];
90+
if (message == null) return false; // TODO(log)
91+
92+
final ZulipStream? channel;
93+
if (message is StreamMessage) {
94+
channel = streams[message.streamId];
95+
if (channel == null) {
96+
assert(false); // TODO(log)
97+
return true;
98+
}
99+
} else {
100+
channel = null;
101+
}
102+
103+
if (channel != null && channel.isArchived) {
104+
return false;
105+
}
106+
107+
// TODO really the default should be `role:administrators`:
108+
// https://github.com/zulip/zulip-flutter/pull/1842#discussion_r2331362461
109+
if (realmCanDeleteAnyMessageGroup != null
110+
&& selfHasPermissionForGroupSetting(realmCanDeleteAnyMessageGroup!,
111+
GroupSettingType.realm, 'can_delete_any_message_group')) {
112+
return true;
113+
}
114+
115+
if (channel != null) {
116+
if (channel.canDeleteAnyMessageGroup != null
117+
&& selfHasPermissionForGroupSetting(channel.canDeleteAnyMessageGroup!,
118+
GroupSettingType.stream, 'can_delete_any_message_group')) {
119+
return true;
120+
}
121+
}
122+
123+
final sender = getUser(message.senderId);
124+
if (sender == null) return false;
125+
126+
if (!(
127+
sender.userId == selfUserId
128+
|| (sender.isBot && sender.botOwnerId == selfUserId)
129+
)) {
130+
return false;
131+
}
132+
133+
// Web returns false here for local-echoed message objects;
134+
// that's impossible here because `message` can't be an [OutboxMessage]
135+
// (it's a [Message] from [MessageStore.messages]).
136+
137+
if (realmCanDeleteOwnMessageGroup != null) {
138+
if (!selfHasPermissionForGroupSetting(realmCanDeleteOwnMessageGroup!,
139+
GroupSettingType.realm, 'can_delete_own_message_group')) {
140+
if (channel == null) {
141+
// i.e. this is a DM
142+
return false;
143+
}
144+
145+
if (
146+
channel.canDeleteOwnMessageGroup == null
147+
|| !selfHasPermissionForGroupSetting(channel.canDeleteOwnMessageGroup!,
148+
GroupSettingType.stream, 'can_delete_own_message_group')
149+
) {
150+
return false;
151+
}
152+
}
153+
} else if (realmDeleteOwnMessagePolicy != null) {
154+
if (!_selfPassesLegacyDeleteMessagePolicy(messageId, atDate: atDate)) {
155+
return false;
156+
}
157+
} else {
158+
assert(false); // TODO(log)
159+
return true;
160+
}
161+
162+
if (realmMessageContentDeleteLimitSeconds == null) {
163+
// i.e., no limit
164+
return true;
165+
}
166+
return atDate.millisecondsSinceEpoch ~/ 1000 - message.timestamp
167+
<= realmMessageContentDeleteLimitSeconds!;
168+
}
169+
170+
bool _selfPassesLegacyDeleteMessagePolicy(int messageId, {required DateTime atDate}) {
171+
assert(realmDeleteOwnMessagePolicy != null);
172+
final role = selfUser.role;
173+
174+
// (Could early-return true on [UserRole.unknown],
175+
// but pre-291 servers shouldn't be giving us an unknown role.)
176+
177+
switch (realmDeleteOwnMessagePolicy!) {
178+
case RealmDeleteOwnMessagePolicy.everyone:
179+
case RealmDeleteOwnMessagePolicy.members:
180+
return true;
181+
case RealmDeleteOwnMessagePolicy.fullMembers: {
182+
if (!role.isAtLeast(UserRole.member)) return false;
183+
if (role == UserRole.member) {
184+
return hasPassedWaitingPeriod(selfUser, byDate: atDate);
185+
}
186+
return true;
187+
}
188+
case RealmDeleteOwnMessagePolicy.moderators:
189+
return role.isAtLeast(UserRole.moderator);
190+
case RealmDeleteOwnMessagePolicy.admins:
191+
return role.isAtLeast(UserRole.administrator);
192+
}
193+
}
81194
}
82195

83196
mixin ProxyMessageStore on MessageStore {

test/example_data.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ User user({
271271
String? dateJoined,
272272
bool? isActive,
273273
bool? isBot,
274+
int? botOwnerId,
274275
UserRole? role,
275276
String? avatarUrl,
276277
Map<int, ProfileFieldUserData>? profileData,
@@ -286,7 +287,7 @@ User user({
286287
isActive: isActive ?? true,
287288
isBot: isBot ?? false,
288289
botType: null,
289-
botOwnerId: null,
290+
botOwnerId: botOwnerId,
290291
role: role ?? UserRole.member,
291292
timezone: 'UTC',
292293
avatarUrl: avatarUrl,

0 commit comments

Comments
 (0)