mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-18 06:57:59 +00:00
Added mailboxes as local data sources (#1022)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
93be539e50
commit
c4400c0ff6
343 files changed
+26051
-3122
No files matched your search
@@ -0,0 +1,139 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Tools.Databases.IndexStore;
|
||||
|
||||
using MailKit;
|
||||
|
||||
using MimeKit;
|
||||
|
||||
namespace AIStudio.Tools.Mail;
|
||||
|
||||
/// <summary>
|
||||
/// Turns what the server reported about a mail into what AI Studio keeps of it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Everything is read from the header block the sync fetches with every mail, not from the
|
||||
/// ENVELOPE of IMAP: a server fills in Sender and Reply-To from From when the mail has neither, and
|
||||
/// the trust check of a later step has to know whether the sender set them.
|
||||
/// </remarks>
|
||||
public static class MailSummaryReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads what tells the mail apart from all others on the server, for MailContentKey.
|
||||
/// </summary>
|
||||
/// <param name="summary">The summary, fetched with its header block.</param>
|
||||
/// <returns>The identity of the mail.</returns>
|
||||
public static MailIdentity ReadIdentity(IMessageSummary summary)
|
||||
{
|
||||
var headers = GetHeaders(summary);
|
||||
return new(
|
||||
summary.EmailId,
|
||||
summary.GMailMessageId,
|
||||
headers[HeaderId.MessageId],
|
||||
MailHeaders.ReadDate(headers),
|
||||
MailHeaders.ReadMailboxes(headers, HeaderId.From).Select(mailbox => mailbox.Address).ToList(),
|
||||
headers[HeaderId.Subject],
|
||||
summary.Size ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts together what MailTextBuilder reads a mail from.
|
||||
/// </summary>
|
||||
/// <param name="summary">The summary, fetched with its header block and its structure.</param>
|
||||
/// <param name="textParts">The text parts of the mail, or null when they were not fetched because the mail is encrypted.</param>
|
||||
/// <returns>The source for MailTextBuilder.</returns>
|
||||
public static MailTextSource ReadTextSource(IMessageSummary summary, MailTextParts? textParts)
|
||||
{
|
||||
var attachmentNames = ReadAttachments(summary)
|
||||
.Select(attachment => attachment.FileName ?? string.Empty)
|
||||
.Where(name => !string.IsNullOrWhiteSpace(name))
|
||||
.ToList();
|
||||
|
||||
return new(GetHeaders(summary), textParts?.HtmlBody, textParts?.TextBody, attachmentNames, MailEncryptionDetection.Detect(summary.Body));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads which attachments a reader of the mail sees, from its structure.
|
||||
/// </summary>
|
||||
/// <param name="summary">The summary, fetched with its structure.</param>
|
||||
/// <returns>The attachments in their order, without the signature of a signed mail, cf. MailAttachmentRules.IsSignature.</returns>
|
||||
public static IReadOnlyList<BodyPartBasic> ReadAttachments(IMessageSummary summary) => summary.Attachments
|
||||
.Where(attachment => !MailAttachmentRules.IsSignature(attachment.ContentType))
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Reads every address of the header block together with the header it comes from.
|
||||
/// </summary>
|
||||
/// <param name="headers">The header block.</param>
|
||||
/// <returns>The addresses, grouped by their header in the order From, Sender, Reply-To, To, Cc, Bcc.</returns>
|
||||
public static IReadOnlyList<MailAddressRecord> ReadAddresses(HeaderList headers)
|
||||
{
|
||||
(HeaderId HeaderId, MailAddressRole Role)[] roles =
|
||||
[
|
||||
(HeaderId.From, MailAddressRole.FROM),
|
||||
(HeaderId.Sender, MailAddressRole.SENDER),
|
||||
(HeaderId.ReplyTo, MailAddressRole.REPLY_TO),
|
||||
(HeaderId.To, MailAddressRole.TO),
|
||||
(HeaderId.Cc, MailAddressRole.CC),
|
||||
(HeaderId.Bcc, MailAddressRole.BCC),
|
||||
];
|
||||
|
||||
return roles
|
||||
.SelectMany(role => MailHeaders.ReadMailboxes(headers, role.HeaderId).Select(mailbox => new MailAddressRecord(
|
||||
role.Role,
|
||||
MailTextNormalization.NormalizeHeaderValue(mailbox.Address),
|
||||
MailTextNormalization.NormalizeHeaderValue(mailbox.Name))))
|
||||
.Where(address => address.Address.Length > 0)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the flags AI Studio keeps of a mail.
|
||||
/// </summary>
|
||||
/// <param name="flags">The flags the server reported, or null when it reported none.</param>
|
||||
/// <returns>The flags.</returns>
|
||||
public static MailFlags ReadFlags(MessageFlags? flags)
|
||||
{
|
||||
var value = flags ?? MessageFlags.None;
|
||||
return new(value.HasFlag(MessageFlags.Seen), value.HasFlag(MessageFlags.Flagged), value.HasFlag(MessageFlags.Answered));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the header block out the way the server delivered it, encoded words and all.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is what the trust check reads, and it needs the header block as it is: the decoded
|
||||
/// text of a header hides whether it was encoded, and how.
|
||||
/// </remarks>
|
||||
/// <param name="headers">The header block.</param>
|
||||
/// <returns>The header block as text.</returns>
|
||||
public static string ReadHeaderBlock(HeaderList headers)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
headers.WriteTo(FormatOptions.Default, stream);
|
||||
return Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hashes the mail as the server delivered it, independent of how AI Studio turns it into text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A mail on an IMAP server never changes, so its header block, its structure and its size are
|
||||
/// enough to tell it from any other mail, without fetching the whole of it.
|
||||
/// </remarks>
|
||||
/// <param name="summary">The summary, fetched with its header block and its structure.</param>
|
||||
/// <returns>The hash in hex.</returns>
|
||||
public static string ComputeMailHash(IMessageSummary summary)
|
||||
{
|
||||
var source = string.Join('\n',
|
||||
ReadHeaderBlock(GetHeaders(summary)),
|
||||
summary.Body?.ToString() ?? string.Empty,
|
||||
(summary.Size ?? 0).ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(source)));
|
||||
}
|
||||
|
||||
private static HeaderList GetHeaders(IMessageSummary summary) => summary.Headers ?? throw new InvalidOperationException("The mail was fetched without its header block.");
|
||||
}
|
||||
Reference in new issue
Block a user