Compare commits

..
9 Commits
Author SHA1 Message Date
Simon Zeyer 883fc84c02 Enhance securecad message parsing to handle additional table structures for 'Einsatzziel', 'Zusatztext zum Ort', and 'Zusatztext zum Objekt' 2026-08-09 11:10:07 +00:00
Simon Zeyer 399d8e498d Add Divera messaging functionality and API keys to environment configuration 2026-08-09 11:09:51 +00:00
Simon Zeyer 4a965a96a4 update swankt-wendel root ca 2026-08-08 13:31:28 +00:00
Simon Zeyer f64879398f Add sleep delay in run function to prevent tight loop execution 2025-06-16 21:22:55 +00:00
Simon Zeyer 3e33de4606 Fix flag setting method in IMAP message handling 2025-06-09 12:01:03 +00:00
Simon Zeyer 8b4bc74b1d Add PDF generation and printing functionality using Google Chrome 2025-06-09 11:52:44 +00:00
Simon Zeyer 2b331fbd1f Remove redundant idle check logic for existing messages in IMAP connection 2025-06-09 11:37:44 +00:00
Simon Zeyer b528b0a748 Refactor idle check logic in IMAP connection to improve message handling 2025-06-09 11:12:18 +00:00
Simon Zeyer 5127caed03 Add IMAP support and refactor environment configuration
- Introduced `imap_connect.py` for handling IMAP email interactions.
- Created `run.py` to manage execution based on the selected mode (IMAP or EWS).
- Updated Dockerfile and .env.dev to include necessary environment variables.
- Enhanced logging and email processing in `exchange_connect.py`.
- Added `weasyprint` and `imapclient` to requirements.
2025-06-09 10:53:08 +00:00
12 changed files with 407 additions and 159 deletions
+3
View File
@@ -1,3 +1,4 @@
mode="EWS"
username=""
password=""
server=""
@@ -12,3 +13,5 @@ alarminator_token=""
alarminator_zvies_use_PEALGRP="False"
printer=DEFAULT
print_num=0
DIVERA_MESSANGER_API_KEY=""
DIVERA_ALARMER_API_KEY=""
+22 -1
View File
@@ -1,5 +1,6 @@
FROM python:3.10-bullseye
ENV mode="EWS"
ENV username=
ENV password=
ENV server="exchange.sankt-wendel.de"
@@ -13,6 +14,8 @@ ENV alarminator_zvies_use_PEALGRP="False"
ENV print_num=0
ENV printer="DEFAULT"
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
ENV DIVERA_MESSANGER_API_KEY=""
ENV DIVERA_ALARMER_API_KEY=""
ENV MAPS_API_KEY=""
@@ -55,6 +58,24 @@ RUN useradd \
print \
&& sed -i '/%sudo[[:space:]]/ s/ALL[[:space:]]*$/NOPASSWD:ALL/' /etc/sudoers
# Print PDF
RUN apt-get update && apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
--no-install-recommends \
&& curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update && apt-get install -y \
google-chrome-stable \
--no-install-recommends
# It won't run from the root user.
RUN groupadd chrome && useradd -g chrome -s /bin/bash -G audio,video chrome \
&& mkdir -p /home/chrome && chown -R chrome:chrome /home/chrome
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
@@ -62,4 +83,4 @@ COPY ./app .
COPY *.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
CMD [ "sh","-c","/etc/init.d/cups start && python3 /usr/src/app/http.py" ]
CMD [ "sh","-c","/etc/init.d/cups start && python3 /usr/src/app/run.py" ]
@@ -0,0 +1,36 @@
-----BEGIN CERTIFICATE-----
MIIGTDCCBDSgAwIBAgIQOXpmzCdWNi4NqofKbqvjsTANBgkqhkiG9w0BAQwFADBf
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
HhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5WjBgMQswCQYDVQQGEwJHQjEY
MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFB1Ymxp
YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gQ0EgRFYgUjM2MIIBojANBgkqhkiG9w0B
AQEFAAOCAY8AMIIBigKCAYEAljZf2HIz7+SPUPQCQObZYcrxLTHYdf1ZtMRe7Yeq
RPSwygz16qJ9cAWtWNTcuICc++p8Dct7zNGxCpqmEtqifO7NvuB5dEVexXn9RFFH
12Hm+NtPRQgXIFjx6MSJcNWuVO3XGE57L1mHlcQYj+g4hny90aFh2SCZCDEVkAja
EMMfYPKuCjHuuF+bzHFb/9gV8P9+ekcHENF2nR1efGWSKwnfG5RawlkaQDpRtZTm
M64TIsv/r7cyFO4nSjs1jLdXYdz5q3a4L0NoabZfbdxVb+CUEHfB0bpulZQtH1Rv
38e/lIdP7OTTIlZh6OYL6NhxP8So0/sht/4J9mqIGxRFc0/pC8suja+wcIUna0HB
pXKfXTKpzgis+zmXDL06ASJf5E4A2/m+Hp6b84sfPAwQ766rI65mh50S0Di9E3Pn
2WcaJc+PILsBmYpgtmgWTR9eV9otfKRUBfzHUHcVgarub/XluEpRlTtZudU5xbFN
xx/DgMrXLUAPaI60fZ6wA+PTAgMBAAGjggGBMIIBfTAfBgNVHSMEGDAWgBRWc1hk
lfmSGrASKgRieaFAFYghSTAdBgNVHQ4EFgQUaMASFhgOr872h6YyV6NGUV3LBycw
DgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYI
KwYBBQUHAwEGCCsGAQUFBwMCMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBAgEw
VAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdv
UHVibGljU2VydmVyQXV0aGVudGljYXRpb25Sb290UjQ2LmNybDCBhAYIKwYBBQUH
AQEEeDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3Rp
Z29QdWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvblJvb3RSNDYucDdjMCMGCCsGAQUF
BzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEA
YtOC9Fy+TqECFw40IospI92kLGgoSZGPOSQXMBqmsGWZUQ7rux7cj1du6d9rD6C8
ze1B2eQjkrGkIL/OF1s7vSmgYVafsRoZd/IHUrkoQvX8FZwUsmPu7amgBfaY3g+d
q1x0jNGKb6I6Bzdl6LgMD9qxp+3i7GQOnd9J8LFSietY6Z4jUBzVoOoz8iAU84OF
h2HhAuiPw1ai0VnY38RTI+8kepGWVfGxfBWzwH9uIjeooIeaosVFvE8cmYUB4TSH
5dUyD0jHct2+8ceKEtIoFU/FfHq/mDaVnvcDCZXtIgitdMFQdMZaVehmObyhRdDD
4NQCs0gaI9AAgFj4L9QtkARzhQLNyRf87Kln+YU0lgCGr9HLg3rGO8q+Y4ppLsOd
unQZ6ZxPNGIfOApbPVf5hCe58EZwiWdHIMn9lPP6+F404y8NNugbQixBber+x536
WrZhFZLjEkhp7fFXf9r32rNPfb74X/U90Bdy4lzp3+X1ukh1BuMxA/EEhDoTOS3l
7ABvc7BYSQubQ2490OcdkIzUh3ZwDrakMVrbaTxUM2p24N6dB+ns2zptWCva6jzW
r8IWKIMxzxLPv5Kt3ePKcUdvkBU/smqujSczTzzSjIoR5QqQA6lN1ZRSnuHIWCvh
JEltkYnTAH41QJ6SAWO66GrrUESwN/cgZzL4JLEqz1Y=
-----END CERTIFICATE-----
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY
MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp
YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa
ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz
SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf
iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X
ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3
IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS
VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE
SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu
+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt
8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L
HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt
zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P
AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ
YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52
gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA
Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB
JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX
DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui
TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5
dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65
LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp
0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY
QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----
+7 -6
View File
@@ -21,13 +21,14 @@ Message.register("alarmfax_parser_verarbeitet", alarmfax_parser_verarbeitet)
Message.register("alarmfax_parser_id", alarmfax_parser_id)
#print([f.name for f in Message.FIELDS if f.is_searchable])
threads = {}
def run():
threads = {}
format = "%(asctime)s|%(threadName)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
format = "%(asctime)s|%(threadName)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S")
def eventHandler(ELEMENT_NAME, item_id, item_changekey):
def eventHandler(ELEMENT_NAME, item_id, item_changekey):
if (ELEMENT_NAME == 'ModifiedEvent' and IS_DEV) or ELEMENT_NAME == 'NewMailEvent' or ELEMENT_NAME == 'SearchFolderEvent':
logging.info(ELEMENT_NAME + " - get Mail")
m: Message = a.inbox.get(id=item_id, changekey=item_changekey)
@@ -49,9 +50,10 @@ def eventHandler(ELEMENT_NAME, item_id, item_changekey):
webhook(parsed_body)
alarminator_api(parsed_body)
cups_print(parsed_body,m.body)
divera_messenger(parsed_body)
pass
def folder_event_subscriber(folder: Folder):
def folder_event_subscriber(folder: Folder):
logging.info('folder_event_subscriber startet for Folder: {}'.format(folder.name))
while True:
# filtern des ordners nach mails der letzten 24h, die nicht verarbeitet wurden
@@ -79,7 +81,6 @@ def folder_event_subscriber(folder: Folder):
t = Thread(target=eventHandler, args=(event.ELEMENT_NAME,event.item_id.id,event.item_id.changekey,),name="eventHandler: {} ({})".format(event.ELEMENT_NAME, event.item_id.id))
t.start()
if __name__ == "__main__":
try:
username = os.environ.get('username')
password = os.environ.get('password')
+90 -16
View File
@@ -2,10 +2,12 @@ import os
import logging
import requests
import cups
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
from weasyprint import HTML
from requests.adapters import Retry
import uuid
import subprocess
import datetime
import time
retries = Retry(total=5,
backoff_factor=0.1,
@@ -40,8 +42,8 @@ def alarminator_api(parsed_body: dict):
req_string +="&alarmtime={}".format(parsed_body['Einsatzbeginn(Soll)'].split(" ")[1])
if 'Auftragsnummer' in parsed_body and not (os.environ.get('IS_DEV') and os.environ.get('IS_DEV') == "True"):
req_string +="&operationnumber={}".format(parsed_body['Auftragsnummer'])
if 'Notfallgeschehen' in parsed_body:
req_string +="&message={}".format(parsed_body['Notfallgeschehen'])
if 'Sachverhalt' in parsed_body:
req_string +="&message={}".format(parsed_body['Sachverhalt'])
if 'Einsatzziel' in parsed_body:
if 'Stadt' in parsed_body['Einsatzziel']:
req_string +="&community={}".format(parsed_body['Einsatzziel']['Stadt'])
@@ -111,6 +113,8 @@ def alarminator_api(parsed_body: dict):
# if False
# req_string +="&lon={}".format() if False
subject = ""
if 'Notfallgeschehen' in parsed_body:
subject = parsed_body['Notfallgeschehen'] + "\n"
if 'Notfallgeschehen' in parsed_body:
subject = parsed_body['Notfallgeschehen'] + "\n"
req_string +="&subject={}".format(subject)
@@ -122,27 +126,97 @@ def alarminator_api(parsed_body: dict):
except Exception as e:
logging.error("alarminator_api", e)
def cups_print(parsed_body: dict, body: str):
html = HTML(string='<html><head></head><body>'+body+"</body></html>", base_url="")
font_config = FontConfiguration()
pdf = html.write_pdf(font_config=font_config,stylesheets=[CSS(string='body { font-family: serif !important }')])
fname = "/tmp/{}.pdf".format(uuid.uuid4())
def generate_pdf(html_body, filename):
f = "/tmp/{}.html".format(uuid.uuid4())
with open(f,"w") as _f:
_f.write(html_body)
subprocess.run(["/usr/bin/google-chrome-stable", "--headless", "--no-sandbox", "--disable-gpu", "--print-to-pdf="+filename, "--no-pdf-header-footer", "--print-to-pdf-no-header", "--no-margins", f])
if os.path.exists(f):
os.remove(f)
if os.environ.get('IS_DEV') and os.environ.get('IS_DEV') == "True":
with open("{}.pdf".format(uuid.uuid4()),"wb") as f:
f.write(pdf)
def cups_print(parsed_body: dict, body: str):
# if os.environ.get('IS_DEV') and os.environ.get('IS_DEV') == "True":
# generate_pdf(body, "{}.pdf".format(uuid.uuid4()))
fname = "{}.pdf".format(uuid.uuid4())
try:
conn = cups.Connection ()
printer_arr = os.environ.get('printer',"DEFAULT").split(";")
print_num = int(os.environ.get('print_num',0))
#if printer_arr.__len__() > 0:
generate_pdf(body, fname)
for printer in printer_arr:
print(printer)
if 'ALARMDEPESCHE' in parsed_body:
with open(fname,"wb") as f:
f.write(pdf)
for i in range(0, print_num):
conn.printFile (printer, fname, "Alarmfax", {})
os.remove(fname)
except Exception as e:
logging.error("cups_print", e)
finally:
if os.path.exists(fname):
os.remove(fname)
logging.error("cups_print", e)
def divera_messenger(parsed_body: dict):
# /api/v2/alarms
# message-channels?accesskey=XXX
try:
api = "https://app.divera247.com/api/v2/"
messenger_api_token = os.environ.get('DIVERA_MESSANGER_API_KEY') if os.environ.get('DIVERA_MESSANGER_API_KEY') else ""
alarmer_api_token = os.environ.get('DIVERA_ALARMER_API_KEY') if os.environ.get('DIVERA_ALARMER_API_KEY') else ""
if messenger_api_token != "":
if 'ALARMDEPESCHE' in parsed_body: # sendAlarm triggern
if 'Einsatzmittelliste' in parsed_body:
zveis = []
message = "Alarmierte Einsatzmittel:\n"
for r in parsed_body['Einsatzmittelliste']:
if r['Typ'] == 'PEALGRP':
zveis.append(r['Ressourcen'])
message +='\n'.join(zveis)
if 'Zusatztext zum Ort' in parsed_body:
message += "\n\nZusatztext zum Ort:\n"+parsed_body['Zusatztext zum Ort']
if 'Zusatztext zum Objekt' in parsed_body:
message += "\n\nZusatztext zum Objekt:\n"+parsed_body['Zusatztext zum Objekt']
logging.info("GET zu {}alarms?accesskey=XXX".format(api))
s = requests.Session()
s.mount('https://', requests.adapters.HTTPAdapter(max_retries=retries))
start = datetime.datetime.now()
message_channel_id = None
alarm_id = None
alarm_text = None
while message_channel_id is None and datetime.datetime.now() < start + datetime.timedelta(minutes=1):
r = s.get("{}alarms?accesskey={}".format(api, messenger_api_token))
r_json = r.json()
if 'items' in r_json.get('data',{}):
for item in r_json.get('data',{})['items']:
if str(r_json['data']['items'][item]['foreign_id']) == str(parsed_body['Auftragsnummer']):
logging.info("EinsatzNr {} in Divera gefunden, sende Nachricht...".format(r_json['data']['items'][item]['foreign_id']))
message_channel_id = r_json['data']['items'][item]['message_channel_id']
alarm_id = r_json['data']['items'][item]['id']
alarm_text = r_json['data']['items'][item]['text']
if message_channel_id is None or alarm_id is None:
time.sleep(20)
if alarmer_api_token != "":
if alarm_text is None:
alarm_text = ""
r = s.put("{}alarms/{}?accesskey={}".format(api, alarm_id, alarmer_api_token),json={
"Alarm": {
"text": alarm_text+"\n\n"+message,
}
})
if message_channel_id:
r = s.post("{}messages?accesskey={}".format(api, messenger_api_token),json={
"Message": {
"message_channel_id": str(message_channel_id),
# "parent_id": 0,
"text": message,
}
})
pass
except Exception as e:
logging.error("divera_messenger", e)
-65
View File
@@ -1,65 +0,0 @@
from flask import Flask, request
import os
import glob
import securecad_parser
import datetime
from hooks import webhook, alarminator_api, cups_print
app = Flask(__name__)
def wrapHtml(innerHtml):
return '''<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body><div class="container text-center">
''' + innerHtml + '''
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>'''
def getAlarmFiles():
dir_path = os.path.dirname(os.path.realpath(__file__))
dir_path = dir_path + "/alarms" + os.sep
a_list = glob.glob(dir_path+"*.html")
a_list.sort()
return a_list
def parseAlarm(f):
dir_path = os.path.dirname(os.path.realpath(__file__))
f = dir_path + "/alarms" + os.sep + f
with open(f, "r") as o:
_r: str = o.read()
now = datetime.datetime.now()
_r = _r.replace('%DATUM%',now.strftime("%d.%m.%Y"))
_r = _r.replace('%UHRZEIT%',now.strftime("%H:%M"))
if _r != '':
return [securecad_parser.parse_securecad_message(_r),_r]
@app.route("/")
def root():
file = request.args.get('sendalarm')
if file is not None:
parsed_body, raw = parseAlarm(file)
if parsed_body != None:
if 'ALARMDEPESCHE' in parsed_body:
webhook(parsed_body)
alarminator_api(parsed_body)
cups_print(parsed_body,raw)
html = '<div class="row align-items-start p-3 ">'
i: str
files = getAlarmFiles()
for f in files:
html = html + "<div class=\"col-6\" style='padding: 5px;'><a style='display:block; padding:20px' class=\"btn btn-block btn-danger\" href=\"/?sendalarm="+os.path.basename(f)+"\">" + os.path.basename(f) + "</a></div>"
html = html + "</div>"
return wrapHtml(html)
app.run(port=5000)
+124
View File
@@ -0,0 +1,124 @@
from datetime import timedelta, datetime
import os
import ssl
import email
import logging
from imapclient import IMAPClient
from threading import Thread
from securecad_parser import parse_securecad_message
from hooks import webhook, alarminator_api, cups_print
import re
from time import sleep
def run():
threads = {}
format = "%(asctime)s|%(threadName)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S")
def eventHandler(ELEMENT_NAME, uid, message_data, _server: IMAPClient = None):
email_message = email.message_from_bytes(message_data[b'RFC822'])
email_from: list[str] = re.findall(r'([\w\.-]+@[\w\.-]+)', email_message.get('From'))
flags = _server.get_flags(uid)
logging.info(ELEMENT_NAME + " - get Mail")
if 'Processed_{}'.format(parser_id).encode() in flags[uid]:
logging.info("Mail {} bereits verarbeitet.. ignoriere".format(uid))
if not IS_DEV:
return
else:
_server.add_flags(uid, ['\\SEEN','Processed_{}'.format(parser_id)])
logging.info("got Mail {} von {}".format(email_message.get('Subject'), email_from))
if any(mail in filter_from for mail in email_from):
# Get HTML body
html_body = ""
if email_message.is_multipart():
for part in email_message.walk():
if part.get_content_type() == 'text/html':
html_body = part.get_payload(decode=True).decode(part.get_content_charset() or 'utf-8', errors='replace')
break
else:
if email_message.get_content_type() == 'text/html':
html_body = email_message.get_payload(decode=True).decode(email_message.get_content_charset() or 'utf-8', errors='replace')
parsed_body = parse_securecad_message(html_body)
logging.debug(parsed_body)
if parsed_body != None:
if 'ALARMDEPESCHE' in parsed_body:
logging.info("Alarm für: {}".format(parsed_body['ALARMDEPESCHE']))
webhook(parsed_body)
alarminator_api(parsed_body)
cups_print(parsed_body,html_body)
pass
def folder_event_subscriber(folder: str):
logging.info('folder_event_subscriber startet for Folder: {}'.format(folder))
with IMAPClient(server, ssl_context=ssl_context) as _server:
_server.login(username, password)
while True:
# filtern des ordners nach mails der letzten 24h, die nicht verarbeitet wurden
now = datetime.now()
_server.select_folder(folder, readonly=False)
q = ['SENTSINCE', now - timedelta(days=1),'NOT','KEYWORD', 'Processed_{}'.format(parser_id)]
if IS_DEV:
q = ['SENTSINCE', now - timedelta(days=1),'UNSEEN']
q = ['UNSEEN']
messages = _server.search(q)
cnt = messages.__len__()
if cnt > 0:
logging.info("{} Mails nicht verarbeitet in den letzten 2 Tagen in ordner: {}".format(cnt, folder))
for uid, message_data in _server.fetch(messages, 'RFC822').items():
# IMAPClient ist nicht thread-safe, daher wird hier der _server übergeben und kein Thread verwendet.
eventHandler('SearchFolderEvent', uid, message_data, _server)
# t = Thread(target=eventHandler, args=('SearchFolderEvent',uid,message_data,),name="eventHandler: SearchFolderEvent ({})".format(uid))
# t.start()
# aktives warten auf streaming_events. maximal eine minute lang, dann wird nochmal der ordner durchsucht, falls mails angekommen sind während eines timeout/cooldown.
_server.idle()
try:
logging.debug("Idle check for folder: {}".format(folder))
messages = _server.idle_check(timeout=60) # Timeout after 60 seconds
_server.idle_done()
# In den events stehen nur vorhandenen nachrichten. Exists ist nicht die neue Nachricht, sondern eine bereits vorhandene.
# for item in messages:
# if item[1] == b'EXISTS':
# logging.info("New messages in folder: {}".format(folder))
# for uid, message_data in _server.fetch([item[0]], 'RFC822').items():
# if uid:
# eventHandler('NewMailEvent', uid, message_data, _server)
except Exception as e:
logging.error("Error during idle check: {}".format(e))
username = os.environ.get('username')
password = os.environ.get('password')
server = os.environ.get('server')
folders = os.environ.get('folders',"")
parser_id = os.environ.get('alarmfax_parser_id',"")
primary_smtp_address = os.environ.get('primary_smtp_address')
filter_from = os.environ.get('filter_from').split(";") if os.environ.get('filter_from') else []
IS_DEV = True if os.environ.get('IS_DEV') and os.environ.get('IS_DEV') == "True" else False
if IS_DEV:
logging.getLogger().setLevel(logging.INFO)
ssl_context = ssl.create_default_context()
with IMAPClient(server, ssl_context=ssl_context) as _server:
_server.login(username, password)
_server.logout()
folders_to_subscribe = []
for f in folders.split(";"):
if f == "":
folders_to_subscribe.append('INBOX')
else:
folders_to_subscribe.append(f)
while True:
for f in folders_to_subscribe:
if not f in threads or not threads[f].is_alive():
logging.info("folder_event_subscriber for folder \"{}\" not alive, starting".format(f))
t = Thread(target=folder_event_subscriber, args=(f,), daemon=True, name="folder_event_subscriber {}".format(f))
threads[f] = t
t.start()
sleep(1)
+10
View File
@@ -0,0 +1,10 @@
import os
from imap_connect import run as run_imap
from exchange_connect import run as run_ews
mode = os.environ.get('mode')
if mode == 'IMAP':
run_imap()
elif mode == 'EWS':
run_ews()
else:
raise ValueError("Invalid mode specified. Use 'IMAP' or 'EWS'.")
+28 -2
View File
@@ -5,8 +5,10 @@ from numpy import nan
tt_list_alarmdepesche = []
tt_list_infodepesche = []
tt_list_einsatzprotokoll = []
def parse_securecad_message(body_html: str):
body_html = body_html.replace(u'\xa0', u' ')
if 'ALARMDEPESCHE' in body_html:
t_list = {}
tables = pd.read_html(body_html) # Returns list of all tables on page
@@ -25,6 +27,7 @@ def parse_securecad_message(body_html: str):
for r in table_dict:
if table_dict[r][0] is not nan:
t_list[table_dict[r][0].strip(' :')] = table_dict[r][1]
#Einsatzziel
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Einsatzziel' and 'Einsatzziel' not in t_list:
t_list['Einsatzziel'] = {}
for r in table_dict:
@@ -32,6 +35,12 @@ def parse_securecad_message(body_html: str):
t_list['Einsatzziel'][table_dict[r][0].strip(' :')] = table_dict[r][1]
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Einsatzziel' and 'Einsatzziel' in t_list:
continue
elif t_count > 3 and table_dict[0] is not nan and 'Einsatzziel' in t_list and len(t_list['Einsatzziel']) == 0:
#Eigene Tabelle unter 'Einsatzziel'
for r in table_dict:
if table_dict[r][0] is not nan and table_dict[r][0] != 'Einsatzziel':
t_list['Einsatzziel'][table_dict[r][0].strip(' :')] = table_dict[r][1]
#Zusatztext zum Ort
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Zusatztext zum Ort' and 'Zusatztext zum Ort' not in t_list:
t_list['Zusatztext zum Ort'] = []
for r in table_dict:
@@ -39,6 +48,12 @@ def parse_securecad_message(body_html: str):
t_list['Zusatztext zum Ort'].append(table_dict[r][1])
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Zusatztext zum Ort' and 'Zusatztext zum Ort' in t_list:
continue
elif t_count > 3 and table_dict[0] is not nan and 'Zusatztext zum Ort' in t_list and len(t_list['Zusatztext zum Ort']) == 0:
#Eigene Tabelle unter 'Zusatztext zum Ort'
for r in table_dict:
if table_dict[r][0] is not nan and table_dict[r][0] != 'Zusatztext zum Ort':
t_list['Zusatztext zum Ort'].append(table_dict[r][1])
#Zusatztext zum Objekt
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Zusatztext zum Objekt' and 'Zusatztext zum Objekt' not in t_list:
t_list['Zusatztext zum Objekt'] = []
for r in table_dict:
@@ -46,13 +61,24 @@ def parse_securecad_message(body_html: str):
t_list['Zusatztext zum Objekt'].append(table_dict[r][1])
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Zusatztext zum Objekt' and 'Zusatztext zum Objekt' in t_list:
continue
elif t_count > 3 and table_dict[0] is not nan and 'Zusatztext zum Objekt' in t_list and len(t_list['Zusatztext zum Objekt']) == 0:
#Eigene Tabelle unter 'Zusatztext zum Objekt'
for r in table_dict:
if table_dict[r][0] is not nan and table_dict[r][0] != 'Zusatztext zum Objekt':
t_list['Zusatztext zum Objekt'].append(table_dict[r][1])
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Einsatzmittelliste':
# es folgt die Einsatzmittelliste
continue
elif t_count > 3 and table_dict[0] is not nan and table_dict[0][0] == 'Ressourcen'and table_dict[0][1] == 'Typ' and table_dict[0][2] == 'Organisation':
elif t_count > 3 and table_dict[0] is not nan and len(table_dict)>1 and (
# In der ersten Zeile steht "Ressourcen, Typ, Organisation"
(table_dict[0][0] == 'Ressourcen'and table_dict[0][1] == 'Typ' and table_dict[0][2] == 'Organisation' )
or
# In der ersten Zeile steht "Alarmierte Ressourcen", in der zweiten Zeile steht "Ressourcen, Typ, Organisation"
(table_dict[1][0] == 'Ressourcen'and table_dict[1][1] == 'Typ' and table_dict[1][2] == 'Organisation' )
):
t_list['Einsatzmittelliste'] = []
for r in table_dict:
if table_dict[r][0] != "Ressourcen":
if table_dict[r][0] != "Ressourcen" and table_dict[r][0] != 'Alarmierte Ressourcen':
t_list['Einsatzmittelliste'].append({
'Ressourcen': table_dict[r][0] if table_dict[r][0] is not nan else "",
'Typ': table_dict[r][1] if table_dict[r][1] is not nan else "",
+3 -1
View File
@@ -6,6 +6,7 @@ services:
restart: always
privileged: true
environment:
- mode=${mode}
- username=${username}
- password=${password}
- server=${server}
@@ -20,7 +21,8 @@ services:
- alarminator_zvies_use_PEALGRP=${alarminator_zvies_use_PEALGRP}
- printer=${printer}
- print_num=${print_num}
- MAPS_API_KEY=${MAPS_API_KEY}
- DIVERA_MESSANGER_API_KEY=${DIVERA_MESSANGER_API_KEY}
- DIVERA_ALARMER_API_KEY=${DIVERA_ALARMER_API_KEY}
volumes:
- ./cups:/etc/cups
- ./cupsd.conf.txt:/etc/cups/cupsd.conf
+3 -19
View File
@@ -1,34 +1,20 @@
blinker==1.8.2
Brotli==1.1.0
cached-property==1.5.2
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==3.0.1
click==8.1.7
cryptography==39.0.0
cssselect2==0.7.0
defusedxml==0.7.1
dnspython==2.3.0
exchangelib==4.9.0
Flask==3.0.3
fonttools==4.54.1
html5lib==1.1
idna==3.4
isodate==0.6.1
itsdangerous==2.2.0
Jinja2==3.1.4
lxml==4.9.2
MarkupSafe==3.0.1
ntlm-auth==1.5.0
numpy==1.24.1
oauthlib==3.2.2
pandas==1.5.2
pillow==10.4.0
pycparser==2.21
pycups==2.0.1
pydyf==0.11.0
Pygments==2.14.0
pyphen==0.16.0
python-dateutil==2.8.2
pytz==2022.7.1
pytz-deprecation-shim==0.1.0.post0
@@ -36,13 +22,11 @@ requests==2.28.2
requests-ntlm==1.1.0
requests-oauthlib==1.3.1
six==1.16.0
tinycss2==1.3.0
tzdata==2022.7
tzlocal==4.2
urllib3==1.26.14
weasyprint==62.3
webencodings==0.5.1
Werkzeug==3.0.4
xmltodict==0.12.0
xmltojson==2.0.1
zopfli==0.2.3
pycups==2.0.1
weasyprint
imapclient==3.0.1