您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1068 行
50KB

  1. using MailKit;
  2. using MailKit.Net.Imap;
  3. using MailKit.Search;
  4. using MailKit.Security;
  5. using MimeKit;
  6. using Services.ReActAgent;
  7. using System.Collections.ObjectModel;
  8. using System.Text;
  9. using System.Text.Json;
  10. using System.Text.Json.Serialization;
  11. using System.Text.RegularExpressions;
  12. using ToolsServices;
  13. namespace Services
  14. {
  15. public static class EmailService
  16. {
  17. public enum eTypeSensibilite
  18. {
  19. Ton,
  20. Posture,
  21. Couleur,
  22. NiveauDetail
  23. }
  24. #region Variables privées
  25. private static readonly string NomFichierParamsMail = FichiersInternesService.ParamsMail;// "paramsMail.txt";
  26. private static readonly string NomFichierMails = FichiersInternesService.ListeMails;// "listeMails.json";
  27. private static ObservableCollection<EmailMessage>? Emails;
  28. private static ReActAgent.ReActAgent _ReActAgent = new();
  29. #endregion
  30. #region Méthodes publiques
  31. public static List<string> LoadSensibilites(eTypeSensibilite eSensibilite)
  32. {
  33. LoggerService.LogInfo($"EmailService.LoadSensibilites : {eSensibilite}");
  34. var nomFile = "";
  35. var contenu = "";
  36. switch(eSensibilite)
  37. {
  38. case eTypeSensibilite.Ton:
  39. nomFile = FichiersInternesService.EmailsSensibiliteTon;
  40. contenu = "Formel;Professionnel neutre;Chaleureux / convivial;Urgent / alerte;Didactique / explicatif;Persuasif / engageant";
  41. break;
  42. case eTypeSensibilite.Posture:
  43. nomFile = FichiersInternesService.EmailsSensibilitePosture;
  44. contenu = "D’égal à égal;Hiérarchique (vers le haut ou vers le bas);De cadrage (donner des instructions, poser un cadre);De demandeur;De médiateur / facilitateur;D’alerte / vigilance;De soutien / collaboratif";
  45. break;
  46. case eTypeSensibilite.Couleur:
  47. nomFile = FichiersInternesService.EmailsSensibiliteCouleur;
  48. contenu = "Politesse (sobre → très formelle);Enthousiasme (positif, motivant);Gravité (sérieux, solennel);Humour (léger, complice);Sarcasme (ironique, risqué)";
  49. break;
  50. case eTypeSensibilite.NiveauDetail:
  51. nomFile = FichiersInternesService.EmailsSensibiliteNiveauDetail;
  52. contenu = "Simple et conçis;simple, objectif clair;structuré et nuancé;précision lexicale et stylistique;stratégie relationnelle autour du mail";
  53. break;
  54. }
  55. var lst = new List<string>();
  56. if (File.Exists(nomFile))
  57. {
  58. string[] lignes = File.ReadAllLines(nomFile);
  59. foreach (string line in lignes)
  60. {
  61. lst.Add(line);
  62. }
  63. }
  64. else
  65. {
  66. lst = new(contenu.Split(";").Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)));
  67. SaveSensibilite(eSensibilite, lst);
  68. }
  69. return lst.OrderBy(x=>x).ToList();
  70. }
  71. public static bool SaveSensibilite(eTypeSensibilite eSensibilite, List<string> lst, string newSensibilite="")
  72. {
  73. LoggerService.LogInfo($"EmailService.SaveSensibilite : {eSensibilite}");
  74. try
  75. {
  76. var nomFile = "";
  77. switch (eSensibilite)
  78. {
  79. case eTypeSensibilite.Ton:
  80. nomFile = FichiersInternesService.EmailsSensibiliteTon;
  81. break;
  82. case eTypeSensibilite.Posture:
  83. nomFile = FichiersInternesService.EmailsSensibilitePosture;
  84. break;
  85. case eTypeSensibilite.Couleur:
  86. nomFile = FichiersInternesService.EmailsSensibiliteCouleur;
  87. break;
  88. case eTypeSensibilite.NiveauDetail:
  89. nomFile = FichiersInternesService.EmailsSensibiliteNiveauDetail;
  90. break;
  91. }
  92. StringBuilder sb = new();
  93. foreach (var s in lst)
  94. {
  95. if(s.Trim() != "")
  96. sb.AppendLine(s.Trim());
  97. }
  98. if (newSensibilite != "")
  99. {
  100. sb.AppendLine(newSensibilite.Trim());
  101. }
  102. var b = TxtService.CreateTextFile(nomFile, sb.ToString());
  103. if(!b)
  104. {
  105. LoggerService.LogError($"EmailService.SaveSensibilite : Erreur lors de la sauvegarde du fichier {nomFile}");
  106. return false;
  107. }
  108. return true;
  109. }
  110. catch (Exception ex)
  111. {
  112. LoggerService.LogError($"EmailService.SaveSensibilite : {ex.Message}");
  113. return false;
  114. }
  115. }
  116. public async static Task<CompteMailUser?> LoadParametresAsync()
  117. {
  118. var p = LoadParametres();
  119. await Task.Delay(1);
  120. return p;
  121. }
  122. public static CompteMailUser? LoadParametres()
  123. {
  124. LoggerService.LogInfo("EmailService.LoadParametres");
  125. CompteMailUser SelectedItem = new();
  126. try
  127. {
  128. if (File.Exists(NomFichierParamsMail))
  129. {
  130. SelectedItem.IsOk = true;
  131. string[] lignes = File.ReadAllLines(NomFichierParamsMail);
  132. if (lignes.Length > 0)
  133. SelectedItem.ServeurImap = DecryptData(lignes[0]);
  134. if (lignes.Length > 1)
  135. SelectedItem.ServeurImapPort = int.Parse(lignes[1]);
  136. if (lignes.Length > 2)
  137. SelectedItem.ServeurSmtp = DecryptData(lignes[2]);
  138. if (lignes.Length > 3)
  139. SelectedItem.ServeurSmtpPort = int.Parse(lignes[3]);
  140. if (lignes.Length > 4)
  141. SelectedItem.UserAdresse = DecryptData(lignes[4]);
  142. if (lignes.Length > 5)
  143. SelectedItem.UserMotPasse = DecryptData(lignes[5]);
  144. if (lignes.Length > 6)
  145. SelectedItem.UserNomPrenom = DecryptData(lignes[6]);
  146. if (lignes.Length > 7)
  147. SelectedItem.UserRole = DecryptData(lignes[7]);
  148. if (lignes.Length > 8)
  149. SelectedItem.UserEntreprise = DecryptData(lignes[8]);
  150. if (lignes.Length > 9)
  151. {
  152. if(int.TryParse(lignes[9], out int delay))
  153. SelectedItem.DelayRefresh = delay;
  154. }
  155. if (lignes.Length > 10)
  156. {
  157. if (int.TryParse(lignes[10], out int delaySentRefresh))
  158. SelectedItem.DelaySentRefresh = delaySentRefresh;
  159. }
  160. if (lignes.Length > 11)
  161. {
  162. if (int.TryParse(lignes[11], out int delaySentRecup))
  163. SelectedItem.DelaySentRecup = delaySentRecup;
  164. }
  165. if (lignes.Length > 12)
  166. {
  167. if (int.TryParse(lignes[12], out int overdueDaysSentRecup))
  168. SelectedItem.OverdueDaysSent = overdueDaysSentRecup;
  169. }
  170. }
  171. return SelectedItem;
  172. }
  173. catch
  174. {
  175. return null;
  176. }
  177. }
  178. public async static Task<bool> SaveParametresAsync(CompteMailUser selectedItem)
  179. {
  180. var b = SaveParametres(selectedItem);
  181. await Task.Delay(1);
  182. return b;
  183. }
  184. public static bool SaveParametres(CompteMailUser selectedItem)
  185. {
  186. LoggerService.LogInfo("EmailService.SaveParametres");
  187. try
  188. {
  189. StringBuilder sb = new();
  190. sb.AppendLine(EncryptData(selectedItem.ServeurImap));
  191. sb.AppendLine(selectedItem.ServeurImapPort.ToString());
  192. sb.AppendLine(EncryptData(selectedItem.ServeurSmtp));
  193. sb.AppendLine(selectedItem.ServeurSmtpPort.ToString());
  194. sb.AppendLine(EncryptData(selectedItem.UserAdresse));
  195. sb.AppendLine(EncryptData(selectedItem.UserMotPasse));
  196. sb.AppendLine(EncryptData(selectedItem.UserNomPrenom));
  197. sb.AppendLine(EncryptData(selectedItem.UserRole));
  198. sb.AppendLine(EncryptData(selectedItem.UserEntreprise));
  199. sb.AppendLine(selectedItem.DelayRefresh.ToString());
  200. sb.AppendLine(selectedItem.DelaySentRefresh.ToString());
  201. sb.AppendLine(selectedItem.DelaySentRecup.ToString());
  202. sb.AppendLine(selectedItem.OverdueDaysSent.ToString());
  203. File.WriteAllText(NomFichierParamsMail, sb.ToString());
  204. selectedItem.IsOk = true;
  205. return true;
  206. }
  207. catch
  208. {
  209. return false;
  210. }
  211. }
  212. public static async Task<ObservableCollection<EmailMessage>?> LireEmailsAsync(ObservableCollection<EmailMessage>? emails)
  213. {
  214. LoggerService.LogInfo("EmailService.LireEmailsAsync");
  215. Emails = emails;
  216. CompteMailUser? CompteMail = LoadParametres();
  217. if (CompteMail == null)
  218. {
  219. return null;
  220. }
  221. return await LoadUnreadEmailsAsync(CompteMail.UserAdresse, CompteMail.UserMotPasse, CompteMail.ServeurImap, CompteMail.ServeurImapPort, CompteMail.UserAdresse);
  222. }
  223. public static async Task<bool> EnvoyerMailAsync(MailKit.UniqueId? id, string destinataire, string sujet, string messageTexte, bool isReponse, string messageOrigineid, string destinataireCc="")
  224. {
  225. LoggerService.LogInfo("EmailService.EnvoyerReponseAsync");
  226. try
  227. {
  228. CompteMailUser? CompteMail = LoadParametres();
  229. if (CompteMail == null)
  230. {
  231. return false;
  232. }
  233. var message = new MimeMessage();
  234. message.From.Add(MailboxAddress.Parse(CompteMail.UserAdresse)); // expéditeur
  235. var dests = destinataire.Split(';');
  236. foreach (var dest in dests)
  237. {
  238. message.To.Add(MailboxAddress.Parse(dest));
  239. }
  240. if (destinataireCc != "")
  241. {
  242. var destsCc = destinataireCc.Split(';');
  243. foreach (var dest in destsCc)
  244. {
  245. message.Cc.Add(MailboxAddress.Parse(dest));
  246. }
  247. }
  248. message.Subject = (isReponse & !sujet.StartsWith("RE:", StringComparison.CurrentCultureIgnoreCase) ? "RE: " + sujet : sujet);
  249. if(isReponse)
  250. {
  251. message.InReplyTo = messageOrigineid;
  252. }
  253. // Version texte brut (fallback)
  254. var plainText = new TextPart("plain")
  255. {
  256. Text = messageTexte
  257. };
  258. // Version HTML avec police Calibri et taille 12pt
  259. // on remplace les sauts de ligne par des paragraphes <p>
  260. var htmlParagraphs = string.Join("",
  261. messageTexte
  262. .Split('\n')
  263. .Where(line => !string.IsNullOrWhiteSpace(line))
  264. .Select(line => $"<p>{System.Net.WebUtility.HtmlEncode(line)}</p>")
  265. );
  266. /*
  267. string signatureHtml = @"
  268. <hr>
  269. <table style=""width: 250px;"">
  270. <tbody>
  271. <tr>
  272. <td style=""width: 96px; text-align: center;border-right-style: solid;border-right-width: 0px;padding-right: 20px;"">
  273. <img src=""data:image/png;base64,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"" alt=""All In IT"" style=""width: 96px; height: 80px;"">
  274. </td>
  275. <td style=""font-size: 14px; line-height: 1.3;padding-left: 15px;border-left-style: solid;border-left-width: 2px;font-family: Helvetica, sans-serif;white-space: nowrap;"">
  276. <h1 style=""margin: 0; font-size: 16px;"">Guillaume TOPENOT</h1>
  277. <p style=""margin: 0;color: #666;"">Manager</p>
  278. <p style=""margin: 0;""><a href=""""></a></p>
  279. </td>
  280. </tr>
  281. </tbody>
  282. </table>";
  283. */
  284. string signatureHtml = "";
  285. var htmlText = new TextPart("html")
  286. {
  287. Text = $"<html><body style='font-family: Calibri, sans-serif; font-size: 12pt;'>{htmlParagraphs+signatureHtml}</body></html>"
  288. };
  289. // Regroupe les deux versions (plain + html)
  290. var alternative = new MultipartAlternative();
  291. alternative.Add(plainText);
  292. alternative.Add(htmlText);
  293. // Ajoute au message
  294. message.Body = alternative;
  295. // --- Envoi via SMTP ---
  296. using var smtp = new MailKit.Net.Smtp.SmtpClient();
  297. await smtp.ConnectAsync(CompteMail.ServeurSmtp, CompteMail.ServeurSmtpPort, SecureSocketOptions.StartTls);
  298. await smtp.AuthenticateAsync(CompteMail.UserAdresse, CompteMail.UserMotPasse);
  299. await smtp.SendAsync(message);
  300. await smtp.DisconnectAsync(true);
  301. if (id != null)
  302. {
  303. var Id = (MailKit.UniqueId)id;
  304. await MarquerMessageCommeLu(Id, CompteMail.UserAdresse, CompteMail.UserMotPasse, CompteMail.ServeurImap, CompteMail.ServeurImapPort);
  305. }
  306. // --- Sauvegarde dans "Sent" via IMAP ---
  307. using var imap = new MailKit.Net.Imap.ImapClient();
  308. await imap.ConnectAsync(CompteMail.ServeurImap, CompteMail.ServeurImapPort, SecureSocketOptions.SslOnConnect);
  309. await imap.AuthenticateAsync(CompteMail.UserAdresse, CompteMail.UserMotPasse);
  310. // Accès à la boîte aux lettres personnelle (root)
  311. var personal = imap.GetFolder(imap.PersonalNamespaces[0]);
  312. // Récupérer le dossier "Sent"
  313. var sentFolder = await personal.GetSubfolderAsync("Sent");
  314. // Si ça ne marche pas (par exemple dossier en français "Envoyés") :
  315. if (sentFolder == null || !sentFolder.Exists)
  316. {
  317. var allFolders = await personal.GetSubfoldersAsync(false);
  318. sentFolder = allFolders.FirstOrDefault(f => f.Name.Equals("Sent", StringComparison.OrdinalIgnoreCase)
  319. || f.Name.Equals("Envoyés", StringComparison.OrdinalIgnoreCase));
  320. }
  321. // Si on a bien trouvé un dossier "Sent" ou équivalent
  322. if (sentFolder != null)
  323. {
  324. await sentFolder.OpenAsync(FolderAccess.ReadWrite);
  325. await sentFolder.AppendAsync(message, MessageFlags.Seen);
  326. }
  327. LoggerService.LogDebug("Réponse par mail envoyée.");
  328. return true;
  329. }
  330. catch (Exception ex)
  331. {
  332. LoggerService.LogError(ex.ToString());
  333. return false;
  334. }
  335. }
  336. public static async Task<bool> MaquerLu(MailKit.UniqueId? id)
  337. {
  338. LoggerService.LogInfo("EmailService.MaquerLu");
  339. CompteMailUser? CompteMail = LoadParametres();
  340. if (CompteMail == null)
  341. {
  342. return false;
  343. }
  344. var b = await MarquerMessageCommeLu(id, CompteMail.UserAdresse, CompteMail.UserMotPasse, CompteMail.ServeurImap, CompteMail.ServeurImapPort);
  345. return b;
  346. }
  347. public static async Task<bool> SauvegarderReponseEmails(EmailMessage item)
  348. {
  349. try
  350. {
  351. LoggerService.LogInfo("EmailService.SauvegarderReponseEmails");
  352. await Task.Delay(1);
  353. if (!File.Exists(NomFichierMails))
  354. return false;
  355. using FileStream fs = File.OpenRead(NomFichierMails);
  356. var items = JsonSerializer.Deserialize<ObservableCollection<EmailMessage>>(fs);
  357. fs.Close();
  358. if (items == null)
  359. {
  360. return false;
  361. }
  362. var mail = items.FirstOrDefault(m => m.UidString == item.UidString);
  363. if (mail != null)
  364. {
  365. mail.Reponse = item.Reponse;
  366. var options = new JsonSerializerOptions { WriteIndented = true };
  367. File.WriteAllText(NomFichierMails, JsonSerializer.Serialize(items, options));
  368. }
  369. return true;
  370. }
  371. catch (Exception ex)
  372. {
  373. LoggerService.LogError($"{ex.Message}");
  374. return false;
  375. }
  376. }
  377. public static void SauvegarderEmails(ObservableCollection<EmailMessage> ListeItems)
  378. {
  379. try
  380. {
  381. LoggerService.LogInfo("EmailService.SauvegarderEmailsAsync");
  382. if (ListeItems == null)
  383. return;
  384. var options = new JsonSerializerOptions
  385. {
  386. WriteIndented = true,
  387. // Ignore les propriétés non sérialisables comme UniqueId si nécessaire
  388. IgnoreReadOnlyProperties = false,
  389. DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
  390. };
  391. if (File.Exists(NomFichierMails))
  392. File.Delete(NomFichierMails);
  393. using FileStream fs = File.Create(NomFichierMails);
  394. JsonSerializer.Serialize(fs, ListeItems, options);
  395. }
  396. catch (Exception ex)
  397. {
  398. LoggerService.LogError(ex.Message);
  399. }
  400. }
  401. public static ObservableCollection<EmailMessage> ChargerEmailsDepuisJson()
  402. {
  403. LoggerService.LogInfo("EmailService.ChargerEmailsDepuisJsonAsync");
  404. if (!File.Exists(NomFichierMails))
  405. return new ObservableCollection<EmailMessage>();
  406. using FileStream fs = File.OpenRead(NomFichierMails);
  407. var items = JsonSerializer.Deserialize<ObservableCollection<EmailMessage>>(fs);
  408. if (items == null)
  409. {
  410. return new ObservableCollection<EmailMessage>();
  411. }
  412. return items;
  413. }
  414. public static async Task<string> SeekByKeyWord(string RechercheString)
  415. {
  416. LoggerService.LogInfo("EmailService.SeekByKeyWord");
  417. var adressEmail = LoadParametres()?.UserAdresse ?? "";
  418. return await RAGService.Search(Domain.Emails, RechercheString, adressEmail);
  419. }
  420. public static bool SaveParametresCheckEmail(int val1, int val2)
  421. {
  422. try
  423. {
  424. StringBuilder sb = new();
  425. sb.AppendLine(val1.ToString());
  426. sb.AppendLine(val2.ToString());
  427. File.WriteAllText(FichiersInternesService.ParamsLancerCheckMails, sb.ToString());
  428. return true;
  429. }
  430. catch (Exception ex)
  431. {
  432. LoggerService.LogError($"EmailService.SaveParametresCheckEmail Erreur : {ex.Message}");
  433. return false;
  434. }
  435. }
  436. public static (int, int) LoadParametresCheckEmail()
  437. {
  438. try
  439. {
  440. int val1 = 0;
  441. int val2 = 0;
  442. if (File.Exists(FichiersInternesService.ParamsLancerCheckMails))
  443. {
  444. string[] lignes = File.ReadAllLines(FichiersInternesService.ParamsLancerCheckMails);
  445. if (lignes.Length > 0)
  446. val1 = int.Parse(lignes[0]);
  447. if (lignes.Length > 1)
  448. val2 = int.Parse(lignes[1]);
  449. }
  450. return (val1,val2);
  451. }
  452. catch (Exception ex)
  453. {
  454. LoggerService.LogError($"EmailService.SaveParametresCheckEmail Erreur : {ex.Message}");
  455. return (0,0);
  456. }
  457. }
  458. public static async Task<(bool, string)> GenererMailAsync(string context, string query)
  459. {
  460. LoggerService.LogInfo($"EmailService.GenererMailAsync");
  461. var paramUser = LoadParametres();
  462. var prompt = PromptService.GetPrompt(PromptService.ePrompt.EmailService_Generer_Mail, query, context, paramUser!.UserNomPrenom, paramUser!.UserRole, paramUser!.UserEntreprise);
  463. LoggerService.LogDebug($"Prompt : {prompt}");
  464. var (textMail, m) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.GenerationMail, true, prompt, "Génération d'email");
  465. LoggerService.LogDebug($"EmailService.GenererMailAsync : Modele : '{m}'\nMail généré : {textMail}");
  466. var b = textMail.Contains("Error", StringComparison.OrdinalIgnoreCase) || textMail.Contains("Exception", StringComparison.OrdinalIgnoreCase);
  467. return (!b, textMail);
  468. }
  469. #endregion
  470. #region Méthodes privées
  471. private static string EncryptData(string clairString)
  472. {
  473. LoggerService.LogDebug("EmailService.EncryptData");
  474. return CryptageService.EncryptData(clairString);
  475. }
  476. private static string DecryptData(string encryptedString)
  477. {
  478. LoggerService.LogDebug("EmailService.DecryptData");
  479. return CryptageService.DecryptData(encryptedString);
  480. }
  481. private static async Task<ObservableCollection<EmailMessage>> LoadUnreadEmailsAsync(string email, string password, string imapServer, int port, string proprietaire)
  482. {
  483. LoggerService.LogInfo("EmailService.LoadUnreadEmailsAsync");
  484. var messages = new ObservableCollection<EmailMessage>();
  485. using var client = new ImapClient();
  486. await client.ConnectAsync(imapServer, port, SecureSocketOptions.SslOnConnect);
  487. await client.AuthenticateAsync(email, password);
  488. var inbox = client.Inbox;
  489. await inbox.OpenAsync(FolderAccess.ReadOnly);
  490. var uids = await inbox.SearchAsync(SearchQuery.NotSeen);
  491. if(Emails != null && Emails.Count > 0)
  492. {
  493. foreach (var mail in Emails)
  494. {
  495. bool isAbsentDansInbox = false;
  496. if (Emails != null)
  497. {
  498. isAbsentDansInbox = !uids.Any(e => e == mail.Uid);
  499. if(isAbsentDansInbox)
  500. {
  501. // Si l'email n'est pas dans la liste des non lus, on le marque pour suppression
  502. mail.ToRemove = true; ;
  503. }
  504. }
  505. }
  506. }
  507. foreach (var uid in uids)
  508. {
  509. bool isExistDansListe = false;
  510. if (Emails != null)
  511. {
  512. isExistDansListe = Emails.Any(e => e.Uid == uid);
  513. }
  514. if (!isExistDansListe)
  515. {
  516. var message = await inbox.GetMessageAsync(uid);
  517. EmailMessage msg = (new EmailMessage
  518. {
  519. Uid = uid,
  520. Id = message.MessageId,
  521. To = string.Join(";", message.To.Mailboxes.Select(e => e.Address).ToList()),
  522. Cc = string.Join(";", message.Cc.Mailboxes.Select(e => e.Address).ToList()),
  523. FromName = message.From.Mailboxes.FirstOrDefault()?.Name ?? "",
  524. From = message.From.Mailboxes.FirstOrDefault()?.Address ?? "",
  525. Subject = message.Subject,
  526. Date = message.Date.DateTime,
  527. Preview = message.TextBody?.Substring(0, Math.Min(200, message.TextBody.Length)) ?? "",
  528. TextBody = message.TextBody != null ? message.TextBody : "",
  529. TextBodyHTML = message.HtmlBody != null ? message.HtmlBody : "",
  530. InReplyTo = message.InReplyTo,
  531. IsDestinatairePrincipal = string.Join(";", message.To.Mailboxes.Select(e => e.Address).ToList()).Contains(email, StringComparison.OrdinalIgnoreCase)
  532. });
  533. if(msg.TextBody == "" && message.HtmlBody != null)
  534. {
  535. msg.TextBody = HtmlToPlainText(message.HtmlBody);
  536. }
  537. // Ici, on vectorize et stocke le message
  538. var identiteMessage = $"Du {msg.Date.ToString("dd/MM/yyyy hh:mm")} - De {msg.From} - Sujet : {msg.Subject}";
  539. if (message.TextBody != null && message.TextBody != "")
  540. {
  541. await RAGService.IngestDocument(Domain.Emails,false, false, msg.TextBody, identiteMessage, msg.Subject,proprietaire);
  542. }
  543. if (message.Attachments != null && message.Attachments.Any())
  544. {
  545. msg.ContentPJ = new();
  546. var listFullText = await ExtractAndSummarizeAttachments(message, identiteMessage);
  547. if (listFullText != null && listFullText.Count > 0)
  548. {
  549. msg.ContentPJ = listFullText;
  550. }
  551. }
  552. // Analyse du message par modele
  553. msg = await AnalyserMail(msg);
  554. messages.Add(msg);
  555. }
  556. }
  557. await client.DisconnectAsync(true);
  558. return messages;
  559. }
  560. private static async Task<string> TraitementPJ(string fullFilename,string filename, ReActAgent.ReActAgent reActRagAgent, string identiteMessage)
  561. {
  562. LoggerService.LogInfo("EmailService.TraitementPJ");
  563. string sReturn = "";
  564. string extractedText = FilesService.ExtractText(fullFilename);
  565. if (!string.IsNullOrWhiteSpace(extractedText))
  566. {
  567. //reActRagAgent.IngestDocument(extractedText, filename, identiteMessage);
  568. var chunks = Services.RAGService.ChunkText(extractedText);
  569. var (summary, model) = await reActRagAgent.SummarizeDocAsync("Résumé de PJ", chunks, false);
  570. sReturn = $"📎 {filename} :\n{summary}";
  571. LoggerService.LogDebug($"{model} \t {summary}");
  572. }
  573. else
  574. {
  575. sReturn = $"📎 {filename} :\n Fichier illisible... ";
  576. }
  577. return sReturn;
  578. }
  579. private static async Task<List<string>> ExtractAndSummarizeAttachments(MimeMessage message, string identiteMessage)
  580. {
  581. LoggerService.LogInfo("EmailService.ExtractAndSummarizeAttachments");
  582. var reActRagAgent = new Services.ReActAgent.ReActAgent();
  583. var summaries = new List<string>();
  584. foreach (var attachment in message.Attachments)
  585. {
  586. if (attachment is MimePart part)
  587. {
  588. var fileName = part.FileName ?? "piece_jointe.xxx";
  589. var extension = Path.GetExtension(fileName).ToLowerInvariant();
  590. var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + "_" + fileName);
  591. LoggerService.LogInfo($"Traitement de la pièce jointe : {fileName}");
  592. using (var stream = File.Create(tempPath))
  593. await part.Content.DecodeToAsync(stream);
  594. if (FilesService.IsExtensionSupport(extension))
  595. {
  596. summaries.Add(await TraitementPJ(tempPath, fileName, reActRagAgent, identiteMessage));
  597. }
  598. else if (extension == ".zip")
  599. {
  600. var extractDir = Path.Combine(Path.GetTempPath(), "zip_" + Guid.NewGuid());
  601. Directory.CreateDirectory(extractDir);
  602. System.IO.Compression.ZipFile.ExtractToDirectory(tempPath, extractDir);
  603. foreach (var innerFile in Directory.GetFiles(extractDir, "*.*", SearchOption.AllDirectories))
  604. {
  605. var filenameInZip = Path.GetFileName(innerFile);
  606. LoggerService.LogInfo($"Traitement de la pièce jointe dans {fileName} : {filenameInZip}");
  607. try
  608. {
  609. var ext = Path.GetExtension(innerFile).ToLowerInvariant();
  610. if (FilesService.IsExtensionSupport(ext))
  611. {
  612. summaries.Add(await TraitementPJ(innerFile, filenameInZip, reActRagAgent, identiteMessage));
  613. }
  614. else
  615. {
  616. summaries.Add($"📎 {filenameInZip} :\n Format de fichier non pris en charge... ");
  617. }
  618. }
  619. catch
  620. {
  621. summaries.Add($"📎 {filenameInZip} :\n Fichier illisible... ");
  622. }
  623. }
  624. Directory.Delete(extractDir, true);
  625. }
  626. else
  627. {
  628. summaries.Add($"📎 {fileName} :\n Format de fichier non pris en charge... ");
  629. }
  630. File.Delete(tempPath);
  631. }
  632. }
  633. return summaries;
  634. }
  635. private static string HtmlToPlainText(string html)
  636. {
  637. LoggerService.LogInfo("EmailService.HtmlToPlainText");
  638. return System.Text.RegularExpressions.Regex.Replace(html, "<.*?>", string.Empty);
  639. }
  640. private static async Task<EmailMessage> AnalyserMail(EmailMessage email)
  641. {
  642. try
  643. {
  644. LoggerService.LogInfo($"EmailService.AnalyserMail : '{email.Subject}'");
  645. #region MAJ du modèle IA
  646. email.ModeleIA = ReActAgent.ReActAgent.GetModeleIA(ModelsUseCases.TypeUseCase.AnalyseMails);
  647. LoggerService.LogInfo($"Modèle utilisé : {email.ModeleIA}");
  648. #endregion
  649. #region Résumé
  650. bool isPromptInjection = false;
  651. string messagePromptInjection = "";
  652. var subject = _ReActAgent.CleanUserInput(email.Subject, out isPromptInjection);
  653. if (isPromptInjection)
  654. {
  655. messagePromptInjection += "Le sujet de l'email contient des éléments suspects.\n";
  656. email.PresenceSuspecte = true;
  657. }
  658. var textBody = _ReActAgent.CleanUserInput(email.TextBody, out isPromptInjection);
  659. if (isPromptInjection)
  660. {
  661. messagePromptInjection = "Le corps de l'email contient des éléments suspects.\n";
  662. email.PresenceSuspecte = true;
  663. }
  664. /*
  665. var promptResume = $@"
  666. Tu es un assistant professionnel chargé d'analyser un e-mail.
  667. Lis le contenu suivant et fais un résumé clair et concis.
  668. Ensuite, indique explicitement :
  669. - Une note d'importance entre 0 et 5 (5 étant la plus haute importance) avec justification si possible
  670. - Une note d'urgence entre 0 et 5 (5 étant la plus haute importance) avec justification si possible
  671. Format de réponse attendu :
  672. Résumé : ...
  673. Importance (0-5) : ...
  674. Urgence (0-5) : ...
  675. Objet : {subject}
  676. Contenu : {textBody}";
  677. */
  678. var promptResume = PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Resume, subject, textBody);
  679. if (email.IsPJ)
  680. {
  681. var contentPJ = _ReActAgent.CleanUserInput(email.ContentPJ_STR, out isPromptInjection); ;
  682. //promptResume += $"\n\n Inclus dans le résumé l'analyse des pièces jointes dont voici le résumé : {contentPJ}";
  683. promptResume += $"\n\n" + PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Resume_PJ, contentPJ);
  684. if (isPromptInjection)
  685. {
  686. messagePromptInjection += "Le contenu des pièces jointes contient des éléments suspects.\n";
  687. email.PresenceSuspecte = true;
  688. }
  689. }
  690. LoggerService.LogDebug($"Génération Résumé de : '{email.Subject}'");
  691. var (resume, m1) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, promptResume, "Résumé d'email");
  692. email.Resume = messagePromptInjection + resume;
  693. var (important, urgent, importanceScore, urgenceScore, analyse) = ExtrairePriorites(resume);
  694. email.IsImportant = important;
  695. email.IsUrgent = urgent;
  696. email.ImportanceScore = importanceScore;
  697. email.UrgenceScore = urgenceScore;
  698. email.Analyse = analyse;
  699. #endregion
  700. #region Catégorie
  701. /*
  702. var promptCategorie = $@"
  703. Tu dois catégoriser un email en fonction de son contenu.
  704. Voici le résumé de l'email :
  705. {resume}
  706. Catégorise ce message parmi les choix suivants :
  707. - Demande d'information
  708. - Réclamation
  709. - Demande de devis
  710. - Problème technique
  711. - Autre
  712. Réponds uniquement par une des catégories listées.";
  713. */
  714. var promptCategorie = PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Categorie, resume);
  715. LoggerService.LogInfo($"Génération Catégorie de : '{email.Subject}'");
  716. var (categorie,m2) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, promptCategorie, "Catégorie d'email");
  717. email.Categorie = categorie;
  718. #endregion
  719. #region Stratégie
  720. /*
  721. var promptStrategie = $@"
  722. Tu dois choisir une stratégie de réponse à un email.
  723. Catégorie : {categorie}
  724. Important : {(important ? "oui" : "non")}
  725. Urgent : {(urgent ? "oui" : "non")}
  726. Voici les options possibles :
  727. - Répondre avec une explication simple
  728. - Demander plus d'informations
  729. - Présenter des excuses et proposer une solution
  730. - Accuser réception et informer d'un délai
  731. Choisis la stratégie la plus adaptée en tenant compte de la **catégorie**, de l'**importance** et de l'**urgence**.";
  732. */
  733. var promptStrategie = PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Strategie, categorie, (important ? "oui" : "non"), (urgent ? "oui" : "non"));
  734. LoggerService.LogInfo($"Génération Stratégie de : '{email.Subject}'");
  735. var (strategie, m3) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, promptStrategie, "Stratégie d'email");
  736. email.Strategie = strategie;
  737. #endregion
  738. #region Réponse proposée
  739. var paramUser = LoadParametres();
  740. /*
  741. var promptReponse = $@"
  742. Rédige une réponse professionnelle à cet email en suivant la stratégie définie.
  743. Objet : {email.Subject}
  744. Contenu : {email.TextBody}
  745. Résumé : {resume}
  746. Catégorie : {categorie}
  747. Stratégie : {strategie}
  748. La réponse doit être claire, polie, adaptée au contexte et suffisamment concise.
  749. Voici la signature à insérer en fin de mail :
  750. Cordialement,
  751. {paramUser!.UserNomPrenom}
  752. {paramUser!.UserRole}
  753. {paramUser!.UserEntreprise}";
  754. */
  755. var promptReponse = PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Reponse, email.Subject, email.TextBody, resume, categorie, strategie, paramUser!.UserNomPrenom, paramUser!.UserRole, paramUser!.UserEntreprise);
  756. if (email.IsPJ)
  757. {
  758. //promptReponse += $"\n\n Inclus dans la réponse proposée l'analyse des pièces jointes dont voici le résumé : {email.ContentPJ_STR}";
  759. promptReponse += $"\n\n" + PromptService.GetPrompt(PromptService.ePrompt.EmailService_AnalyserMail_Reponse_PJ, email.ContentPJ_STR);
  760. }
  761. LoggerService.LogInfo($"Génération Réponse proposée de : '{email.Subject}'");
  762. var (reponse,m4) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, promptReponse, "Réponse proposée d'email");
  763. email.Reponse = reponse;
  764. #endregion
  765. LoggerService.LogInfo($"Analyse d'un mail terminée : '{email.Subject}'");
  766. return email;
  767. }
  768. catch (Exception ex)
  769. {
  770. LoggerService.LogError($"Erreur lors de l'analyse du mail '{email.Subject}': {ex.Message}");
  771. email.Reponse = "Une erreur est survenue lors de l'analyse de cet email.";
  772. return email;
  773. }
  774. }
  775. private static (bool important, bool urgent, int importanceScore, int urgenceScore, string analyse) ExtrairePriorites(string resume)
  776. {
  777. LoggerService.LogInfo("EmailService.ExtrairePriorites");
  778. //var important = resume.Contains("Important : oui", StringComparison.OrdinalIgnoreCase);
  779. //var urgent = resume.Contains("Urgent : oui", StringComparison.OrdinalIgnoreCase);
  780. int importanceScore = ExtraireScore(resume, "Importance");
  781. int urgenceScore = ExtraireScore(resume, "Urgence");
  782. string analyse = ExtraireAnalyse(resume, "Analyse");
  783. var important = (importanceScore > 2);
  784. var urgent = (urgenceScore > 2);
  785. return (important, urgent, importanceScore, urgenceScore, analyse);
  786. }
  787. private static int ExtraireScore(string texte, string motCle)
  788. {
  789. LoggerService.LogInfo("EmailService.ExtraireScore");
  790. // Cette regex gère :
  791. // - "Importance (0-5) : 4"
  792. // - "Importance (0-5):4"
  793. // - "Importance (4/5) : ..."
  794. // - "Importance: 4/5"
  795. // - avec ou sans espaces
  796. var pattern = $@"{motCle}\s*(?:\(\s*0\s*[--–—]\s*5\s*\)\s*:?\s*(\d)|\(\s*(\d)\s*/\s*5\s*\)|:?\s*(\d)\s*/\s*5)";
  797. var match = System.Text.RegularExpressions.Regex.Match(texte, pattern, RegexOptions.IgnoreCase);
  798. if (match.Success)
  799. {
  800. // On prend le premier groupe non vide (selon le format rencontré)
  801. for (int i = 1; i < match.Groups.Count; i++)
  802. {
  803. if (match.Groups[i].Success)
  804. return int.Parse(match.Groups[i].Value);
  805. }
  806. }
  807. return 0;
  808. }
  809. private static string ExtraireAnalyse(string texte, string motCle)
  810. {
  811. if (string.IsNullOrWhiteSpace(texte) || string.IsNullOrWhiteSpace(motCle))
  812. return string.Empty;
  813. // Échappe le mot-clé pour éviter les problèmes si motCle contient des caractères regex spéciaux
  814. var escaped = Regex.Escape(motCle);
  815. // 1) Pattern principal : capture un mot (lettres Unicode, -, +) juste après "Analyse :"
  816. var pattern = $@"\b{escaped}\s*:\s*([\p{{L}}\-+]+)";
  817. var match = Regex.Match(texte, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
  818. if (match.Success)
  819. return match.Groups[1].Value; // "Neutre"
  820. // 2) Fallback : capture la portion jusqu'à la fin de la ligne ou jusqu'à une ponctuation,
  821. // puis on prend le premier token (au cas où il y aurait du texte additionnel sur la même ligne)
  822. pattern = $@"\b{escaped}\s*:\s*([^\r\n\.\,\;\:\!\?]+)";
  823. match = Regex.Match(texte, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
  824. if (match.Success)
  825. {
  826. var s = match.Groups[1].Value.Trim();
  827. var first = s.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
  828. return first.Length > 0 ? first[0] : string.Empty;
  829. }
  830. return string.Empty;
  831. }
  832. private static async Task<bool> MarquerMessageCommeLu(MailKit.UniqueId? id, string email, string password, string imapServer, int port)
  833. {
  834. LoggerService.LogInfo("EmailService.MarquerMessageCommeLu");
  835. try
  836. {
  837. using var client = new ImapClient();
  838. await client.ConnectAsync(imapServer, port, SecureSocketOptions.SslOnConnect);
  839. await client.AuthenticateAsync(email, password);
  840. var inbox = client.Inbox;
  841. await inbox.OpenAsync(FolderAccess.ReadWrite);
  842. if(id.HasValue) // Marque comme lu
  843. await inbox.AddFlagsAsync(id.Value, MessageFlags.Seen, true); // Marque comme lu
  844. await client.DisconnectAsync(true);
  845. await Task.Delay(1);
  846. LoggerService.LogInfo("Mail marqué comme 'lu'");
  847. return true;
  848. }
  849. catch (Exception ex)
  850. {
  851. LoggerService.LogError(ex.ToString());
  852. return false;
  853. }
  854. }
  855. #endregion
  856. #region Conservés pour la culture
  857. private static async Task<MimeMessage?> GetLastUnreadEmailAsync(string email, string password, string imapServer, int port = 993)
  858. {
  859. using var client = new ImapClient();
  860. await client.ConnectAsync(imapServer, port, SecureSocketOptions.SslOnConnect);
  861. await client.AuthenticateAsync(email, password);
  862. var inbox = client.Inbox;
  863. await inbox.OpenAsync(MailKit.FolderAccess.ReadOnly);
  864. // Récupère tous les messages non lus
  865. var uids = await inbox.SearchAsync(SearchQuery.NotSeen);
  866. if (!uids.Any()) return null;
  867. // On prend le dernier non lu
  868. var lastUnread = uids.Last();
  869. var message = await inbox.GetMessageAsync(lastUnread);
  870. await client.DisconnectAsync(true);
  871. return message;
  872. }
  873. private static async Task<MimeMessage> LireDernierEmailAsync(string email, string password, string imapServer, int port = 993)
  874. {
  875. using var client = new ImapClient();
  876. await client.ConnectAsync(imapServer, port, SecureSocketOptions.SslOnConnect);
  877. await client.AuthenticateAsync(email, password);
  878. var inbox = client.Inbox;
  879. await inbox.OpenAsync(MailKit.FolderAccess.ReadOnly);
  880. var message = await inbox.GetMessageAsync(inbox.Count - 1);
  881. await client.DisconnectAsync(true);
  882. return message;
  883. }
  884. #endregion
  885. }
  886. }