-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathnvrnoti.py
executable file
·344 lines (274 loc) · 11.4 KB
/
nvrnoti.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
import subprocess
import atexit
from watchdog.observers import Observer
import time
from watchdog.events import FileSystemEventHandler
from email import policy
from email.parser import BytesParser
from PIL import Image
import requests
import os
import sys
import json
import logging
import imageio_ffmpeg as ffmpeg
from aiosmtpd.controller import Controller
from aiosmtpd.handlers import Sink
from email import policy
from email.parser import BytesParser
import uuid
import threading
# SMTP server
stop_event = threading.Event()
class CustomHandler(Sink):
async def handle_DATA(self, server, session, envelope):
print("Handling incoming email...")
file_uuid = uuid.uuid4().hex
current_directory = os.path.dirname(sys.executable)
email_folder = os.path.join(current_directory, "email")
with open(f"{email_folder}/{file_uuid}.eml", "wb") as f:
f.write(envelope.content)
return '250 Message accepted for delivery'
def start_email_server():
# Read config.json
try:
with open("config.json", "r") as f:
config = json.load(f)
port = config.get("SMTP_PORT", 2525) or 2525
except (FileNotFoundError, json.JSONDecodeError):
port = 2525
handler = CustomHandler()
controller = Controller(handler, hostname="0.0.0.0", port=port)
print(f"Starting server on port {port}...")
controller.start()
try:
while not stop_event.is_set():
time.sleep(1)
except KeyboardInterrupt:
print("Server stopped.")
# Send push notification when script is terminated
def script_terminated():
stop_event.set()
send_push_notification(
"reolink-rich-notifications has been terminated. Please restart.")
# Remove all files in email and attachments folders
folders_to_clean = [watch_folder, image_folder]
for folder in folders_to_clean:
for filename in os.listdir(folder):
file_path = os.path.join(folder, filename)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
except Exception as e:
print(f"Failed to delete {file_path}. Reason: {e}")
# Register the termination function to run when this script exits
atexit.register(script_terminated)
# Function to send push notification
def send_push_notification(message, attachment_path=None):
payload = {
"token": PUSHOVER_APP_TOKEN,
"user": PUSHOVER_USER_KEY,
"message": message,
"url": URL,
"url_title": URL_TITLE
}
files = {}
if attachment_path:
attachment_filename = os.path.basename(attachment_path)
files["attachment"] = (attachment_filename,
open(attachment_path, "rb"))
max_retries = 3
for i in range(max_retries):
try:
response = requests.post(
"https://api.pushover.net/1/messages.json", data=payload, files=files if files else None, timeout=10)
response_data = response.json()
if response_data.get("status") == 1:
print("Push sent successfully.")
break # If successful, break the loop
except requests.exceptions.RequestException as e:
logging.error(
f"Failed to send push notification (attempt {i + 1}): {e}")
if i < max_retries - 1:
time.sleep(5) # Wait for 5 seconds before retrying
else:
print(
"All retries failed. There might be an issue with the Pushover server.")
print("Please restart the script later.")
sys.exit(1)
return response_data
# Checks for config file with key and token / creates it if not found and asks user for values
def load_or_create_config():
config_file = 'config.json'
if os.path.exists(config_file):
with open(config_file, 'r') as f:
config = json.load(f)
else:
config = {}
config['PUSHOVER_APP_TOKEN'] = input('Enter your Pushover App Token: ')
config['PUSHOVER_USER_KEY'] = input('Enter your Pushover User Key: ')
config['SMTP_PORT'] = input(
'Enter the SMTP port to use (default is 2525, press Enter to use default): ')
with open(config_file, 'w') as f:
json.dump(config, f)
return config
# Create `email` and `attachments` folder
def ensure_directories_exist():
script_dir = os.path.dirname(sys.executable)
folders = ['email', 'attachments']
for folder in folders:
full_path = os.path.join(script_dir, folder)
if not os.path.exists(full_path):
os.makedirs(full_path)
# Ensure required directories exist
ensure_directories_exist()
# Direcotry paths
current_dir = os.path.dirname(sys.executable)
watch_folder = os.path.join(current_dir, 'email')
image_folder = os.path.join(current_dir, 'attachments')
# Pushover credentials
config = load_or_create_config()
PUSHOVER_APP_TOKEN = config['PUSHOVER_APP_TOKEN']
PUSHOVER_USER_KEY = config['PUSHOVER_USER_KEY']
# URL to open Reolink app
URL = "fb1675493782511558://"
URL_TITLE = "Open Reolink"
# Start the SMTP server in a new thread
email_server_thread = threading.Thread(target=start_email_server)
# Daemon threads exit when the main program exits
email_server_thread.daemon = True
email_server_thread.start()
# Resizes images if needed
def resize_image(image_path):
quality = 90
while os.path.getsize(image_path) > 2.4 * 1024 * 1024: # 2.4 MB in bytes
img = Image.open(image_path)
img.save(image_path, "JPEG", quality=quality)
quality -= 10 # reduce quality by 10% each iteration
# Converts video to .gif
def convert_mp4_to_gif(mp4_path, gif_path):
retries = 0
max_retries = 3
scale = 480 # Initial scale width
while retries < max_retries:
if os.path.exists(gif_path):
os.remove(gif_path)
try:
input_file = mp4_path
output_file = gif_path
# FFmpeg command with dynamic scaling
ffmpeg_cmd = [
ffmpeg.get_ffmpeg_exe(),
"-i", input_file, # Input file
# Video filter chain
"-vf", f"fps=5,scale={scale}:-1:flags=lanczos",
"-f", "gif", # Output format
output_file # Output file
]
subprocess.run(ffmpeg_cmd, check=True)
file_size = os.path.getsize(gif_path)
if file_size > 2.4 * 1024 * 1024:
print("GIF file size is too large. Adjusting scale...")
scale = int(scale * 0.8) # Reduce scale by 20%
retries += 1 # Increment retries
continue # Skip to the next iteration
else:
print("GIF file size is acceptable.")
break # Successfully converted, break out of loop
except Exception as e:
print(f"Failed to convert video to GIF: {e}. Retrying...")
retries += 1
time.sleep(2)
if retries == max_retries:
print("Max retries reached. Exiting.")
sys.exit(1)
class Watcher:
def __init__(self, watch_folder):
self.watch_folder = watch_folder
self.event_handler = Handler()
self.observer = Observer()
def run(self):
self.observer.schedule(
self.event_handler, self.watch_folder, recursive=True)
self.observer.start()
try:
while True:
pass
except:
self.observer.stop()
print("Observer stopped")
class Handler(FileSystemEventHandler):
def process(self, event):
for file in os.listdir(watch_folder):
if not file.endswith(".eml"):
continue
file_path = os.path.join(watch_folder, file)
if os.path.exists(file_path):
print(f"New file detected: {file_path}")
with open(file_path, 'rb') as f:
msg = BytesParser(policy=policy.default).parse(f)
text_data = ""
attachment_filename = ""
attachment_path = ""
if msg.is_multipart():
for part in msg.iter_parts():
content_disposition = part.get(
"Content-Disposition", "")
if "attachment" not in content_disposition:
text_data = part.get_payload(
decode=True).decode().split(".")[0]
else:
attachment_filename = part.get_filename()
# Generate a unique identifier
unique_id = str(uuid.uuid4())[:8]
base_name, file_extension = os.path.splitext(
attachment_filename)
# Append the unique identifier to the filename
unique_filename = f"{base_name}_{unique_id}{file_extension}"
attachment_path = os.path.join(
image_folder, unique_filename)
with open(attachment_path, 'wb') as f:
f.write(part.get_payload(decode=True))
if attachment_filename.endswith('.jpg'):
resize_image(attachment_path)
elif attachment_filename.endswith('.mp4'):
gif_path = attachment_path.replace(
'.mp4', '.gif')
convert_mp4_to_gif(attachment_path, gif_path)
attachment_path = gif_path
else:
text_data = msg.get_payload(
decode=True).decode().split(".")[0]
# Delete the original email file after successful extraction
os.remove(file_path)
# Send push notification via Pushover
response_data = send_push_notification(
text_data, attachment_path)
if response_data.get("status") == 1:
mp4_path = ""
# Store the .mp4 file path if the attachment is a video
if attachment_filename.endswith('.mp4'):
mp4_path = attachment_path.replace('.gif', '.mp4')
# Delete the .gif file after storing .mp4 path
if attachment_path and os.path.exists(attachment_path):
os.remove(attachment_path)
# Check if the attachment file exists before attempting to delete it
try:
if mp4_path: # Only try to delete if mp4_path is not empty
os.remove(mp4_path) # Try to delete the .mp4 file
except FileNotFoundError:
pass # File doesn't exist, ignore the error
def on_created(self, event):
self.process(event)
if __name__ == '__main__':
try:
# Send the initial push notification
send_push_notification(
"reolink-rich-notifications has been started. Enjoy!")
# Create a Watcher instance and run it
watcher = Watcher(watch_folder)
watcher.run()
except Exception as e:
print(f"An error occurred: {e}")
# This will signal the SMTP server thread to stop and do additional cleanup
sys.exit(1)