Skip to content

Commit 536cbb7

Browse files
committed
message: Implement selfCanDeleteMessage
Related: #1548
1 parent e5b43df commit 536cbb7

File tree

3 files changed

+625
-1
lines changed

3 files changed

+625
-1
lines changed

lib/model/message.dart

Lines changed: 117 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,122 @@ 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) {
91+
assert(false); // TODO(log)
92+
return true;
93+
}
94+
95+
final ZulipStream? channel;
96+
if (message is StreamMessage) {
97+
channel = streams[message.streamId];
98+
if (channel == null) {
99+
assert(false); // TODO(log)
100+
return true;
101+
}
102+
} else {
103+
channel = null;
104+
}
105+
106+
if (channel != null && channel.isArchived) {
107+
return false;
108+
}
109+
110+
// TODO(#1850) really the default should be `role:administrators`:
111+
// https://github.com/zulip/zulip-flutter/pull/1842#discussion_r2331362461
112+
if (realmCanDeleteAnyMessageGroup != null
113+
&& selfHasPermissionForGroupSetting(realmCanDeleteAnyMessageGroup!,
114+
GroupSettingType.realm, 'can_delete_any_message_group')) {
115+
return true;
116+
}
117+
118+
if (channel != null) {
119+
if (channel.canDeleteAnyMessageGroup != null
120+
&& selfHasPermissionForGroupSetting(channel.canDeleteAnyMessageGroup!,
121+
GroupSettingType.stream, 'can_delete_any_message_group')) {
122+
return true;
123+
}
124+
}
125+
126+
final sender = getUser(message.senderId);
127+
if (sender == null) return false;
128+
129+
if (!(
130+
sender.userId == selfUserId
131+
|| (sender.isBot && sender.botOwnerId == selfUserId)
132+
)) {
133+
return false;
134+
}
135+
136+
// Web returns false here for local-echoed message objects;
137+
// that's impossible here because `message` can't be an [OutboxMessage]
138+
// (it's a [Message] from [MessageStore.messages]).
139+
140+
if (realmCanDeleteOwnMessageGroup != null) {
141+
if (!selfHasPermissionForGroupSetting(realmCanDeleteOwnMessageGroup!,
142+
GroupSettingType.realm, 'can_delete_own_message_group')) {
143+
if (channel == null) {
144+
// i.e. this is a DM
145+
return false;
146+
}
147+
148+
if (
149+
channel.canDeleteOwnMessageGroup == null
150+
|| !selfHasPermissionForGroupSetting(channel.canDeleteOwnMessageGroup!,
151+
GroupSettingType.stream, 'can_delete_own_message_group')
152+
) {
153+
return false;
154+
}
155+
}
156+
} else if (realmDeleteOwnMessagePolicy != null) {
157+
if (!_selfPassesLegacyDeleteMessagePolicy(messageId, atDate: atDate)) {
158+
return false;
159+
}
160+
} else {
161+
assert(false); // TODO(log)
162+
return true;
163+
}
164+
165+
if (realmMessageContentDeleteLimitSeconds == null) {
166+
// i.e., no limit
167+
return true;
168+
}
169+
return atDate.millisecondsSinceEpoch ~/ 1000 - message.timestamp
170+
<= realmMessageContentDeleteLimitSeconds!;
171+
}
172+
173+
bool _selfPassesLegacyDeleteMessagePolicy(int messageId, {required DateTime atDate}) {
174+
assert(realmDeleteOwnMessagePolicy != null);
175+
final role = selfUser.role;
176+
177+
// (Could early-return true on [UserRole.unknown],
178+
// but pre-291 servers shouldn't be giving us an unknown role.)
179+
180+
switch (realmDeleteOwnMessagePolicy!) {
181+
case RealmDeleteOwnMessagePolicy.everyone:
182+
return true;
183+
case RealmDeleteOwnMessagePolicy.members:
184+
return role.isAtLeast(UserRole.member);
185+
case RealmDeleteOwnMessagePolicy.fullMembers: {
186+
if (!role.isAtLeast(UserRole.member)) return false;
187+
if (role == UserRole.member) {
188+
return hasPassedWaitingPeriod(selfUser, byDate: atDate);
189+
}
190+
return true;
191+
}
192+
case RealmDeleteOwnMessagePolicy.moderators:
193+
return role.isAtLeast(UserRole.moderator);
194+
case RealmDeleteOwnMessagePolicy.admins:
195+
return role.isAtLeast(UserRole.administrator);
196+
}
197+
}
81198
}
82199

83200
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)