django-outlook-sync 1.0.0__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.
- django_outlook_sync-1.0.0/PKG-INFO +22 -0
- django_outlook_sync-1.0.0/README.md +8 -0
- django_outlook_sync-1.0.0/django_outlook_sync/__init__.py +0 -0
- django_outlook_sync-1.0.0/django_outlook_sync/apps.py +6 -0
- django_outlook_sync-1.0.0/django_outlook_sync/bin/__init__.py +0 -0
- django_outlook_sync-1.0.0/django_outlook_sync/bin/auto_sync.py +52 -0
- django_outlook_sync-1.0.0/django_outlook_sync/management/__init__.py +0 -0
- django_outlook_sync-1.0.0/django_outlook_sync/management/commands/__init__.py +0 -0
- django_outlook_sync-1.0.0/django_outlook_sync/management/commands/sync_outlook.py +19 -0
- django_outlook_sync-1.0.0/django_outlook_sync/models.py +18 -0
- django_outlook_sync-1.0.0/django_outlook_sync/outlook.py +18 -0
- django_outlook_sync-1.0.0/django_outlook_sync/serializers.py +18 -0
- django_outlook_sync-1.0.0/django_outlook_sync/sync.py +153 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/PKG-INFO +22 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/SOURCES.txt +19 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/dependency_links.txt +1 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/entry_points.txt +2 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/requires.txt +5 -0
- django_outlook_sync-1.0.0/django_outlook_sync.egg-info/top_level.txt +1 -0
- django_outlook_sync-1.0.0/pyproject.toml +30 -0
- django_outlook_sync-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-outlook-sync
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A standalone reusable Django package to automate background syncing of local desktop Outlook emails.
|
|
5
|
+
Author-email: Yonas <yonas2754@gmail.com>
|
|
6
|
+
Classifier: Framework :: Django
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pywin32; sys_platform == "win32"
|
|
12
|
+
Requires-Dist: schedule>=1.2.0
|
|
13
|
+
Requires-Dist: djangorestframework>=3.14.0
|
|
14
|
+
|
|
15
|
+
# Django Outlook Sync Engine
|
|
16
|
+
|
|
17
|
+
A reusable Django module to sync structural message items seamlessly from localized Outlook desktop software installations into individual app environments.
|
|
18
|
+
|
|
19
|
+
## Setup Instructions
|
|
20
|
+
1. Install your local package.
|
|
21
|
+
2. Register `'django_outlook_sync'` in your `INSTALLED_APPS`.
|
|
22
|
+
3. Run `python manage.py migrate`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Django Outlook Sync Engine
|
|
2
|
+
|
|
3
|
+
A reusable Django module to sync structural message items seamlessly from localized Outlook desktop software installations into individual app environments.
|
|
4
|
+
|
|
5
|
+
## Setup Instructions
|
|
6
|
+
1. Install your local package.
|
|
7
|
+
2. Register `'django_outlook_sync'` in your `INSTALLED_APPS`.
|
|
8
|
+
3. Run `python manage.py migrate`.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import subprocess
|
|
5
|
+
import schedule
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
def run_django_sync():
|
|
9
|
+
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
10
|
+
print(f"[{current_time}] Triggering automated Outlook database sync...")
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Executes within context of target project using active runtime interpreter variables
|
|
14
|
+
result = subprocess.run(
|
|
15
|
+
[sys.executable, "manage.py", "sync_outlook"],
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
log_file_path = "sync_log.txt"
|
|
21
|
+
with open(log_file_path, "a") as log:
|
|
22
|
+
log.write(f"--- Sync executed at {current_time} ---\n")
|
|
23
|
+
log.write(result.stdout)
|
|
24
|
+
if result.stderr:
|
|
25
|
+
log.write(f"Errors:\n{result.stderr}")
|
|
26
|
+
log.write("\n")
|
|
27
|
+
|
|
28
|
+
print(f"[{current_time}] Sync pipeline finished successfully.")
|
|
29
|
+
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"[{current_time}] Critical automation failure: {str(e)}")
|
|
32
|
+
|
|
33
|
+
def main():
|
|
34
|
+
if not os.path.exists("manage.py"):
|
|
35
|
+
print("❌ Error: 'manage.py' not found! Open your terminal in a valid Django project root folder to run this engine daemon.")
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
schedule.every(5).minutes.do(run_django_sync)
|
|
39
|
+
run_django_sync()
|
|
40
|
+
|
|
41
|
+
print("⏰ Outlook background daemon initialized. Keeping alive... (Press Ctrl+C to exit)")
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
schedule.run_pending()
|
|
46
|
+
time.sleep(1)
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
print("\nStopping Outlook background daemon. Goodbye!")
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from django_outlook_sync.outlook import OutlookService
|
|
3
|
+
from django_outlook_sync.sync import SyncService
|
|
4
|
+
|
|
5
|
+
class Command(BaseCommand):
|
|
6
|
+
help = "Sync outlook emails from running desktop application client environment"
|
|
7
|
+
|
|
8
|
+
def handle(self, *args, **options):
|
|
9
|
+
self.stdout.write("Initializing local COM Connection parameters...")
|
|
10
|
+
try:
|
|
11
|
+
outlook = OutlookService()
|
|
12
|
+
messages = outlook.get_messages()
|
|
13
|
+
|
|
14
|
+
self.stdout.write("Running processing pipelines...")
|
|
15
|
+
SyncService().run(messages)
|
|
16
|
+
|
|
17
|
+
self.stdout.write(self.style.SUCCESS("Outlook database sync completed successfully."))
|
|
18
|
+
except Exception as e:
|
|
19
|
+
self.stdout.write(self.style.ERROR(f"Sync operation encountered failures: {str(e)}"))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
class EmailMessage(models.Model):
|
|
4
|
+
message_id = models.CharField(max_length=255, unique=True)
|
|
5
|
+
subject = models.CharField(max_length=500, blank=True, null=True)
|
|
6
|
+
sender = models.CharField(max_length=255, blank=True, null=True)
|
|
7
|
+
recipients = models.TextField(blank=True, null=True)
|
|
8
|
+
cc_recipients = models.TextField(blank=True, null=True)
|
|
9
|
+
html_body = models.TextField(blank=True, null=True)
|
|
10
|
+
received_at = models.DateTimeField()
|
|
11
|
+
is_thread_reply = models.BooleanField(default=False)
|
|
12
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
13
|
+
|
|
14
|
+
class Meta:
|
|
15
|
+
db_table = 'outlook_email_messages'
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"{self.subject or 'No Subject'} - {self.sender or 'Unknown'}"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
class OutlookService:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
if sys.platform != "win32":
|
|
6
|
+
raise OSError("OutlookService requires a Windows Environment running Microsoft Outlook.")
|
|
7
|
+
|
|
8
|
+
import win32com.client
|
|
9
|
+
import pythoncom
|
|
10
|
+
|
|
11
|
+
pythoncom.CoInitialize()
|
|
12
|
+
self.outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
|
|
13
|
+
|
|
14
|
+
def get_messages(self):
|
|
15
|
+
inbox = self.outlook.GetDefaultFolder(6)
|
|
16
|
+
messages = inbox.Items
|
|
17
|
+
messages.Sort("[ReceivedTime]", True)
|
|
18
|
+
return messages
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from .models import EmailMessage
|
|
3
|
+
|
|
4
|
+
class EmailMessageSerializer(serializers.ModelSerializer):
|
|
5
|
+
class Meta:
|
|
6
|
+
model = EmailMessage
|
|
7
|
+
fields = [
|
|
8
|
+
'id',
|
|
9
|
+
'message_id',
|
|
10
|
+
'subject',
|
|
11
|
+
'sender',
|
|
12
|
+
'recipients',
|
|
13
|
+
'cc_recipients',
|
|
14
|
+
'received_at',
|
|
15
|
+
'is_thread_reply',
|
|
16
|
+
'created_at',
|
|
17
|
+
'html_body',
|
|
18
|
+
]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.utils.timezone import make_aware
|
|
7
|
+
from django.db import transaction, IntegrityError
|
|
8
|
+
|
|
9
|
+
from .models import EmailMessage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SyncService:
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
# Fetch configurations from parent project settings with safe default fallbacks
|
|
16
|
+
self.allowed_recipients = getattr(settings, "OUTLOOK_ALLOWED_RECIPIENTS", [])
|
|
17
|
+
self.auto_reply_template = getattr(
|
|
18
|
+
settings,
|
|
19
|
+
"OUTLOOK_REPLY_TEMPLATE",
|
|
20
|
+
"<p>Hello,</p><p>Your email has been received and processed.</p><p>Thank you.</p>"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def is_valid_recipient(self, to_field, cc_field):
|
|
24
|
+
if not self.allowed_recipients:
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
all_recipients = f"{to_field} {cc_field}".lower()
|
|
28
|
+
return all(name.lower() in all_recipients for name in self.allowed_recipients)
|
|
29
|
+
|
|
30
|
+
def run(self, messages):
|
|
31
|
+
now = datetime.now()
|
|
32
|
+
five_minutes_ago = now - timedelta(minutes=5)
|
|
33
|
+
saved_count = 0
|
|
34
|
+
|
|
35
|
+
output_dir = os.path.join(settings.BASE_DIR, "static", "email_images")
|
|
36
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
messages.Sort("[ReceivedTime]", True)
|
|
40
|
+
messages_list = list(messages)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Unable to natively sort messages: {e}")
|
|
43
|
+
messages_list = list(messages)
|
|
44
|
+
|
|
45
|
+
print(f"Processing {len(messages_list)} messages")
|
|
46
|
+
|
|
47
|
+
for msg in messages_list:
|
|
48
|
+
try:
|
|
49
|
+
if getattr(msg, "Class", 0) != 43:
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
message_id = getattr(msg, "EntryID", None)
|
|
53
|
+
if not message_id:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
raw_time = getattr(msg, "ReceivedTime", None)
|
|
57
|
+
if not raw_time:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
naive_dt = datetime(
|
|
61
|
+
raw_time.year, raw_time.month, raw_time.day,
|
|
62
|
+
raw_time.hour, raw_time.minute, raw_time.second,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if naive_dt < five_minutes_ago:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
subject = getattr(msg, "Subject", "No Subject")
|
|
69
|
+
sender = getattr(msg, "SenderName", "Unknown Sender")
|
|
70
|
+
received_at = make_aware(naive_dt)
|
|
71
|
+
|
|
72
|
+
to_recipients = getattr(msg, "To", "")
|
|
73
|
+
cc_recipients = getattr(msg, "CC", "")
|
|
74
|
+
|
|
75
|
+
if not self.is_valid_recipient(to_recipients, cc_recipients):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
id_exists = EmailMessage.objects.filter(message_id=message_id).exists()
|
|
79
|
+
footprint_exists = EmailMessage.objects.filter(
|
|
80
|
+
sender=sender, subject=subject, received_at=received_at
|
|
81
|
+
).exists()
|
|
82
|
+
|
|
83
|
+
if id_exists or footprint_exists:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
html_body = getattr(msg, "HTMLBody", "")
|
|
87
|
+
|
|
88
|
+
if html_body and "Your email has been received and processed." in html_body:
|
|
89
|
+
print(f"Skipping email '{subject}'—loop protection.")
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
print(f"Processing New Email: {subject} from {sender}")
|
|
93
|
+
|
|
94
|
+
if html_body:
|
|
95
|
+
html_body = re.sub(
|
|
96
|
+
r"IMPORTANT\..*?actually\s+virus\s+free\s*\.?",
|
|
97
|
+
"", html_body, flags=re.DOTALL | re.IGNORECASE,
|
|
98
|
+
)
|
|
99
|
+
html_body = re.sub(r"<p[^>]*>\s*</p>", "", html_body)
|
|
100
|
+
html_body = re.sub(r"<div[^>]*>\s*</div>", "", html_body)
|
|
101
|
+
|
|
102
|
+
safe_msg_id = re.sub(r"[^a-zA-Z0-9]", "", str(message_id))[-20:]
|
|
103
|
+
attachments = getattr(msg, "Attachments", None)
|
|
104
|
+
|
|
105
|
+
if attachments and attachments.Count > 0:
|
|
106
|
+
for i in range(1, attachments.Count + 1):
|
|
107
|
+
attachment = attachments.Item(i)
|
|
108
|
+
try:
|
|
109
|
+
cid = attachment.PropertyAccessor.GetProperty(
|
|
110
|
+
"http://schemas.microsoft.com/mapi/proptag/0x3712001E"
|
|
111
|
+
)
|
|
112
|
+
except Exception:
|
|
113
|
+
cid = None
|
|
114
|
+
|
|
115
|
+
if cid:
|
|
116
|
+
file_ext = os.path.splitext(attachment.FileName)[1] or ".png"
|
|
117
|
+
filename = f"{safe_msg_id}_{cid}{file_ext}"
|
|
118
|
+
file_path = os.path.join(output_dir, filename)
|
|
119
|
+
attachment.SaveAsFile(file_path)
|
|
120
|
+
|
|
121
|
+
html_body = html_body.replace(
|
|
122
|
+
f"cid:{cid}", f"/static/email_images/{filename}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
is_reply = subject and subject.lower().strip().startswith(("re:", "fw:"))
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with transaction.atomic():
|
|
129
|
+
EmailMessage.objects.create(
|
|
130
|
+
message_id=message_id,
|
|
131
|
+
subject=subject,
|
|
132
|
+
sender=sender,
|
|
133
|
+
recipients=to_recipients,
|
|
134
|
+
cc_recipients=cc_recipients,
|
|
135
|
+
html_body=html_body,
|
|
136
|
+
received_at=received_at,
|
|
137
|
+
is_thread_reply=is_reply,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
reply = msg.Reply()
|
|
141
|
+
reply.HTMLBody = self.auto_reply_template + reply.HTMLBody
|
|
142
|
+
reply.Send()
|
|
143
|
+
saved_count += 1
|
|
144
|
+
|
|
145
|
+
except IntegrityError:
|
|
146
|
+
print(f"Duplicate blocked: {subject}")
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f"Skipping unreadable email: {e}")
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
print(f"Sync completed: {saved_count} emails processed since {five_minutes_ago}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-outlook-sync
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A standalone reusable Django package to automate background syncing of local desktop Outlook emails.
|
|
5
|
+
Author-email: Yonas <yonas2754@gmail.com>
|
|
6
|
+
Classifier: Framework :: Django
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pywin32; sys_platform == "win32"
|
|
12
|
+
Requires-Dist: schedule>=1.2.0
|
|
13
|
+
Requires-Dist: djangorestframework>=3.14.0
|
|
14
|
+
|
|
15
|
+
# Django Outlook Sync Engine
|
|
16
|
+
|
|
17
|
+
A reusable Django module to sync structural message items seamlessly from localized Outlook desktop software installations into individual app environments.
|
|
18
|
+
|
|
19
|
+
## Setup Instructions
|
|
20
|
+
1. Install your local package.
|
|
21
|
+
2. Register `'django_outlook_sync'` in your `INSTALLED_APPS`.
|
|
22
|
+
3. Run `python manage.py migrate`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
django_outlook_sync/__init__.py
|
|
4
|
+
django_outlook_sync/apps.py
|
|
5
|
+
django_outlook_sync/models.py
|
|
6
|
+
django_outlook_sync/outlook.py
|
|
7
|
+
django_outlook_sync/serializers.py
|
|
8
|
+
django_outlook_sync/sync.py
|
|
9
|
+
django_outlook_sync.egg-info/PKG-INFO
|
|
10
|
+
django_outlook_sync.egg-info/SOURCES.txt
|
|
11
|
+
django_outlook_sync.egg-info/dependency_links.txt
|
|
12
|
+
django_outlook_sync.egg-info/entry_points.txt
|
|
13
|
+
django_outlook_sync.egg-info/requires.txt
|
|
14
|
+
django_outlook_sync.egg-info/top_level.txt
|
|
15
|
+
django_outlook_sync/bin/__init__.py
|
|
16
|
+
django_outlook_sync/bin/auto_sync.py
|
|
17
|
+
django_outlook_sync/management/__init__.py
|
|
18
|
+
django_outlook_sync/management/commands/__init__.py
|
|
19
|
+
django_outlook_sync/management/commands/sync_outlook.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
django_outlook_sync
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-outlook-sync"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Yonas", email="yonas2754@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "A standalone reusable Django package to automate background syncing of local desktop Outlook emails."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Framework :: Django",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: Microsoft :: Windows",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"pywin32; sys_platform == 'win32'",
|
|
21
|
+
"schedule>=1.2.0",
|
|
22
|
+
"djangorestframework>=3.14.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
django-outlook-daemon = "django_outlook_sync.bin.auto_sync:main"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["."]
|
|
30
|
+
include = ["django_outlook_sync*"]
|