← All source files

V16 / 6df58cd3ed86

Program.cs

Services/EmailService/Program.cs

Unchanged from the preceding version
CS
using Lilith.EmailService;
using Microsoft.AspNetCore.DataProtection;

if (!OperatingSystem.IsWindows()) throw new PlatformNotSupportedException("V13 account storage uses Windows user-protected encryption.");
string root = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LilithEmailService");
Directory.CreateDirectory(root);
var protection = DataProtectionProvider.Create(new DirectoryInfo(Path.Combine(root, "keys")), configuration => { configuration.SetApplicationName("LilithEmailService.V1"); if (OperatingSystem.IsWindows()) configuration.ProtectKeysWithDpapi(); });
var protector = protection.CreateProtector("mailbox-secrets-and-state-v1");
var store = new MailStore(root, protector, new AccountMailReader(root));
// Prevent simultaneous setup or serving processes from replacing each other's state.
using var lease = new FileStream(Path.Combine(root, "service.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
if (args is ["dummy-add", var dummyNickname])
{
    await store.AddDummyAsync(dummyNickname, CancellationToken.None);
    Console.WriteLine($"Created local dummy mailbox '{dummyNickname}' with 12 sample messages (8 unread). Not a Google account; no network connection or real password.");
    return;
}
if (args is ["dummy-briefing-add", var briefingNickname])
{
    await store.AddDummyAsync(briefingNickname, CancellationToken.None, briefing: true);
    Console.WriteLine("Created three local dummy emails with bodies: shopping, a review request and a promotion. No sender is automatically blocked. Not Gmail; existing accounts were not replaced.");
    return;
}
if (args is ["dummy-arrive", var arrivalNickname])
{
    await store.AddDummyMessageAsync(arrivalNickname, CancellationToken.None);
    Console.WriteLine("Added one local dummy arrival. No real email was sent.");
    return;
}
if (args is ["account-add", var nickname])
{
    Console.Write("Google email address: "); string address = Console.ReadLine()?.Trim() ?? "";
    if (Console.IsInputRedirected) throw new InvalidOperationException("Enter the app password interactively; redirected secret input is disabled.");
    Console.Write("Google app password (hidden): ");
    var password = new System.Text.StringBuilder();
    while (true) { var key = Console.ReadKey(true); if (key.Key == ConsoleKey.Enter) break; if (key.Key == ConsoleKey.Backspace) { if (password.Length > 0) password.Length--; } else if (!char.IsControl(key.KeyChar) && password.Length < 256) password.Append(key.KeyChar); }
    Console.WriteLine();
    await store.AddAsync(nickname, address, password.ToString(), CancellationToken.None); password.Clear();
    Console.WriteLine("Account stored. No messages read. Restart the service to use it.");
    return;
}
if (args is ["accounts"])
{
    foreach (var account in await store.AccountsAsync(CancellationToken.None)) Console.WriteLine(account.Nickname + " / " + account.Provider);
    return;
}
if (args.Length != 0 && args is not ["serve"]) throw new ArgumentException("EmailService account-add <nickname> | dummy-add <nickname> | dummy-briefing-add <nickname> | dummy-arrive <nickname> | accounts | serve");
string token = Environment.GetEnvironmentVariable("LILITH_EMAIL_API_TOKEN") ?? "";
int port = int.TryParse(Environment.GetEnvironmentVariable("LILITH_EMAIL_PORT"), out int selected) ? selected : 5183;
await using var app = LocalMailApi.Create(store, token, port);
Console.WriteLine($"Email service listening on http://127.0.0.1:{port}. Inbox reads only. Press Ctrl+C to stop.");
await app.RunAsync();