nexusbot/bin/nexusbot.dart
2025-08-19 18:42:19 -04:00

185 lines
6.7 KiB
Dart

import "dart:io";
import "package:cli_tools/config.dart";
import "package:enough_mail/enough_mail.dart" as mail;
import "package:markdown/markdown.dart";
import "package:matrix/matrix.dart";
import "package:nexusbot/controllers/client_controller.dart";
import "package:nexusbot/controllers/mail_client_controller.dart";
import "package:nexusbot/controllers/registration_controller.dart";
import "package:nexusbot/controllers/settings_controller.dart";
import "package:nexusbot/models/registration.dart";
import "package:riverpod/riverpod.dart";
import "package:shelf/shelf.dart";
import "package:shelf/shelf_io.dart";
import "package:shelf_router/shelf_router.dart";
void main(List<String> argsRaw) async {
final parser = ConfigParser()
..addOption("socket", mandatory: true)
..addOption("homeserver", mandatory: true)
..addOption("failureUri", mandatory: true)
..addOption("successUri", mandatory: true)
..addOption("name", mandatory: true)
..addOption("adminName", mandatory: true)
..addOption("adminRoom", mandatory: true)
..addOption("email", mandatory: true)
..addOption("emailAlias", mandatory: false)
..addOption("mailName", mandatory: true)
..addOption("mailDomain", mandatory: true)
..addOption("smtpPasswordFile", mandatory: true)
..addOption("botPasswordFile", mandatory: true)
..addOption("inviteTo");
final container = ProviderContainer();
container
.read(SettingsController.provider.notifier)
.set(parser.parse(argsRaw));
final client = await container.read(ClientController.provider.future);
client.onTimelineEvent.stream.listen((event) async {
final settings = container.read(SettingsController.provider)!;
if (event.room.canonicalAlias != settings.adminRoom) return;
if (event.senderId.startsWith("@${settings.name}:")) return;
switch (event.type) {
case EventTypes.Reaction:
if ((event.content["m.relates_to"] as Map<String, dynamic>)["key"] !=
"") {
return;
}
final parentEvent = await client.getOneRoomEvent(
event.roomId!,
event.relationshipEventId!,
);
if (!parentEvent.senderId.startsWith("@${settings.name}:")) return;
final registration = Registration.fromJson(parentEvent.content);
container
.read(RegistrationController.provider.notifier)
.add(registration);
await event.room.sendTextEvent(
"!admin create-user ${registration.username}",
);
break;
case EventTypes.Message:
if (!event.senderId.startsWith("@${settings.adminName}:")) break;
final results = RegExp(
r"@(?<username>[a-zA-Z0-9._-]+):[^\s]+.*?password:\s+(?<password>\S+)",
).firstMatch(event.body);
if (results == null) return;
final registration = container
.read(RegistrationController.provider)
.firstWhere(
(account) => account.username == results.namedGroup("username"),
);
final password = results.namedGroup("password")!;
final reactionEvent = await event.room.sendReaction(
event.eventId,
"✉️ Sending...",
);
await container
.read(MailClientController.provider.notifier)
.sendMessage(
plainText:
"""Your registration for Federated Nexus has been accepted! Your credentials are:
Username: ${registration.username}.
Password: $password
If you have any questions, check out our documentation: https://federated.nexus/services/matrix/.
If you have any issues, reply to this email.""",
markdown:
"""# Your registration for Federated Nexus has been accepted!
## Your credentials are:
- ### Username: ${registration.username}.
- ### Password: $password
If you have any questions, check out [our documentation](https://federated.nexus/services/matrix/).
If you have any issues, reply to this email.""",
subject:
"Your registration for Federated Nexus has been accepted!",
to: mail.MailAddress(registration.username, registration.email),
);
await event.room.redactEvent(reactionEvent!);
await event.room.sendReaction(event.eventId, "✉️ Sent!");
if (settings.inviteTo != null) {
client
.getRoomByAlias(settings.inviteTo!)!
.invite(
RegExp(
r"(?<userid>@[a-zA-Z0-9._-]+:[^\s]+)",
).firstMatch(event.body)!.namedGroup("userid")!,
);
}
}
});
final handler = const Pipeline()
.addMiddleware(logRequests())
.addHandler(
(Router()..post("/", (Request request) async {
final settings = container.read(SettingsController.provider)!;
try {
final body = await request.readAsString();
final registration = Registration.fromJson(
Uri.splitQueryString(
body,
).map((key, value) => MapEntry(key, value.toLowerCase())),
);
final client = await container.read(
ClientController.provider.future,
);
final room = client.getRoomByAlias(settings.adminRoom)!;
final message =
"""# Registration request
- Username: `${registration.username}`
- Email: `${registration.email}`""";
final event = await room.sendEvent({
"body": message,
"msgtype": MessageTypes.Text,
"format": "org.matrix.custom.html",
"formatted_body": markdownToHtml(message),
...registration.toJson(),
});
await room.sendReaction(event!, "");
return Response.found(settings.successUri);
} catch (error, stackTrace) {
final message =
"""An error occurred for this request:
${await request.readAsString()}
$error
$stackTrace
""";
try {
final room = client.getRoomByAlias(settings.adminRoom)!;
await room.sendTextEvent(message);
} catch (_) {
print(message);
}
return Response.found(settings.failureUri);
}
}))
.call,
);
final server = HttpServer.listenOn(
await ServerSocket.bind(
InternetAddress(
container.read(SettingsController.provider)!.socket,
type: InternetAddressType.unix,
),
0,
),
);
serveRequests(server, handler);
print("Bot listening at ${server.address.address}");
}