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:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/gomuks_commands.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/helpers/extensions/get_localpart.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";
|
||||
|
||||
class BotCommandsController(final String roomId)
|
||||
extends Notifier<IList<BotCommand>> {
|
||||
extends AsyncNotifier<IList<BotCommand>> {
|
||||
@override
|
||||
IList<BotCommand> build() => ref.watch(
|
||||
RoomsController.provider.select((rooms) {
|
||||
final room = rooms[roomId];
|
||||
if (room == null) return const .empty();
|
||||
Future<IList<BotCommand>> build() async {
|
||||
final gomuksCommands = await ref.watch(
|
||||
GomuksCommandsController.provider.future,
|
||||
);
|
||||
|
||||
Content? membership(String userId) =>
|
||||
switch (room.state[EventType.membership.type]?[userId]) {
|
||||
final rowId? => room.events[rowId]?.content,
|
||||
null => null,
|
||||
};
|
||||
final botCommands = ref.watch(
|
||||
RoomsController.provider.select((rooms) {
|
||||
final room = rooms[roomId];
|
||||
if (room == null) return const IList<BotCommand>.empty();
|
||||
|
||||
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
|
||||
.sortedBy((command) => command.description.command)
|
||||
.toIList();
|
||||
}),
|
||||
);
|
||||
Content? membership(String userId) =>
|
||||
switch (room.state[EventType.membership.type]?[userId]) {
|
||||
final rowId? => room.events[rowId]?.content,
|
||||
null => null,
|
||||
};
|
||||
|
||||
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>(
|
||||
BotCommandsController.new,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import "package:nexus/controllers/client_state.dart";
|
|||
import "package:nexus/controllers/settings.dart";
|
||||
import "package:nexus/helpers/extensions/gomuks_buffer.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/capabilities.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) =>
|
||||
_sendCommand("redact_event", report.toJson());
|
||||
|
||||
Future<Event> sendMessage(SendMessageRequest request) async =>
|
||||
Event.fromJson(await _sendCommand("send_message", request.toJson()));
|
||||
/// Null for gomuks' own commands that don't reply.
|
||||
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 =>
|
||||
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
|
||||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
.new({
|
||||
info.roomId: .new(
|
||||
events: .new({event.rowId: event}),
|
||||
clientSticky: .new({event.rowId}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
addSent(event);
|
||||
}
|
||||
|
||||
/// Shows [event], which was just sent, until it comes down sync.
|
||||
void addSent(Event? event) {
|
||||
if (event case final event?) {
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
.new({
|
||||
info.roomId: .new(
|
||||
events: .new({event.rowId: event}),
|
||||
clientSticky: .new({event.rowId}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeReaction(
|
||||
|
|
@ -356,7 +363,6 @@ class RoomChatController(final ChatParams info)
|
|||
Content content, {
|
||||
bool shouldMention = true,
|
||||
int? repliedEventRowId,
|
||||
IList<String> mentionedUserIds = const .empty(),
|
||||
}) async {
|
||||
final repliedEvent = repliedEventRowId == null
|
||||
? null
|
||||
|
|
@ -375,7 +381,6 @@ class RoomChatController(final ChatParams info)
|
|||
content: content,
|
||||
mentions: Mentions(
|
||||
userIds: [
|
||||
...mentionedUserIds,
|
||||
if (shouldMention && repliedEvent != null) repliedEvent.sender,
|
||||
].toIList(),
|
||||
),
|
||||
|
|
@ -383,16 +388,7 @@ class RoomChatController(final ChatParams info)
|
|||
),
|
||||
);
|
||||
|
||||
ref
|
||||
.read(RoomsController.provider.notifier)
|
||||
.update(
|
||||
.new({
|
||||
info.roomId: .new(
|
||||
events: .new({event.rowId: event}),
|
||||
clientSticky: .new({event.rowId}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
addSent(event);
|
||||
}
|
||||
|
||||
/// Sends [image] as a sticker, as a reply to [repliedEventRowId] if set.
|
||||
|
|
@ -437,13 +433,32 @@ class RoomChatController(final ChatParams info)
|
|||
Future<void> sendBotCommand(
|
||||
BotCommandDraft draft, {
|
||||
int? repliedEventRowId,
|
||||
}) => sendWithReply(
|
||||
.message,
|
||||
BotCommandMessageContent(body: draft.body, command: draft.input),
|
||||
shouldMention: false,
|
||||
mentionedUserIds: [draft.command.sender].lock,
|
||||
repliedEventRowId: repliedEventRowId,
|
||||
);
|
||||
}) async {
|
||||
final repliedEvent = repliedEventRowId == null
|
||||
? null
|
||||
: ref
|
||||
.read(RoomsController.provider)[info.roomId]
|
||||
?.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.
|
||||
Future<void> sendPollReference(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class const BotCommand({
|
|||
"htmlmd",
|
||||
});
|
||||
|
||||
static const gomuksSender = "@gomuks";
|
||||
|
||||
String get prefix => "/${description.command}";
|
||||
String get about => [botName, ?description.description].join(" · ");
|
||||
|
||||
|
|
|
|||
|
|
@ -300,7 +300,8 @@ class const Composer(
|
|||
|
||||
final botCommands = isEditing || hasReplacement || attachment != null
|
||||
? const IList<BotCommand>.empty()
|
||||
: ref.watch(BotCommandsController.provider(roomId));
|
||||
: ref.watch(BotCommandsController.provider(roomId)).value ??
|
||||
const .empty();
|
||||
|
||||
void pickBotCommand(BotCommand command) {
|
||||
ref
|
||||
|
|
|
|||
Loading…
Reference in a new issue