AIEmailAutomationUtility 0.0.31__py3-none-any.whl → 0.0.33__py3-none-any.whl
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.
- AIEmailAutomationUtility/Email_DocumentUploader.py +0 -1
- AIEmailAutomationUtility/Email_Draft.py +1 -0
- AIEmailAutomationUtility/Email_Read.py +24 -11
- AIEmailAutomationUtility/Process_Category.py +84 -19
- {AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/METADATA +3 -3
- AIEmailAutomationUtility-0.0.33.dist-info/RECORD +15 -0
- AIEmailAutomationUtility-0.0.31.dist-info/RECORD +0 -15
- {AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/LICENCE.txt +0 -0
- {AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/WHEEL +0 -0
- {AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ from datetime import datetime
|
|
7
7
|
import threading
|
8
8
|
import traceback
|
9
9
|
import json
|
10
|
-
import os
|
10
|
+
import os, shutil
|
11
11
|
import csv
|
12
12
|
|
13
13
|
import io
|
@@ -112,7 +112,7 @@ class Email_Read:
|
|
112
112
|
login_status = "Success"
|
113
113
|
mail.select('inbox')
|
114
114
|
|
115
|
-
file_JsonArray, categories = self.read_JSON_File(templateName)
|
115
|
+
file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
|
116
116
|
|
117
117
|
except Exception as e:
|
118
118
|
login_status = "Failed"
|
@@ -251,7 +251,7 @@ class Email_Read:
|
|
251
251
|
login_status = "Success"
|
252
252
|
mail.select('inbox')
|
253
253
|
|
254
|
-
file_JsonArray, categories = self.read_JSON_File(templateName)
|
254
|
+
file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
|
255
255
|
|
256
256
|
except Exception as e:
|
257
257
|
logger.log(f"Login failed: {e}")
|
@@ -484,7 +484,7 @@ class Email_Read:
|
|
484
484
|
templateName = "ai_email_automation.json"
|
485
485
|
fileName = ""
|
486
486
|
|
487
|
-
file_JsonArray, categories = self.read_JSON_File(templateName)
|
487
|
+
file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
|
488
488
|
# Call the `extract_all_email_info` method to extract details from the eml content
|
489
489
|
extracted_info,msg = self.extract_all_email_info(eml_content)
|
490
490
|
|
@@ -552,24 +552,37 @@ class Email_Read:
|
|
552
552
|
|
553
553
|
return "success"
|
554
554
|
|
555
|
-
def read_JSON_File(self, json_fileName):
|
555
|
+
def read_JSON_File(self, json_fileName, user_id):
|
556
556
|
category_list = []
|
557
557
|
categories = ""
|
558
558
|
try:
|
559
|
-
|
560
|
-
|
561
|
-
|
559
|
+
logger.log(f"\nEmail_Read() read_JSON_File user_id ::: {user_id}")
|
560
|
+
user_file = json_fileName
|
561
|
+
if user_id:
|
562
|
+
user_dir = os.path.join('user_data', user_id)
|
563
|
+
logger.log(f"\nEmail_Read() read_JSON_File user_dir ::: {user_dir}")
|
564
|
+
if not os.path.exists(user_dir):
|
565
|
+
os.makedirs(user_dir, exist_ok=True)
|
566
|
+
user_file = os.path.join(user_dir, json_fileName)
|
567
|
+
if not os.path.exists(user_file) and os.path.exists(json_fileName):
|
568
|
+
shutil.copy(json_fileName, user_file)
|
569
|
+
|
570
|
+
logger.log(f"\nEmail_Read() read_JSON_File user_file ::: {user_file}")
|
571
|
+
|
572
|
+
if os.path.exists(user_file):
|
573
|
+
with open(user_file, "r") as fileObj:
|
574
|
+
file_JsonArray = json.load(fileObj)
|
562
575
|
|
563
576
|
for eachJson in file_JsonArray :
|
564
577
|
for key, value in eachJson.items():
|
565
|
-
if key == "Category"
|
578
|
+
if key == "Category":
|
566
579
|
category_list.append(value)
|
567
580
|
# categories = ", ".join(category_list)
|
568
581
|
|
569
582
|
return file_JsonArray, category_list
|
570
583
|
|
571
584
|
else:
|
572
|
-
message = f"{
|
585
|
+
message = f"{user_file} file not found."
|
573
586
|
raise Exception(message)
|
574
587
|
except Exception as e:
|
575
588
|
msg = f"'{json_fileName}' file is empty. Please provide JSON parameters in the filename."
|
@@ -579,7 +592,7 @@ class Email_Read:
|
|
579
592
|
|
580
593
|
def log_email_login(self, user_id, email, model_name, login_status):
|
581
594
|
base_dir="EMail_log"
|
582
|
-
timestamp = datetime.now().strftime("%Y-%m-%
|
595
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
583
596
|
log_dir = os.path.join(base_dir, user_id)
|
584
597
|
os.makedirs(log_dir, exist_ok=True)
|
585
598
|
log_file_path = os.path.join(log_dir, f"{user_id}.csv")
|
@@ -14,6 +14,8 @@ import openai
|
|
14
14
|
import json
|
15
15
|
import csv
|
16
16
|
import os
|
17
|
+
from email import message_from_string
|
18
|
+
import re
|
17
19
|
|
18
20
|
class Process_Category:
|
19
21
|
|
@@ -106,12 +108,13 @@ class Process_Category:
|
|
106
108
|
logger.log(f"document_type 108::: {document_type}")
|
107
109
|
|
108
110
|
if responseMethod == "Upload_Document":
|
109
|
-
# Get today's date folder path
|
110
|
-
today_date = datetime.today().strftime('%Y-%m-%d')
|
111
|
-
order_folder = os.path.join(document_type, today_date)
|
112
111
|
|
113
112
|
email_upload_document = Email_DocumentUploader()
|
114
113
|
if len(fileName) != 0 and document_type != '':
|
114
|
+
# Get today's date folder path
|
115
|
+
today_date = datetime.today().strftime('%Y-%m-%d')
|
116
|
+
order_folder = os.path.join(document_type, today_date)
|
117
|
+
|
115
118
|
file_path = os.path.join(order_folder, fileName) # Correct file path
|
116
119
|
|
117
120
|
with open(file_path, "rb") as file:
|
@@ -128,7 +131,22 @@ class Process_Category:
|
|
128
131
|
|
129
132
|
parameters["DOCUMENT_TYPE"] = document_type
|
130
133
|
logger.log(f"Updated Parameters ::: {parameters}")
|
131
|
-
|
134
|
+
|
135
|
+
email_parts = []
|
136
|
+
if sender_email_addr:
|
137
|
+
email_parts.append(f"From: {sender_email_addr}")
|
138
|
+
if to_email_addr:
|
139
|
+
email_parts.append(f"To: {to_email_addr}")
|
140
|
+
if cc_email_addr:
|
141
|
+
email_parts.append(f"CC: {cc_email_addr}")
|
142
|
+
if subject:
|
143
|
+
email_parts.append(f"Subject: {subject}")
|
144
|
+
|
145
|
+
email_parts.append(email_body)
|
146
|
+
email_body_with_details = "\n".join(email_parts)
|
147
|
+
|
148
|
+
logger.log(f"email_body ::: {email_body}")
|
149
|
+
new_fileName = self.create_file_from_emailBody(email_body_with_details, sender_email_addr, parameters)
|
132
150
|
new_file_path = os.path.join(order_folder, new_fileName)
|
133
151
|
|
134
152
|
with open(new_file_path, "rb") as file:
|
@@ -161,12 +179,11 @@ class Process_Category:
|
|
161
179
|
# Extract customer code once
|
162
180
|
customer_code = customer_data.get("customer_code", "")
|
163
181
|
|
164
|
-
|
165
|
-
# Step 5: Identify product from email using AI
|
166
|
-
|
182
|
+
# Step 5: Identify product from email using AI
|
167
183
|
#If there is attachment then append the content in the body.
|
168
184
|
extracted_content = dataValues.get('extracted_content')
|
169
|
-
|
185
|
+
logger.log(f"extracted_content is ::: {extracted_content}")
|
186
|
+
if extracted_content != "NA" and extracted_content != None:
|
170
187
|
attachment_email_body =email_body + " \n\n" + extracted_content
|
171
188
|
products = self.identify_products(attachment_email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Product_Assistant_Id"])
|
172
189
|
else:
|
@@ -366,7 +383,7 @@ class Process_Category:
|
|
366
383
|
mail.expunge()
|
367
384
|
logger.log(f"Mail removed from inbox and added to '{LABEL}' label.")
|
368
385
|
|
369
|
-
current_timestamp = datetime.now().strftime("%Y-%m-%
|
386
|
+
current_timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
370
387
|
filename = f'{uid}{current_timestamp}'
|
371
388
|
logger.log(f"The file name for csv and eml is ::: {filename}")
|
372
389
|
self.store_email_details_to_csv(
|
@@ -426,7 +443,7 @@ class Process_Category:
|
|
426
443
|
filename = part.get_filename()
|
427
444
|
mime_type = part.get_content_type().lower()
|
428
445
|
|
429
|
-
if "pdf" in mime_type:
|
446
|
+
if "pdf" in mime_type or filename.lower().endswith(".pdf"):
|
430
447
|
document_type = "Orders"
|
431
448
|
elif "msword" in mime_type or "wordprocessingml" in mime_type or filename.lower().endswith(".docx"):
|
432
449
|
document_type = "Order Excel"
|
@@ -438,12 +455,12 @@ class Process_Category:
|
|
438
455
|
document_type = "Order Email"
|
439
456
|
elif "rtf" in mime_type or filename.lower().endswith(".rtf"):
|
440
457
|
document_type = "Order Excel"
|
441
|
-
|
442
|
-
today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
|
443
|
-
date_folder = os.path.join(document_type, today_date) # Path: ORDERS/YYYY-MM-DD
|
444
|
-
os.makedirs(date_folder, exist_ok=True)
|
445
458
|
|
446
459
|
if filename:
|
460
|
+
today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
|
461
|
+
date_folder = os.path.join(document_type, today_date) # Path: ORDERS/YYYY-MM-DD
|
462
|
+
os.makedirs(date_folder, exist_ok=True)
|
463
|
+
|
447
464
|
filepath = os.path.join(date_folder, filename) # Save inside date-wise folder
|
448
465
|
|
449
466
|
with open(filepath, 'wb') as f:
|
@@ -479,7 +496,7 @@ class Process_Category:
|
|
479
496
|
fileName = fileName + ".txt"
|
480
497
|
filePath = os.path.join(order_folder, fileName)
|
481
498
|
|
482
|
-
with open(filePath, "w") as file:
|
499
|
+
with open(filePath, "w", encoding="utf-8") as file:
|
483
500
|
file.write(text)
|
484
501
|
logger.log(f"New TXT file created from email body and stored in '{filePath}'")
|
485
502
|
else:
|
@@ -525,7 +542,7 @@ class Process_Category:
|
|
525
542
|
logger.log("Inside identify_products")
|
526
543
|
|
527
544
|
if model_type == "OpenAI":
|
528
|
-
prompt = f"""/* Extract complete item pricing information from the following mixed-format email content. The email may contain a combination of:- descriptive product listings- tabular structured price info- semi-structured lines with HSN, quantity, material, etc. Give me price in INR and information of all items in following format requested_description, item_no, make, description, price, price unit, inventory unit strictly in JSON format. If the item is not available in the assistant files return requested description from email data and rest of the columns as NA. Do not include any instruction as the output will be directly in a program. */ /n {email_body}. """
|
545
|
+
prompt = f"""/* Extract complete item pricing information from the following mixed-format email content. The email may contain a combination of:- descriptive product listings- tabular structured price info- semi-structured lines with HSN, quantity, material, etc. Give me price in INR and information of all items in following format requested_description, item_no, make, description, price, Quantity, price unit, inventory unit strictly in JSON format. If the item is not available in the assistant files return requested description from email data and rest of the columns as NA. Do not include any instruction as the output will be directly in a program. */ /n {email_body}. """
|
529
546
|
emailreplyassistant = EmailReplyAssistant()
|
530
547
|
ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
|
531
548
|
|
@@ -554,13 +571,31 @@ class Process_Category:
|
|
554
571
|
return product_data
|
555
572
|
|
556
573
|
def generate_quotation_draft(self, customer_data, products, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id, email_body, subject, signature):
|
557
|
-
logger.log("Inside generate_quotation_draft")
|
558
574
|
|
559
575
|
customer = customer_data
|
560
576
|
|
561
577
|
product_table = "Products:\n"
|
562
578
|
for product in products:
|
563
|
-
|
579
|
+
rate = product.get("rate", "NA")
|
580
|
+
quantity = product.get("Quantity", "NA")
|
581
|
+
try:
|
582
|
+
total = float(rate) * float(quantity)
|
583
|
+
except:
|
584
|
+
total = rate if rate != "NA" and rate not in ("", None) else "-"
|
585
|
+
product_table += (
|
586
|
+
f'- Requested Description: {product.get("requested_description", "-")}, '
|
587
|
+
f'Item Code: {product.get("item_no", "-")}, '
|
588
|
+
f'Item Description: {product.get("description", "-")}, '
|
589
|
+
f'Manufacturer: {product.get("make", "-")}, '
|
590
|
+
f'Price: {product.get("price", "-")}, '
|
591
|
+
f'Inventory Unit: {product.get("inventory unit", "-")}, '
|
592
|
+
f'Discount: {product.get("Discount", "-")}, '
|
593
|
+
f'Rate: {rate}, '
|
594
|
+
f'Quantity: {quantity}, '
|
595
|
+
f'Total: {total}, '
|
596
|
+
f'Price Pickup Source: {product.get("Price Pickup Source", "-")}, '
|
597
|
+
f'Availability: ""\n'
|
598
|
+
)
|
564
599
|
|
565
600
|
if model_type == "OpenAI":
|
566
601
|
prompt = f"""
|
@@ -570,7 +605,12 @@ class Process_Category:
|
|
570
605
|
Customer Code: {customer.get('customer_code', '')}
|
571
606
|
|
572
607
|
{product_table}
|
573
|
-
|
608
|
+
Ensure the table has the following columns in this exact order:
|
609
|
+
Sr. No., Requested Description, Item Code, Item Description, Manufacturer, Price, Inventory Unit, Discount, Rate, Quantity, Total, Price Pickup Source, Availability
|
610
|
+
|
611
|
+
- If any value is missing, use a dash ("-") instead.
|
612
|
+
- "Total" is calculated as Rate × Quantity.
|
613
|
+
- "Availability" should be a blank column.
|
574
614
|
Original Email Subject: {subject}
|
575
615
|
|
576
616
|
Return only the following JSON String format:
|
@@ -787,6 +827,17 @@ class Process_Category:
|
|
787
827
|
with the name user_id.csv.
|
788
828
|
"""
|
789
829
|
|
830
|
+
logger.log(f"The response am getting in csv is : {response}")
|
831
|
+
match = re.search(
|
832
|
+
r'Content-Transfer-Encoding:\s*base64\s+([\s\S]+?)\n--',
|
833
|
+
response,
|
834
|
+
re.IGNORECASE
|
835
|
+
)
|
836
|
+
if match:
|
837
|
+
response = self.extract_html_from_mime(response)
|
838
|
+
|
839
|
+
logger.log(f"The response after am getting in csv is : {response}")
|
840
|
+
|
790
841
|
# Ensure the Mail_log folder exists
|
791
842
|
log_folder = "Mail_log"
|
792
843
|
os.makedirs(log_folder, exist_ok=True)
|
@@ -910,4 +961,18 @@ class Process_Category:
|
|
910
961
|
if user_id:
|
911
962
|
return user_id.value
|
912
963
|
return None
|
964
|
+
|
965
|
+
def extract_html_from_mime(self, raw_data):
|
966
|
+
msg = message_from_string(raw_data)
|
967
|
+
|
968
|
+
if msg.is_multipart():
|
969
|
+
for part in msg.walk():
|
970
|
+
content_type = part.get_content_type()
|
971
|
+
content_encoding = part.get("Content-Transfer-Encoding", "").lower()
|
972
|
+
|
973
|
+
# Look for text/html part with base64 encoding
|
974
|
+
if content_type == "text/html" and content_encoding == "base64":
|
975
|
+
payload = part.get_payload(decode=True)
|
976
|
+
return payload.decode(part.get_content_charset() or 'utf-8')
|
977
|
+
return "No HTML content found."
|
913
978
|
|
{AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/METADATA
RENAMED
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: AIEmailAutomationUtility
|
3
|
-
Version: 0.0.
|
4
|
-
Summary:
|
3
|
+
Version: 0.0.33
|
4
|
+
Summary: Added new columns and Modified the draft prompt to return draft in consistent format for all mails
|
5
5
|
Author: Proteus Technology PVT. LTD.
|
6
6
|
Author-email: <apps@baseinformation.com>
|
7
7
|
Keywords: python,first package
|
@@ -13,4 +13,4 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
13
13
|
Classifier: Operating System :: Microsoft :: Windows
|
14
14
|
License-File: LICENCE.txt
|
15
15
|
|
16
|
-
|
16
|
+
Added new columns and Modified the draft prompt to return draft in consistent format for all mails
|
@@ -0,0 +1,15 @@
|
|
1
|
+
AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=R_wJna3-ITsVxQEccryhM93T_Nf_Oxo8DXnS-sDN8VE,6679
|
2
|
+
AIEmailAutomationUtility/Email_Classification.py,sha256=Ar0g4Ff8HOT7xICktd3nP_C_vCyeY-xCpUjVCVRWAyc,9417
|
3
|
+
AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=BWNRt2X-E2HCogBaKDfl7cZZNSkZUeIsVs8iXjFjH88,3218
|
4
|
+
AIEmailAutomationUtility/Email_Draft.py,sha256=JYZijUh_zan2asyMYQwIBwIpGNJ5SSQGma5AL1meaXk,7808
|
5
|
+
AIEmailAutomationUtility/Email_Read.py,sha256=Ehzx9SKjzCgWY9K4rWCnwokDzo6M0Nbhi2XLKYpeEis,31451
|
6
|
+
AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
|
7
|
+
AIEmailAutomationUtility/Process_Category.py,sha256=9X3b2BEw31T6RmJ66g0zMP8WYVlVqi8CDGMIsR6ATbU,47741
|
8
|
+
AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
|
9
|
+
AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
|
10
|
+
AIEmailAutomationUtility/__init__.py,sha256=Jad3IdPRsVMeLqEEh-FbCrc1lE2tzJO2DTG5Hgmxh5g,391
|
11
|
+
AIEmailAutomationUtility-0.0.33.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
|
12
|
+
AIEmailAutomationUtility-0.0.33.dist-info/METADATA,sha256=9kdhPKSz1FqZiWMkeQq6GFK3TefmevbwWikMIem7BHw,699
|
13
|
+
AIEmailAutomationUtility-0.0.33.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
14
|
+
AIEmailAutomationUtility-0.0.33.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
|
15
|
+
AIEmailAutomationUtility-0.0.33.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=R_wJna3-ITsVxQEccryhM93T_Nf_Oxo8DXnS-sDN8VE,6679
|
2
|
-
AIEmailAutomationUtility/Email_Classification.py,sha256=Ar0g4Ff8HOT7xICktd3nP_C_vCyeY-xCpUjVCVRWAyc,9417
|
3
|
-
AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=YJu4tuTHr0K-5vuds9gZfj-Hwsgm4MuAOP39Lmu_t98,3219
|
4
|
-
AIEmailAutomationUtility/Email_Draft.py,sha256=DcyBeDaE8CReKHnHxLiz-o2tDxuUgwy91c4k0qhQbVw,7749
|
5
|
-
AIEmailAutomationUtility/Email_Read.py,sha256=6obif-ZLhxuatNcGhO8en_R7-Q56EOhzeyILb8vZBjM,30743
|
6
|
-
AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
|
7
|
-
AIEmailAutomationUtility/Process_Category.py,sha256=Rhs_flyv-_LvDzu6aO_i-ZDjnMKWDIP_mAcqf8mkl_Y,44607
|
8
|
-
AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
|
9
|
-
AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
|
10
|
-
AIEmailAutomationUtility/__init__.py,sha256=Jad3IdPRsVMeLqEEh-FbCrc1lE2tzJO2DTG5Hgmxh5g,391
|
11
|
-
AIEmailAutomationUtility-0.0.31.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
|
12
|
-
AIEmailAutomationUtility-0.0.31.dist-info/METADATA,sha256=E_KUzOjVXHaejQTsqUsfCZqLQYTWJQIFY4xUrZufw_o,701
|
13
|
-
AIEmailAutomationUtility-0.0.31.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
14
|
-
AIEmailAutomationUtility-0.0.31.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
|
15
|
-
AIEmailAutomationUtility-0.0.31.dist-info/RECORD,,
|
{AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/LICENCE.txt
RENAMED
File without changes
|
{AIEmailAutomationUtility-0.0.31.dist-info → AIEmailAutomationUtility-0.0.33.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|