IO4IT 2.1.0__tar.gz → 2.1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. io4it-2.1.0.4/PKG-INFO +33 -0
  2. io4it-2.1.0.4/io4it.egg-info/PKG-INFO +33 -0
  3. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/SOURCES.txt +2 -0
  4. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/utils/mail.py +33 -13
  5. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/utils/offuscation_basique.py +123 -16
  6. io4it-2.1.0.4/orangecontrib/IO4IT/utils/pool_exec_utils.py +33 -0
  7. io4it-2.1.0.4/orangecontrib/IO4IT/utils/utils_md.py +168 -0
  8. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +2 -2
  9. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +652 -0
  10. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWS3Uploader.py +1 -1
  11. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWmailLoader.py +3 -2
  12. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui +82 -0
  13. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owchatgpt.ui +168 -0
  14. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui +87 -0
  15. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui +72 -0
  16. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owmailloader.ui +149 -0
  17. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owmailsender.ui +149 -0
  18. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui +103 -0
  19. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +130 -0
  20. io4it-2.1.0.4/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +70 -0
  21. {io4it-2.1.0 → io4it-2.1.0.4}/setup.py +1 -1
  22. io4it-2.1.0/PKG-INFO +0 -7
  23. io4it-2.1.0/io4it.egg-info/PKG-INFO +0 -7
  24. io4it-2.1.0/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +0 -563
  25. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui +0 -69
  26. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owchatgpt.ui +0 -155
  27. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui +0 -74
  28. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui +0 -59
  29. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owmailloader.ui +0 -32
  30. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owmailsender.ui +0 -32
  31. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owmarkdownizer.ui +0 -101
  32. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +0 -104
  33. io4it-2.1.0/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +0 -57
  34. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/dependency_links.txt +0 -0
  35. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/entry_points.txt +0 -0
  36. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/namespace_packages.txt +0 -0
  37. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/requires.txt +0 -0
  38. {io4it-2.1.0 → io4it-2.1.0.4}/io4it.egg-info/top_level.txt +0 -0
  39. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/__init__.py +0 -0
  40. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
  41. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/ocr_function/word_converter.py +0 -0
  42. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/utils/__init__.py +0 -0
  43. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWChatGpt.py +0 -0
  44. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWDeep_Search.py +0 -0
  45. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWS3downloader.py +0 -0
  46. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWS3list.py +0 -0
  47. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWSpeechToText.py +0 -0
  48. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWmailSender.py +0 -0
  49. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +0 -0
  50. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/__init__.py +0 -0
  51. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/designer/__init__.py +0 -0
  52. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/designer/chart.html +0 -0
  53. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/designer/nogui.ui +0 -0
  54. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui +0 -0
  55. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/__init__.py +0 -0
  56. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/chatgpt.png +0 -0
  57. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/deepsearch.svg +0 -0
  58. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/download.png +0 -0
  59. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/export_md.png +0 -0
  60. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/file_extensor.png +0 -0
  61. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
  62. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/mail_loader.png +0 -0
  63. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/mail_writer.png +0 -0
  64. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/md.png +0 -0
  65. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
  66. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
  67. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/visualizationer.png +0 -0
  68. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
  69. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/IO4IT/widgets/icons_dev/__init__.py +0 -0
  70. {io4it-2.1.0 → io4it-2.1.0.4}/orangecontrib/__init__.py +0 -0
  71. {io4it-2.1.0 → io4it-2.1.0.4}/setup.cfg +0 -0
io4it-2.1.0.4/PKG-INFO ADDED
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.1
2
+ Name: io4it
3
+ Version: 2.1.0.4
4
+ Summary: UNKNOWN
5
+ Home-page:
6
+ Author:
7
+ Author-email:
8
+ License: UNKNOWN
9
+ Keywords: orange3 add-on
10
+ Platform: UNKNOWN
11
+ Requires-Dist: pylatexenc
12
+ Requires-Dist: docopt
13
+ Requires-Dist: boto3
14
+ Requires-Dist: opencv-python-headless==4.6.0.66
15
+ Requires-Dist: docling==2.30.0
16
+ Requires-Dist: docling-core==2.26.3
17
+ Requires-Dist: speechbrain
18
+ Requires-Dist: whisper
19
+ Requires-Dist: whisper-openai
20
+ Requires-Dist: pyannote.audio
21
+ Requires-Dist: pyannote-core
22
+ Requires-Dist: pypandoc
23
+ Requires-Dist: pypandoc-binary
24
+ Requires-Dist: scikit-learn
25
+ Requires-Dist: openai
26
+ Requires-Dist: pip-system-certs==5.0
27
+ Requires-Dist: docx2pdf
28
+ Requires-Dist: doc2docx
29
+ Requires-Dist: msal
30
+ Requires-Dist: exchangelib
31
+ Requires-Dist: CATEGORIT
32
+
33
+ UNKNOWN
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.1
2
+ Name: io4it
3
+ Version: 2.1.0.4
4
+ Summary: UNKNOWN
5
+ Home-page:
6
+ Author:
7
+ Author-email:
8
+ License: UNKNOWN
9
+ Keywords: orange3 add-on
10
+ Platform: UNKNOWN
11
+ Requires-Dist: pylatexenc
12
+ Requires-Dist: docopt
13
+ Requires-Dist: boto3
14
+ Requires-Dist: opencv-python-headless==4.6.0.66
15
+ Requires-Dist: docling==2.30.0
16
+ Requires-Dist: docling-core==2.26.3
17
+ Requires-Dist: speechbrain
18
+ Requires-Dist: whisper
19
+ Requires-Dist: whisper-openai
20
+ Requires-Dist: pyannote.audio
21
+ Requires-Dist: pyannote-core
22
+ Requires-Dist: pypandoc
23
+ Requires-Dist: pypandoc-binary
24
+ Requires-Dist: scikit-learn
25
+ Requires-Dist: openai
26
+ Requires-Dist: pip-system-certs==5.0
27
+ Requires-Dist: docx2pdf
28
+ Requires-Dist: doc2docx
29
+ Requires-Dist: msal
30
+ Requires-Dist: exchangelib
31
+ Requires-Dist: CATEGORIT
32
+
33
+ UNKNOWN
@@ -13,6 +13,8 @@ orangecontrib/IO4IT/ocr_function/word_converter.py
13
13
  orangecontrib/IO4IT/utils/__init__.py
14
14
  orangecontrib/IO4IT/utils/mail.py
15
15
  orangecontrib/IO4IT/utils/offuscation_basique.py
16
+ orangecontrib/IO4IT/utils/pool_exec_utils.py
17
+ orangecontrib/IO4IT/utils/utils_md.py
16
18
  orangecontrib/IO4IT/widgets/OWChatGpt.py
17
19
  orangecontrib/IO4IT/widgets/OWDeep_Search.py
18
20
  orangecontrib/IO4IT/widgets/OWExportMarkdown.py
@@ -4,19 +4,21 @@ import email
4
4
  from email.header import decode_header
5
5
  import time
6
6
  import os
7
- from bs4 import BeautifulSoup
8
7
  from email import policy
9
8
  from email.message import EmailMessage
10
9
  import mimetypes
11
10
 
12
- from exchangelib import Credentials, Account, DELEGATE, Configuration, HTMLBody, Message, Mailbox
11
+ from exchangelib import Credentials, HTMLBody, Message, Mailbox
13
12
  from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
14
13
  from exchangelib import OAuth2Credentials, Identity, Configuration, Account, DELEGATE
15
- from oauthlib.oauth2 import OAuth2Token
14
+
16
15
 
17
16
  from bs4 import BeautifulSoup
18
- BaseProtocol.HTTP_ADAPTER_CLS=NoVerifyHTTPAdapter
19
- import json
17
+
18
+
19
+ ##### please uncomment this line to ignore certificate checking (owa)
20
+ #BaseProtocol.HTTP_ADAPTER_CLS=NoVerifyHTTPAdapter
21
+
20
22
  from msal import ConfidentialClientApplication
21
23
  from oauthlib.oauth2 import OAuth2Token
22
24
 
@@ -185,6 +187,8 @@ def check_new_emails(offusc_conf_agent,type_co, list_agent_email=[]):
185
187
  with open(folder + "/" + "mail.ok", "w") as f:
186
188
  f.close()
187
189
 
190
+
191
+
188
192
  imap.logout()
189
193
  except Exception as e:
190
194
  print(f"Erreur lors de la vérification des mails : {e}")
@@ -199,7 +203,7 @@ def check_new_emails(offusc_conf_agent,type_co, list_agent_email=[]):
199
203
  )
200
204
 
201
205
  account = Account(
202
- primary_smtp_address=mail,
206
+ primary_smtp_address=alias,
203
207
  credentials=credentials,
204
208
  config=config,
205
209
  autodiscover=False,
@@ -301,6 +305,7 @@ def check_new_emails(offusc_conf_agent,type_co, list_agent_email=[]):
301
305
  f.write("\n".join(output_lines))
302
306
  with open(os.path.join(folder, "mail.ok"), "w") as f:
303
307
  pass
308
+
304
309
  except Exception as e:
305
310
  print(f"Erreur lors du traitement du mail : {e}")
306
311
  elif type_co == "MICROSOFT_EXCHANGE_OAUTH2":
@@ -427,6 +432,7 @@ def check_new_emails(offusc_conf_agent,type_co, list_agent_email=[]):
427
432
  with open(os.path.join(folder, "mail.ok"), "w") as f:
428
433
  pass
429
434
 
435
+
430
436
  except Exception as e:
431
437
  print(f"Erreur OAuth2 lors du traitement du mail : {e}")
432
438
 
@@ -662,13 +668,27 @@ def check_send_new_emails(offusc_conf_agent,type_co):
662
668
  else:
663
669
  print("type de co non valide")
664
670
 
671
+ def list_conf_files(type_co):
672
+ conf_files = []
673
+ if type_co is None or type_co == "":
674
+ return conf_files
675
+ dossier = MetManagement.get_secret_content_dir()
676
+ dossier = dossier + type_co
677
+ files = os.listdir(dossier)
678
+ for file in files:
679
+ if file.lower().endswith(".json"):
680
+ conf_files.append(file)
681
+ return conf_files
682
+
683
+
684
+
665
685
  if __name__ == "__main__":
666
- offusc_conf_agent=""
667
- #type_co="IMAP4_SSL"
668
- type_co="MICROSOFT_EXCHANGE_OAUTH2"
686
+ type_co="IMAP4_SSL"
669
687
  list_agent_email = []
688
+ offusc_conf_agents = list_conf_files(type_co)
670
689
  while True:
671
- check_new_emails(offusc_conf_agent,type_co, list_agent_email)
672
- time.sleep(1)
673
- check_send_new_emails(offusc_conf_agent,type_co)
674
- time.sleep(1)
690
+ for offusc_conf_agent in offusc_conf_agents:
691
+ check_new_emails(offusc_conf_agent,type_co, list_agent_email)
692
+ time.sleep(1)
693
+ check_send_new_emails(offusc_conf_agent,type_co)
694
+ time.sleep(1)
@@ -2,6 +2,9 @@ import os
2
2
  import json
3
3
  import hashlib
4
4
  import getpass
5
+ from cryptography.fernet import Fernet
6
+ import base64
7
+
5
8
 
6
9
  if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
7
10
  from Orange.widgets.orangecontrib.AAIT.utils import MetManagement
@@ -47,12 +50,26 @@ def get_keys_dir(type_key: str = "MICROSOFT_EXCHANGE_OAUTH2") -> str:
47
50
  if os.path.basename(base) != "keys":
48
51
  base = os.path.join(base, "keys")
49
52
  dossier = os.path.normpath(os.path.join(base, type_key))
50
- print("dossier", dossier)
51
53
  os.makedirs(dossier, exist_ok=True)
52
54
  return dossier
53
55
  except Exception as e:
54
56
  raise RuntimeError(f"Erreur création/récupération dossier : {e}")
55
57
 
58
+ def get_fernet_key() -> bytes:
59
+ """
60
+ Dérive une clé Fernet (32 octets base64) à partir du nom d'utilisateur local.
61
+ """
62
+ username = getpass.getuser().encode("utf-8")
63
+ digest = hashlib.sha256(username).digest()
64
+ return base64.urlsafe_b64encode(digest[:32]) # 32 bytes en base64
65
+
66
+ def encrypt_secure(data: str) -> str:
67
+ fernet = Fernet(get_fernet_key())
68
+ return fernet.encrypt(data.encode("utf-8")).decode("utf-8")
69
+
70
+ def decrypt_secure(data: str) -> str:
71
+ fernet = Fernet(get_fernet_key())
72
+ return fernet.decrypt(data.encode("utf-8")).decode("utf-8")
56
73
 
57
74
 
58
75
  # GSTION IMAP
@@ -160,6 +177,83 @@ def enregistrer_config_owa(mail,alias,server,username,password,interval):
160
177
  print(f"❌ Erreur lors de l'enregistrement : {e}")
161
178
  return 1
162
179
 
180
+ def enregistrer_config_owa_secure(mail, alias, server, username, password, interval):
181
+ try:
182
+ dossier = get_keys_dir("MICROSOFT_EXCHANGE_OWA_SECURE")
183
+ os.makedirs(dossier, exist_ok=True)
184
+
185
+ mdp_chiffre = encrypt_secure(password)
186
+
187
+ nom_fichier = os.path.join(dossier, f"{alias.replace('@', '_at_')}.json")
188
+
189
+ contenu = {
190
+ "mail": mail,
191
+ "alias": alias,
192
+ "server": server,
193
+ "username": username,
194
+ "password_encrypted": mdp_chiffre,
195
+ "interval_second": interval
196
+ }
197
+
198
+ with open(nom_fichier, "w", encoding="utf-8") as f:
199
+ json.dump(contenu, f, indent=4)
200
+
201
+ print(f"✅ Fichier OWA sécurisé enregistré : {nom_fichier}")
202
+ return 0
203
+
204
+ except Exception as e:
205
+ print(f"❌ Erreur d'enregistrement sécurisé OWA : {e}")
206
+ return 1
207
+
208
+ def enregistrer_config_cli_owa_secure():
209
+ print("\n🔐 Écriture fichier OWA avec chiffrement sécurisé :")
210
+ mail = input("📧 Mail (nom@domain.com) : ").strip()
211
+ alias = mail #input("📛 Alias (visible) : ").strip()
212
+ server = input("🌐 Server : ").strip()
213
+ username = input("👤 Username (domain\\user) : ").strip()
214
+ password = input("🔑 Password (non masqué) : ").strip()
215
+ interval = int(input("⏱️ Interval (secondes) : ").strip())
216
+ if alias == "''" or alias == "\"\"" or alias == "":
217
+ alias = mail
218
+
219
+ if 0 != enregistrer_config_owa_secure(mail, alias, server, username, password, interval):
220
+ print("❌ Erreur lors de enregistrer_config_owa_secure !")
221
+
222
+ def lecture_config_cli_owa_secure():
223
+ fichier = input("📄 Nom du fichier JSON (sans chemin) : ").strip()
224
+ cfg = lire_config_owa_secure(fichier)
225
+
226
+ if cfg is None:
227
+ print("❌ Erreur")
228
+ return
229
+
230
+ print(f"\n📧 mail : {cfg[0]}")
231
+ print(f"📛 alias : {cfg[1]}")
232
+ print(f"🌐 server : {cfg[2]}")
233
+ print(f"👤 username : {cfg[3]}")
234
+ print(f"🔑 password : {cfg[4]}")
235
+ print(f"⏱️ intervalle : {cfg[5]}s")
236
+
237
+ def lire_config_owa_secure(chemin_fichier):
238
+ try:
239
+ chemin_fichier = os.path.join(get_keys_dir("MICROSOFT_EXCHANGE_OWA_SECURE"), chemin_fichier)
240
+ with open(chemin_fichier, "r", encoding="utf-8") as f:
241
+ contenu = json.load(f)
242
+
243
+ mdp_dechiffre = decrypt_secure(contenu["password_encrypted"])
244
+
245
+ return [
246
+ contenu["mail"],
247
+ contenu["alias"],
248
+ contenu["server"],
249
+ contenu["username"],
250
+ mdp_dechiffre,
251
+ int(contenu["interval_second"])
252
+ ]
253
+
254
+ except Exception as e:
255
+ print(f"❌ Erreur de lecture sécurisée OWA : {e}")
256
+ return None
163
257
 
164
258
 
165
259
  # Fonction pour lire le fichier de configuration et déchiffrer le mot de passe
@@ -357,21 +451,28 @@ def lire_config_api(service_name):
357
451
  def enregistrer_config_cli_api():
358
452
  print("\n📝 Écriture d’une clé API :")
359
453
  service = input("🔖 Nom du service : ").strip()
360
- api_key = getpass.getpass("🔑 Clé API : ").strip()
454
+ api_key = input("🔑 Clé API : ").strip()
361
455
  desc = input("✏️ Description : ").strip()
362
456
  if 0 != enregistrer_config_api(service, api_key, desc):
363
457
  print("erreur!")
364
458
 
365
- def lire_config_cli_api():
366
- service = input("🔖 Nom du service : ").strip()
367
- cfg = lire_config_api(service)
368
- if cfg is None:
369
- print("erreur")
370
- return
371
- print(f"\n📄 service : {cfg['service']}")
372
- print(f"🔑 clé API : {cfg['api_key']}")
373
- if cfg['description']:
374
- print(f"📝 description : {cfg['description']}")
459
+ def lire_config_cli_api(service=""):
460
+ if service == "":
461
+ service = input("🔖 Nom du service : ").strip()
462
+ try:
463
+ cfg = lire_config_api(service)
464
+ if cfg is None:
465
+ print("erreur")
466
+ return
467
+ print(f"\n📄 service : {cfg['service']}")
468
+ print(f"🔑 clé API : {cfg['api_key']}")
469
+ if cfg['description']:
470
+ print(f"📝 description : {cfg['description']}")
471
+ return cfg['api_key']
472
+ except Exception as e:
473
+ print(f"❌ Erreur lors de la lecture : {e}")
474
+ return None
475
+
375
476
 
376
477
 
377
478
  # Gestion d’éléments de nxp (DOSSIER_NODE_ID, SERVEUR, USERNAME, PASSWORD) (HARD dossier aait_store/keys)
@@ -458,8 +559,6 @@ def lire_config_cli_nxp():
458
559
  print(f"📝 description : {cfg['description']}")
459
560
 
460
561
 
461
-
462
-
463
562
  if __name__ == "__main__":
464
563
  print("1) ecrire fichier IMAP4_SSL")
465
564
  print("2) dechiffer fichier IMAP4_SSL")
@@ -471,7 +570,10 @@ if __name__ == "__main__":
471
570
  print("8) Déchiffrer fichier Microsoft Exchange (OWA)")
472
571
  print("9) Écrire fichier Microsoft Exchange (OAuth2)")
473
572
  print("10) Déchiffrer fichier Microsoft Exchange (OAuth2)")
474
- choix = input("👉 Que faire ? [1-10] : ").strip()
573
+ print("11) Écrire fichier Microsoft Exchange (OWA) [SECURE]")
574
+ print("12) Déchiffrer fichier Microsoft Exchange (OWA) [SECURE]")
575
+
576
+ choix = input("👉 Que faire ? [1-12] : ").strip()
475
577
 
476
578
  if choix == "1":
477
579
  enregistrer_config_cli_imap4_ssl()
@@ -492,7 +594,12 @@ if __name__ == "__main__":
492
594
  elif choix=="9":
493
595
  enregistrer_config_cli_oauth2()
494
596
  elif choix=="10":
495
- lecture_config_cli_oauth2()
597
+ lire_config_cli_oauth2()
598
+ elif choix == "11":
599
+ enregistrer_config_cli_owa_secure()
600
+ elif choix == "12":
601
+ lecture_config_cli_owa_secure()
602
+
496
603
 
497
604
  else:
498
605
  print("❌ Choix invalide. Réessayez.\n")
@@ -0,0 +1,33 @@
1
+ # pool_exec_utils.py
2
+ import os
3
+ from concurrent.futures import ProcessPoolExecutor
4
+ from typing import Optional, Tuple
5
+
6
+ def available_cpus(default: int = 4) -> int:
7
+ c = os.cpu_count()
8
+ return c if (c and c > 0) else default
9
+
10
+ def cpu_label_text() -> str:
11
+ return f"🖥️ CPU dispo : {available_cpus()}"
12
+
13
+ def shutdown_executor(executor: Optional[ProcessPoolExecutor]) -> None:
14
+ if executor is not None:
15
+ try:
16
+ executor.shutdown(wait=False, cancel_futures=True)
17
+ except TypeError:
18
+ executor.shutdown(wait=False)
19
+ except Exception:
20
+ pass
21
+
22
+ def create_or_update_executor(
23
+ executor: Optional[ProcessPoolExecutor],
24
+ current_workers: Optional[int],
25
+ new_workers: int,
26
+ ) -> Tuple[ProcessPoolExecutor, int, str, bool]:
27
+ changed = (executor is None) or (current_workers != int(new_workers))
28
+ if changed:
29
+ shutdown_executor(executor)
30
+ executor = ProcessPoolExecutor(max_workers=int(new_workers))
31
+ return executor, int(new_workers), f"Executor prêt ({int(new_workers)} workers).", True
32
+ else:
33
+ return executor, int(current_workers), f"Executor inchangé ({int(new_workers)} workers).", False
@@ -0,0 +1,168 @@
1
+ # utils_md.py
2
+ import os, re, hashlib, secrets, urllib.parse, logging, time, datetime, numpy as np
3
+ from pathlib import Path
4
+ import fitz # PyMuPDF
5
+
6
+ try:
7
+ import easyocr
8
+ except Exception:
9
+ easyocr = None
10
+
11
+ MAX_STEM_LEN = 40
12
+ IMAGE_RESOLUTION_SCALE = 2.0
13
+
14
+ _log = logging.getLogger(__name__)
15
+ logging.getLogger("fitz").setLevel(logging.ERROR)
16
+ logging.getLogger("PIL").setLevel(logging.WARNING)
17
+
18
+ def slugify(text: str) -> str:
19
+ text = text.strip().lower()
20
+ text = re.sub(r"[^\w\-]+", "-", text)
21
+ text = re.sub(r"-{2,}", "-", text)
22
+ return text.strip("-") or "file"
23
+
24
+ def short_stem(original_stem: str) -> str:
25
+ base = slugify(original_stem)[:MAX_STEM_LEN]
26
+ h = secrets.token_hex(6)
27
+ return f"{base}-{h}"
28
+
29
+ def truncate_path(path, max_length=240):
30
+ p = Path(path)
31
+ s = str(p)
32
+ if len(s) <= max_length:
33
+ return p
34
+ h = hashlib.md5(s.encode()).hexdigest()
35
+ new_name = (p.stem[:50] + "_" + h + p.suffix)
36
+ return p.parent / new_name
37
+
38
+ def try_read_text(path: Path) -> str:
39
+ for enc in ("utf-8", "utf-8-sig", "latin-1"):
40
+ try:
41
+ return urllib.parse.unquote(path.read_text(encoding=enc, errors="ignore"))
42
+ except Exception:
43
+ continue
44
+ return ""
45
+
46
+ def is_pdf_text_based(fpath: Path) -> bool:
47
+ try:
48
+ with fitz.open(fpath) as doc:
49
+ for page in doc:
50
+ if page.get_text().strip():
51
+ return True
52
+ return False
53
+ except Exception:
54
+ return False
55
+
56
+ def ocr_fallback(pdf_path: Path, langs=('fr','en')) -> str:
57
+ if easyocr is None:
58
+ raise RuntimeError("easyocr introuvable. Installez easyocr pour l'OCR.")
59
+ reader = easyocr.Reader(list(langs))
60
+ _ = reader.readtext(np.zeros((10, 10, 3), dtype=np.uint8), detail=0) # warm-up
61
+ text = []
62
+ with fitz.open(pdf_path) as doc:
63
+ for page in doc:
64
+ pix = page.get_pixmap(dpi=200)
65
+ img = np.frombuffer(pix.samples, dtype=np.uint8).reshape((pix.height, pix.width, pix.n))
66
+ result = reader.readtext(img)
67
+ text.extend([r[1] for r in result])
68
+ return "\n".join(text)
69
+
70
+ def strip_image_markdown(md_text: str) -> str:
71
+ md_text = re.sub(r'!\[[^\]]*\]\([^\)]+\)', '', md_text)
72
+ md_text = re.sub(r'<img\b[^>]*>', '', md_text, flags=re.IGNORECASE)
73
+ md_text = re.sub(r'\n{3,}', '\n\n', md_text)
74
+ return md_text.strip()
75
+
76
+ # --- COM helpers (Windows + Office)
77
+ def _make_word_invisible(word):
78
+ try:
79
+ word.Visible = False
80
+ word.DisplayAlerts = 0
81
+ word.ScreenUpdating = False
82
+ except Exception:
83
+ pass
84
+
85
+ def _make_powerpoint_invisible(ppt):
86
+ try:
87
+ ppt.Visible = 0 # 0=Hidden
88
+ except Exception:
89
+ pass
90
+
91
+ def convert_doc_to_docx(src: Path, out_dir: Path) -> Path:
92
+ import win32com.client
93
+ out_dir.mkdir(parents=True, exist_ok=True)
94
+ if src.suffix.lower() != ".doc":
95
+ return src if src.suffix.lower() == ".docx" else src
96
+ dst = out_dir / (src.stem + ".docx")
97
+ if dst.exists() and dst.stat().st_size > 0:
98
+ return dst
99
+ word = win32com.client.Dispatch("Word.Application")
100
+ _make_word_invisible(word)
101
+ try:
102
+ doc = word.Documents.Open(str(src), ReadOnly=True)
103
+ doc.SaveAs(str(dst), FileFormat=16) # DOCX
104
+ doc.Close(False)
105
+ if not dst.exists() or dst.stat().st_size == 0:
106
+ raise RuntimeError("DOCX non généré")
107
+ return dst
108
+ finally:
109
+ word.Quit()
110
+
111
+ def convert_ppt_to_pptx(src: Path, out_dir: Path) -> Path:
112
+ import win32com.client
113
+ out_dir.mkdir(parents=True, exist_ok=True)
114
+ if src.suffix.lower() != ".ppt":
115
+ return src if src.suffix.lower() == ".pptx" else src
116
+ dst = out_dir / (src.stem + ".pptx")
117
+ if dst.exists() and dst.stat().st_size > 0:
118
+ return dst
119
+ ppt = win32com.client.Dispatch("PowerPoint.Application")
120
+ _make_powerpoint_invisible(ppt)
121
+ try:
122
+ pres = ppt.Presentations.Open(str(src), WithWindow=False)
123
+ pres.SaveAs(str(dst), 24) # ppSaveAsOpenXMLPresentation
124
+ pres.Close()
125
+ if not dst.exists() or dst.stat().st_size == 0:
126
+ raise RuntimeError("PPTX non généré")
127
+ return dst
128
+ finally:
129
+ ppt.Quit()
130
+
131
+ def docx_to_pdf(src_docx: Path, out_dir: Path) -> Path:
132
+ import win32com.client
133
+ out_dir.mkdir(parents=True, exist_ok=True)
134
+ pdf_path = out_dir / (src_docx.stem + ".pdf")
135
+ if pdf_path.exists() and pdf_path.stat().st_size > 0:
136
+ return pdf_path
137
+ word = win32com.client.Dispatch("Word.Application")
138
+ _make_word_invisible(word)
139
+ try:
140
+ doc = word.Documents.Open(str(src_docx), ReadOnly=True)
141
+ try:
142
+ doc.ExportAsFixedFormat(OutputFileName=str(pdf_path), ExportFormat=17)
143
+ except Exception:
144
+ doc.SaveAs(str(pdf_path), FileFormat=17)
145
+ doc.Close(False)
146
+ if not pdf_path.exists() or pdf_path.stat().st_size == 0:
147
+ raise RuntimeError("PDF non généré depuis DOCX")
148
+ return pdf_path
149
+ finally:
150
+ word.Quit()
151
+
152
+ def pptx_to_pdf(src_pptx: Path, out_dir: Path) -> Path:
153
+ import win32com.client
154
+ out_dir.mkdir(parents=True, exist_ok=True)
155
+ pdf_path = out_dir / (src_pptx.stem + ".pdf")
156
+ if pdf_path.exists() and pdf_path.stat().st_size > 0:
157
+ return pdf_path
158
+ ppt = win32com.client.Dispatch("PowerPoint.Application")
159
+ _make_powerpoint_invisible(ppt)
160
+ try:
161
+ pres = ppt.Presentations.Open(str(src_pptx), WithWindow=False)
162
+ pres.SaveAs(str(pdf_path), 32) # PDF
163
+ pres.Close()
164
+ if not pdf_path.exists() or pdf_path.stat().st_size == 0:
165
+ raise RuntimeError("PDF non généré depuis PPTX")
166
+ return pdf_path
167
+ finally:
168
+ ppt.Quit()
@@ -165,7 +165,7 @@ class OWExportMarkdown(widget.OWWidget):
165
165
 
166
166
  try:
167
167
  # DOCX
168
- pypandoc.convert_file(tmp_md, to="docx", outputfile=docx_out)
168
+ pypandoc.convert_file(tmp_md, to="docx", format="gfm-yaml_metadata_block", outputfile=docx_out)
169
169
  self.ajouter_en_tete_pied_docx(
170
170
  docx_out,
171
171
  "Rapport - Orange AI",
@@ -173,7 +173,7 @@ class OWExportMarkdown(widget.OWWidget):
173
173
  )
174
174
 
175
175
  # PPTX
176
- pypandoc.convert_file(tmp_md, to="pptx", outputfile=pptx_out)
176
+ pypandoc.convert_file(tmp_md, to="pptx", format="gfm-yaml_metadata_block", outputfile=pptx_out)
177
177
  self.ajouter_entete_pied_pptx(
178
178
  pptx_out,
179
179
  "Orange AI – Présentation",