Skip to content

Commit 41ba775

Browse files
committed
message: Implement selfCanDeleteMessage
1 parent 0c708e5 commit 41ba775

File tree

3 files changed

+636
-1
lines changed

3 files changed

+636
-1
lines changed

lib/model/message.dart

Lines changed: 116 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,11 @@ 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 [byDate].
84+
///
85+
/// For a value of [byDate], use [ZulipBinding.instance.utcNow].
86+
bool selfCanDeleteMessage(int messageId, {required DateTime byDate});
8187
}
8288

8389
mixin ProxyMessageStore on MessageStore {
@@ -122,6 +128,9 @@ mixin ProxyMessageStore on MessageStore {
122128
({String originalRawContent, String newContent}) takeFailedMessageEdit(int messageId) {
123129
return messageStore.takeFailedMessageEdit(messageId);
124130
}
131+
@override
132+
bool selfCanDeleteMessage(int messageId, {required DateTime byDate}) =>
133+
messageStore.selfCanDeleteMessage(messageId, byDate: byDate);
125134

126135
@override
127136
Set<MessageListView> get debugMessageListViews => messageStore.debugMessageListViews;
@@ -376,6 +385,113 @@ class MessageStoreImpl extends HasChannelStore with MessageStore, _OutboxMessage
376385
);
377386
}
378387

388+
@override
389+
bool selfCanDeleteMessage(int messageId, {required DateTime byDate}) {
390+
// Compare web's message_delete.get_deletability.
391+
392+
final message = messages[messageId];
393+
if (message == null) return false; // TODO(log)
394+
395+
final ZulipStream? channel;
396+
if (message is StreamMessage) {
397+
channel = streams[message.streamId];
398+
// TODO(log) if channel null here?
399+
} else {
400+
channel = null;
401+
}
402+
403+
if (channel != null && channel.isArchived == true) {
404+
return false;
405+
}
406+
407+
if (realmCanDeleteAnyMessageGroup != null
408+
&& selfHasPermissionForGroupSetting(realmCanDeleteAnyMessageGroup!,
409+
GroupSettingType.realm, 'can_delete_any_message_group')) {
410+
return true;
411+
}
412+
413+
if (channel != null) {
414+
if (channel.canDeleteAnyMessageGroup != null
415+
&& selfHasPermissionForGroupSetting(channel.canDeleteAnyMessageGroup!,
416+
GroupSettingType.stream, 'can_delete_any_message_group')) {
417+
return true;
418+
}
419+
}
420+
421+
final sender = getUser(message.senderId);
422+
if (sender == null) return false;
423+
424+
if (
425+
sender.userId != selfUserId
426+
&& !(sender.isBot && sender.botOwnerId == selfUserId)
427+
) {
428+
return false;
429+
}
430+
431+
// Web returns false here for local-echoed message objects;
432+
// that's impossible here because `message` can't be an [OutboxMessage]
433+
// (it's a [Message] from [MessageStore.messages]).
434+
435+
if (realmCanDeleteOwnMessageGroup != null) {
436+
if (!selfHasPermissionForGroupSetting(realmCanDeleteOwnMessageGroup!,
437+
GroupSettingType.realm, 'can_delete_own_message_group')) {
438+
if (channel == null) {
439+
// i.e. this is a DM
440+
return false;
441+
}
442+
443+
if (
444+
channel.canDeleteOwnMessageGroup == null
445+
|| !selfHasPermissionForGroupSetting(channel.canDeleteOwnMessageGroup!,
446+
GroupSettingType.stream, 'can_delete_own_message_group')
447+
) {
448+
return false;
449+
}
450+
}
451+
}
452+
453+
if (
454+
realmDeleteOwnMessagePolicy != null
455+
&& !_selfPassesLegacyDeleteMessagePolicy(messageId, byDate: byDate)
456+
) {
457+
return false;
458+
}
459+
460+
if (realmMessageContentDeleteLimitSeconds == null) {
461+
// i.e., no limit
462+
return true;
463+
}
464+
465+
return byDate.millisecondsSinceEpoch ~/ 1000 - message.timestamp
466+
<= realmMessageContentDeleteLimitSeconds!;
467+
}
468+
469+
bool _selfPassesLegacyDeleteMessagePolicy(int messageId, {required DateTime byDate}) {
470+
assert(realmDeleteOwnMessagePolicy != null);
471+
final role = selfUser.role;
472+
473+
// (Could early-return true on [UserRole.unknown],
474+
// but pre-291 servers shouldn't be giving us an unknown role.)
475+
476+
switch (realmDeleteOwnMessagePolicy!) {
477+
case RealmDeleteOwnMessagePolicy.members:
478+
return true;
479+
case RealmDeleteOwnMessagePolicy.admins:
480+
return role.isAtLeast(UserRole.administrator);
481+
case RealmDeleteOwnMessagePolicy.fullMembers: {
482+
if (!role.isAtLeast(UserRole.member)) return false;
483+
if (role == UserRole.member) {
484+
return hasPassedWaitingPeriod(selfUser, byDate: byDate);
485+
}
486+
return true;
487+
}
488+
case RealmDeleteOwnMessagePolicy.moderators:
489+
return role.isAtLeast(UserRole.moderator);
490+
case RealmDeleteOwnMessagePolicy.everyone:
491+
return true;
492+
}
493+
}
494+
379495
void handleUserTopicEvent(UserTopicEvent event) {
380496
for (final view in _messageListViews) {
381497
view.handleUserTopicEvent(event);

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)