add internal gomuks command support
This commit is contained in:
parent
3fc33a5ac9
commit
8650c44266
7 changed files with 168 additions and 79 deletions
2
gomuks
2
gomuks
|
|
@ -1 +1 @@
|
||||||
Subproject commit 081d5df14be6bf7131e955461207e26da7ad23c9
|
Subproject commit e745b6dc1baa45ebb62dadf577f54e6ffb2f486f
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/gomuks_commands.dart";
|
||||||
import "package:nexus/controllers/rooms.dart";
|
import "package:nexus/controllers/rooms.dart";
|
||||||
import "package:nexus/helpers/extensions/get_localpart.dart";
|
import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||||
import "package:nexus/models/bot_command.dart";
|
import "package:nexus/models/bot_command.dart";
|
||||||
|
|
@ -10,56 +11,66 @@ import "package:nexus/models/content/membership.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
|
|
||||||
class BotCommandsController(final String roomId)
|
class BotCommandsController(final String roomId)
|
||||||
extends Notifier<IList<BotCommand>> {
|
extends AsyncNotifier<IList<BotCommand>> {
|
||||||
@override
|
@override
|
||||||
IList<BotCommand> build() => ref.watch(
|
Future<IList<BotCommand>> build() async {
|
||||||
RoomsController.provider.select((rooms) {
|
final gomuksCommands = await ref.watch(
|
||||||
final room = rooms[roomId];
|
GomuksCommandsController.provider.future,
|
||||||
if (room == null) return const .empty();
|
);
|
||||||
|
|
||||||
Content? membership(String userId) =>
|
final botCommands = ref.watch(
|
||||||
switch (room.state[EventType.membership.type]?[userId]) {
|
RoomsController.provider.select((rooms) {
|
||||||
final rowId? => room.events[rowId]?.content,
|
final room = rooms[roomId];
|
||||||
null => null,
|
if (room == null) return const IList<BotCommand>.empty();
|
||||||
};
|
|
||||||
|
|
||||||
return (room.state[EventType.botCommandDescription.type]?.values ??
|
Content? membership(String userId) =>
|
||||||
const [])
|
switch (room.state[EventType.membership.type]?[userId]) {
|
||||||
.map(
|
final rowId? => room.events[rowId]?.content,
|
||||||
(rowId) => switch (room.events[rowId]) {
|
null => null,
|
||||||
Event(
|
};
|
||||||
redactedBy: null,
|
|
||||||
:final sender,
|
|
||||||
content: final BotCommandDescriptionContent description,
|
|
||||||
)
|
|
||||||
when description.isValid &&
|
|
||||||
!BotCommand.builtIn.contains(
|
|
||||||
description.command.split(" ").first,
|
|
||||||
) =>
|
|
||||||
switch (membership(sender)) {
|
|
||||||
MembershipContent(
|
|
||||||
status: .join,
|
|
||||||
:final displayName,
|
|
||||||
:final avatarUrl,
|
|
||||||
) =>
|
|
||||||
BotCommand(
|
|
||||||
sender: sender,
|
|
||||||
botName: displayName ?? sender.localpart,
|
|
||||||
botAvatar: avatarUrl,
|
|
||||||
description: description,
|
|
||||||
),
|
|
||||||
_ => null,
|
|
||||||
},
|
|
||||||
_ => null,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.nonNulls
|
|
||||||
.sortedBy((command) => command.description.command)
|
|
||||||
.toIList();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
static final provider = NotifierProvider.autoDispose
|
return (room.state[EventType.botCommandDescription.type]?.values ??
|
||||||
|
const [])
|
||||||
|
.map(
|
||||||
|
(rowId) => switch (room.events[rowId]) {
|
||||||
|
Event(
|
||||||
|
redactedBy: null,
|
||||||
|
:final sender,
|
||||||
|
content: final BotCommandDescriptionContent description,
|
||||||
|
)
|
||||||
|
when description.isValid &&
|
||||||
|
!BotCommand.builtIn.contains(
|
||||||
|
description.command.split(" ").first,
|
||||||
|
) =>
|
||||||
|
switch (membership(sender)) {
|
||||||
|
MembershipContent(
|
||||||
|
status: .join,
|
||||||
|
:final displayName,
|
||||||
|
:final avatarUrl,
|
||||||
|
) =>
|
||||||
|
BotCommand(
|
||||||
|
sender: sender,
|
||||||
|
botName: displayName ?? sender.localpart,
|
||||||
|
botAvatar: avatarUrl,
|
||||||
|
description: description,
|
||||||
|
),
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.nonNulls
|
||||||
|
.toIList();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return gomuksCommands
|
||||||
|
.addAll(botCommands)
|
||||||
|
.sortedBy((command) => command.description.command)
|
||||||
|
.toIList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.autoDispose
|
||||||
.family<BotCommandsController, IList<BotCommand>, String>(
|
.family<BotCommandsController, IList<BotCommand>, String>(
|
||||||
BotCommandsController.new,
|
BotCommandsController.new,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import "package:nexus/controllers/client_state.dart";
|
||||||
import "package:nexus/controllers/settings.dart";
|
import "package:nexus/controllers/settings.dart";
|
||||||
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
|
import "package:nexus/models/content/bot_command_description.dart";
|
||||||
import "package:nexus/models/account.dart";
|
import "package:nexus/models/account.dart";
|
||||||
import "package:nexus/models/capabilities.dart";
|
import "package:nexus/models/capabilities.dart";
|
||||||
import "package:nexus/models/content/message.dart";
|
import "package:nexus/models/content/message.dart";
|
||||||
|
|
@ -240,8 +241,23 @@ class ClientController(final String? accountId) extends AsyncNotifier<int> {
|
||||||
Future<void> redactEvent(RedactEventRequest report) =>
|
Future<void> redactEvent(RedactEventRequest report) =>
|
||||||
_sendCommand("redact_event", report.toJson());
|
_sendCommand("redact_event", report.toJson());
|
||||||
|
|
||||||
Future<Event> sendMessage(SendMessageRequest request) async =>
|
/// Null for gomuks' own commands that don't reply.
|
||||||
Event.fromJson(await _sendCommand("send_message", request.toJson()));
|
Future<Event?> sendMessage(SendMessageRequest request) async =>
|
||||||
|
switch (await _sendCommand("send_message", request.toJson())) {
|
||||||
|
final Map<String, dynamic> json => .fromJson(json),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The MSC4391 definitions of gomuks' own commands.
|
||||||
|
Future<IList<BotCommandDescriptionContent>> getCommandDefinitions() async =>
|
||||||
|
switch (await _sendCommand("get_command_definitions")) {
|
||||||
|
final List definitions => .new(
|
||||||
|
definitions.whereType<Map<String, dynamic>>().map(
|
||||||
|
BotCommandDescriptionContent.fromJson,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_ => const .empty(),
|
||||||
|
};
|
||||||
|
|
||||||
Future<Event> sendEvent(SendEventRequest request) async =>
|
Future<Event> sendEvent(SendEventRequest request) async =>
|
||||||
Event.fromJson(await _sendCommand("send_event", request.toJson()));
|
Event.fromJson(await _sendCommand("send_event", request.toJson()));
|
||||||
|
|
|
||||||
44
lib/controllers/gomuks_commands.dart
Normal file
44
lib/controllers/gomuks_commands.dart
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/client.dart";
|
||||||
|
import "package:nexus/models/bot_command.dart";
|
||||||
|
|
||||||
|
class GomuksCommandsController extends AsyncNotifier<IList<BotCommand>> {
|
||||||
|
static const unsupported = ISetConst({
|
||||||
|
// Needs gomuks web
|
||||||
|
"devtools",
|
||||||
|
"upgraderoom",
|
||||||
|
|
||||||
|
// Needs attachment stuff
|
||||||
|
"myroomavatar",
|
||||||
|
"globalavatar",
|
||||||
|
"roomavatar",
|
||||||
|
|
||||||
|
// Already in Nexus' UI with a better UX
|
||||||
|
"poll",
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<IList<BotCommand>> build() async =>
|
||||||
|
(await ref
|
||||||
|
.read(ClientController.provider.notifier)
|
||||||
|
.getCommandDefinitions())
|
||||||
|
.where(
|
||||||
|
(description) =>
|
||||||
|
description.isValid &&
|
||||||
|
!unsupported.contains(description.command),
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(description) => BotCommand(
|
||||||
|
sender: BotCommand.gomuksSender,
|
||||||
|
botName: "gomuks",
|
||||||
|
description: description,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toIList();
|
||||||
|
|
||||||
|
static final provider =
|
||||||
|
AsyncNotifierProvider<GomuksCommandsController, IList<BotCommand>>(
|
||||||
|
GomuksCommandsController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -281,16 +281,23 @@ class RoomChatController(final ChatParams info)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
ref
|
addSent(event);
|
||||||
.read(RoomsController.provider.notifier)
|
}
|
||||||
.update(
|
|
||||||
.new({
|
/// Shows [event], which was just sent, until it comes down sync.
|
||||||
info.roomId: .new(
|
void addSent(Event? event) {
|
||||||
events: .new({event.rowId: event}),
|
if (event case final event?) {
|
||||||
clientSticky: .new({event.rowId}),
|
ref
|
||||||
),
|
.read(RoomsController.provider.notifier)
|
||||||
}),
|
.update(
|
||||||
);
|
.new({
|
||||||
|
info.roomId: .new(
|
||||||
|
events: .new({event.rowId: event}),
|
||||||
|
clientSticky: .new({event.rowId}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeReaction(
|
Future<void> removeReaction(
|
||||||
|
|
@ -356,7 +363,6 @@ class RoomChatController(final ChatParams info)
|
||||||
Content content, {
|
Content content, {
|
||||||
bool shouldMention = true,
|
bool shouldMention = true,
|
||||||
int? repliedEventRowId,
|
int? repliedEventRowId,
|
||||||
IList<String> mentionedUserIds = const .empty(),
|
|
||||||
}) async {
|
}) async {
|
||||||
final repliedEvent = repliedEventRowId == null
|
final repliedEvent = repliedEventRowId == null
|
||||||
? null
|
? null
|
||||||
|
|
@ -375,7 +381,6 @@ class RoomChatController(final ChatParams info)
|
||||||
content: content,
|
content: content,
|
||||||
mentions: Mentions(
|
mentions: Mentions(
|
||||||
userIds: [
|
userIds: [
|
||||||
...mentionedUserIds,
|
|
||||||
if (shouldMention && repliedEvent != null) repliedEvent.sender,
|
if (shouldMention && repliedEvent != null) repliedEvent.sender,
|
||||||
].toIList(),
|
].toIList(),
|
||||||
),
|
),
|
||||||
|
|
@ -383,16 +388,7 @@ class RoomChatController(final ChatParams info)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
ref
|
addSent(event);
|
||||||
.read(RoomsController.provider.notifier)
|
|
||||||
.update(
|
|
||||||
.new({
|
|
||||||
info.roomId: .new(
|
|
||||||
events: .new({event.rowId: event}),
|
|
||||||
clientSticky: .new({event.rowId}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends [image] as a sticker, as a reply to [repliedEventRowId] if set.
|
/// Sends [image] as a sticker, as a reply to [repliedEventRowId] if set.
|
||||||
|
|
@ -437,13 +433,32 @@ class RoomChatController(final ChatParams info)
|
||||||
Future<void> sendBotCommand(
|
Future<void> sendBotCommand(
|
||||||
BotCommandDraft draft, {
|
BotCommandDraft draft, {
|
||||||
int? repliedEventRowId,
|
int? repliedEventRowId,
|
||||||
}) => sendWithReply(
|
}) async {
|
||||||
.message,
|
final repliedEvent = repliedEventRowId == null
|
||||||
BotCommandMessageContent(body: draft.body, command: draft.input),
|
? null
|
||||||
shouldMention: false,
|
: ref
|
||||||
mentionedUserIds: [draft.command.sender].lock,
|
.read(RoomsController.provider)[info.roomId]
|
||||||
repliedEventRowId: repliedEventRowId,
|
?.events[repliedEventRowId];
|
||||||
);
|
|
||||||
|
final event = await ref
|
||||||
|
.read(ClientController.provider.notifier)
|
||||||
|
.sendMessage(
|
||||||
|
.new(
|
||||||
|
roomId: info.roomId,
|
||||||
|
text: "",
|
||||||
|
baseContent: BotCommandMessageContent(
|
||||||
|
body: draft.body,
|
||||||
|
command: draft.input,
|
||||||
|
),
|
||||||
|
mentions: .new(userIds: [draft.command.sender].lock),
|
||||||
|
relation: repliedEvent == null
|
||||||
|
? null
|
||||||
|
: .new(eventId: repliedEvent.eventId, relationType: .reply),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
addSent(event);
|
||||||
|
}
|
||||||
|
|
||||||
/// Completes once the event has come down sync.
|
/// Completes once the event has come down sync.
|
||||||
Future<void> sendPollReference(
|
Future<void> sendPollReference(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class const BotCommand({
|
||||||
"htmlmd",
|
"htmlmd",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static const gomuksSender = "@gomuks";
|
||||||
|
|
||||||
String get prefix => "/${description.command}";
|
String get prefix => "/${description.command}";
|
||||||
String get about => [botName, ?description.description].join(" · ");
|
String get about => [botName, ?description.description].join(" · ");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,8 @@ class const Composer(
|
||||||
|
|
||||||
final botCommands = isEditing || hasReplacement || attachment != null
|
final botCommands = isEditing || hasReplacement || attachment != null
|
||||||
? const IList<BotCommand>.empty()
|
? const IList<BotCommand>.empty()
|
||||||
: ref.watch(BotCommandsController.provider(roomId));
|
: ref.watch(BotCommandsController.provider(roomId)).value ??
|
||||||
|
const .empty();
|
||||||
|
|
||||||
void pickBotCommand(BotCommand command) {
|
void pickBotCommand(BotCommand command) {
|
||||||
ref
|
ref
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue