← All source files

V14 / fad4e1a110e5

EmailOperations.cs

Lilith/Core/Email/EmailOperations.cs

Unchanged from the preceding version
CS
using System.Text.Json;
namespace Lilith.Core.Email;

internal static class EmailOperations
{
    internal static readonly HashSet<string> Names = ["email_check_operation", "email_accounts_operation", "email_greeting_operation", "email_batch_operation", "email_read_operation", "email_assessment_operation", "email_spam_operation", "email_sections_operation"];
    internal static async Task<string> ExecuteAsync(ProviderClient provider, string name, IReadOnlyDictionary<string, string> inputs, CancellationToken ct)
    {
        string Get(string key) => inputs.GetValueOrDefault(key) ?? "";
        string nickname = Get("nickname");
        switch (name)
        {
            case "email_greeting_operation": return await EmailClient.GreetingAsync(ct);
            case "email_accounts_operation": return await EmailClient.RequestAsync("accounts", null, provider.EmailMode, ct);
            case "email_sections_operation": return await EmailClient.SendAsync("sections", new { nickname }, ct);
            case "email_check_operation":
                string batch = await EmailClient.RequestAsync("check", nickname, provider.EmailMode, ct);
                provider.EmailBatches[nickname] = batch;
                return batch;
            case "email_batch_operation":
                string current = provider.EmailBatches.GetValueOrDefault(nickname) ?? await EmailClient.RequestAsync("check", nickname, provider.EmailMode, ct);
                provider.EmailBatches[nickname] = current;
                using (var data = JsonDocument.Parse(current))
                using (var sections = JsonDocument.Parse(await EmailClient.SendAsync("sections", new { nickname }, ct)))
                {
                    var root = data.RootElement;
                    var blocked = sections.RootElement.GetProperty("blockedSenders").EnumerateArray().Select(x => x.GetString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
                    var all = root.GetProperty("messages").EnumerateArray().ToArray();
                    return JsonSerializer.Serialize(new { nickname, validity = root.GetProperty("validity").GetUInt32(), source = root.GetProperty("source").GetString(), moreAvailable = root.GetProperty("moreAvailable").GetBoolean(), batchCount = all.Length, selectedCount = all.Count(x => !blocked.Contains(x.GetProperty("senderAddress").GetString())), omittedSpam = all.Count(x => blocked.Contains(x.GetProperty("senderAddress").GetString())), messages = all.Where(x => !blocked.Contains(x.GetProperty("senderAddress").GetString())).Select(x => x.Clone()).ToArray() });
                }
            case "email_read_operation":
                try { return await EmailClient.SendAsync("read", new { nickname, validity = uint.Parse(Get("validity")), uid = uint.Parse(Get("uid")) }, ct); }
                catch (Exception ex) when ((ex is IOException or HttpRequestException or TaskCanceledException) && !ct.IsCancellationRequested)
                { return JsonSerializer.Serialize(new { status = "unreadable", nickname, error = "Message could not be read; do not invent a summary." }); }
            case "email_assessment_operation":
                try
                {
                    using (var assessment = JsonDocument.Parse(Get("assessment")))
                    using (var message = JsonDocument.Parse(Get("message")))
                    {
                        if (message.RootElement.GetProperty("status").GetString() != "completed") return JsonSerializer.Serialize(new { readable = false, error = "Message unavailable" });
                        string section = assessment.RootElement.GetProperty("section").GetString() ?? "";
                        string summary = assessment.RootElement.GetProperty("summary").GetString() ?? "";
                        if (section is not ("inbox" or "shopping") || string.IsNullOrWhiteSpace(summary) || summary.Length > 1800) throw new InvalidDataException("Invalid email assessment.");
                        var mail = message.RootElement;
                        await EmailClient.SendAsync("section", new { nickname, validity = mail.GetProperty("validity").GetUInt32(), uid = mail.GetProperty("message").GetProperty("uid").GetUInt32(), section }, ct);
                        return JsonSerializer.Serialize(new { readable = true, section, summary, message = mail.GetProperty("message").Clone(), source = mail.GetProperty("source").GetString(), truncated = mail.GetProperty("truncated").GetBoolean() });
                    }
                }
                catch (Exception ex) when (ex is JsonException or InvalidDataException or KeyNotFoundException or InvalidOperationException)
                { return JsonSerializer.Serialize(new { readable = false, error = "Reader assessment was invalid; this message has no reliable briefing summary." }); }
            case "email_spam_operation":
                if (Get("blocked") is not ("true" or "false")) throw new ArgumentException("blocked must be true or false.");
                if (!System.Net.Mail.MailAddress.TryCreate(Get("sender"), out var sender) || sender.Address != Get("sender")) throw new ArgumentException("Specify an exact sender address.");
                string id = Guid.NewGuid().ToString("N");
                provider.PendingEmailRule = (id, nickname, sender.Address, Get("blocked") == "true");
                return $"Proposed local sender rule for {nickname}: {(Get("blocked") == "true" ? "block" : "unblock")} {sender.Address}. Applies to existing and future messages from this exact sender in this mailbox; Gmail stays unchanged. To apply it, enter /email-confirm {id}.";
            default: throw new ArgumentException("Unknown email operation.");
        }
    }
}