Skip to content

Commit 6ae36e2

Browse files
committed
Add support for REDACT
References: ircv3/ircv3-specifications#524
1 parent d8d8afe commit 6ae36e2

File tree

6 files changed

+76
-7
lines changed

6 files changed

+76
-7
lines changed

lib/client.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ Set<String> _getDefaultCaps(ConnectParams params) {
105105
'draft/chathistory',
106106
'draft/extended-monitor',
107107
'draft/pre-away',
108+
'draft/message-redaction',
108109
'draft/read-marker',
109110

110111
'soju.im/bouncer-networks',

lib/client_controller.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,17 @@ class ClientController {
773773
}).then((unreadCount) {
774774
buffer.unreadCount = unreadCount;
775775
});
776+
case 'REDACT':
777+
var target = msg.params[0];
778+
var msgid = msg.params[1];
779+
780+
var buffer = _bufferList.get(target, network);
781+
if (buffer == null) {
782+
break;
783+
}
784+
785+
// TODO: handle REDACT in a chathistory batch
786+
return _handleRedact(buffer, msgid);
776787
case RPL_MONONLINE:
777788
case RPL_MONOFFLINE:
778789
var online = msg.cmd == RPL_MONONLINE;
@@ -905,6 +916,18 @@ class ClientController {
905916
}
906917
}
907918

919+
Future<void> _handleRedact(BufferModel buffer, String msgid) async {
920+
var msg = await _db.fetchMessageByNetworkMsgid(buffer.id, msgid);
921+
if (msg == null) {
922+
log.print('Received REDACT for unknown msgid "$msgid"');
923+
return;
924+
}
925+
msg.redacted = true;
926+
await _db.storeMessages([msg]);
927+
928+
buffer.redactMessage(msgid);
929+
}
930+
908931
Future<void> _handleBouncerNetworksBatch(ClientBatch batch) async {
909932
for (var msg in batch.messages) {
910933
await _handleBouncerNetwork(msg);

lib/database.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class MessageEntry {
198198
final String? networkMsgid;
199199
final int buffer;
200200
final String raw;
201+
bool redacted;
201202

202203
IrcMessage? _msg;
203204
DateTime? _dateTime;
@@ -209,21 +210,24 @@ class MessageEntry {
209210
'network_msgid': networkMsgid,
210211
'buffer': buffer,
211212
'raw': raw,
213+
'redacted': redacted ? 1 : 0,
212214
};
213215
}
214216

215217
MessageEntry(IrcMessage msg, this.buffer) :
216218
time = msg.tags['time'] ?? formatIrcTime(DateTime.now()),
217219
networkMsgid = msg.tags['msgid'],
218220
raw = msg.toString(),
221+
redacted = false,
219222
_msg = msg;
220223

221224
MessageEntry.fromMap(Map<String, dynamic> m) :
222225
id = m['id'] as int,
223226
time = m['time'] as String,
224227
networkMsgid = m['network_msgid'] as String?,
225228
buffer = m['buffer'] as int,
226-
raw = m['raw'] as String;
229+
raw = m['raw'] as String,
230+
redacted = m['redacted'] == 1;
227231

228232
IrcMessage get msg {
229233
_msg ??= IrcMessage.parse(raw);
@@ -435,6 +439,7 @@ const _schema = [
435439
network_msgid TEXT,
436440
buffer INTEGER NOT NULL,
437441
raw TEXT NOT NULL,
442+
redacted INTEGER NOT NULL DEFAULT 0,
438443
FOREIGN KEY (buffer) REFERENCES Buffer(id) ON DELETE CASCADE
439444
)
440445
''',
@@ -538,6 +543,7 @@ const _migrations = [
538543
)
539544
''',
540545
'CREATE INDEX index_reaction_reply_network_msgid on Reaction(reply_network_msgid)',
546+
'ALTER TABLE Message ADD COLUMN redacted INTEGER NOT NULL DEFAULT 0',
541547
];
542548

543549
class DB {
@@ -784,6 +790,11 @@ class DB {
784790
return messages;
785791
}
786792

793+
Future<MessageEntry?> fetchMessageByNetworkMsgid(int buffer, String msgid) async {
794+
var messages = await fetchMessageSetByNetworkMsgid(buffer, [msgid]);
795+
return messages[msgid];
796+
}
797+
787798
Future<void> storeMessages(List<MessageEntry> entries) async {
788799
await _db.transaction((txn) async {
789800
await Future.wait(entries.map((entry) async {

lib/models.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,16 @@ class BufferModel extends ChangeNotifier {
547547
notifyListeners();
548548
}
549549

550+
void redactMessage(String msgid) {
551+
var msg = _messagesByNetworkMsgid[msgid];
552+
if (msg == null) {
553+
return;
554+
}
555+
556+
msg.entry.redacted = true;
557+
notifyListeners();
558+
}
559+
550560
void populateMessageHistory(List<MessageModel> l) {
551561
// The messages passed here must be already sorted by the caller, and
552562
// must always come before the existing messages

lib/page/buffer.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -972,13 +972,23 @@ class _MessageItem extends StatelessWidget {
972972
);
973973
}
974974

975-
body = stripAnsiFormatting(body);
975+
TextSpan bodyTextSpan;
976+
if (entry.redacted) {
977+
bodyTextSpan = TextSpan(
978+
text: 'This message has been deleted.',
979+
style: TextStyle(fontStyle: FontStyle.italic),
980+
);
981+
} else {
982+
body = stripAnsiFormatting(body);
983+
bodyTextSpan = linkify(context, body, linkStyle: linkStyle);
984+
}
985+
976986
content = [
977987
if (isFirstInGroup) senderTextSpan,
978988
if (isFirstInGroup) TextSpan(text: '\n'),
979989
if (replyChip != null) replyChip,
980990
if (replyChip != null) WidgetSpan(child: SizedBox(width: 5, height: 5)),
981-
linkify(context, body, linkStyle: linkStyle),
991+
bodyTextSpan,
982992
];
983993

984994
if (prefs.linkPreview) {

lib/widget/message_sheet.dart

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
55
import '../ansi.dart';
66
import '../client.dart';
77
import '../client_controller.dart';
8+
import '../irc.dart';
89
import '../models.dart';
910
import '../page/buffer.dart';
1011

@@ -34,19 +35,23 @@ class MessageSheet extends StatelessWidget {
3435

3536
@override
3637
Widget build(BuildContext context) {
37-
var sender = message.msg.source!.name;
38+
var ircMsg = message.msg;
39+
var sender = ircMsg.source!.name;
3840
var client = context.read<Client>();
41+
var isOwn = client.isMyNick(sender);
42+
// TODO: we can redact if we are channel operator too
43+
var canRedact = client.caps.enabled.contains('draft/message-redaction') && ircMsg.tags['msgid'] != null && isOwn && !message.entry.redacted;
3944

4045
return Column(mainAxisSize: MainAxisSize.min, children: [
41-
if (onReply != null && !client.isMyNick(sender)) ListTile(
46+
if (onReply != null && !isOwn) ListTile(
4247
title: Text('Reply'),
4348
leading: Icon(Icons.reply),
4449
onTap: () {
4550
Navigator.pop(context);
4651
onReply!();
4752
},
4853
),
49-
if (!client.isMyNick(sender)) ListTile(
54+
if (!isOwn) ListTile(
5055
title: Text('Message $sender'),
5156
leading: Icon(Icons.chat_bubble),
5257
onTap: () {
@@ -59,14 +64,23 @@ class MessageSheet extends StatelessWidget {
5964
title: Text('Copy'),
6065
leading: Icon(Icons.content_copy),
6166
onTap: () async {
62-
var body = stripAnsiFormatting(message.msg.params[1]);
67+
var body = stripAnsiFormatting(ircMsg.params[1]);
6368
var text = '<$sender> $body';
6469
await Clipboard.setData(ClipboardData(text: text));
6570
if (context.mounted) {
6671
Navigator.pop(context);
6772
}
6873
},
6974
),
75+
if (canRedact) ListTile(
76+
title: Text('Delete'),
77+
leading: Icon(Icons.delete),
78+
onTap: () async {
79+
var buffer = context.read<BufferModel>();
80+
client.send(IrcMessage('REDACT', [buffer.name, ircMsg.tags['msgid']!]));
81+
Navigator.pop(context);
82+
},
83+
),
7084
]);
7185
}
7286
}

0 commit comments

Comments
 (0)