From 8b4bc74b1dc1b4c9bd37337ac5c22577fe77c096 Mon Sep 17 00:00:00 2001 From: Simon Zeyer Date: Mon, 9 Jun 2025 11:52:44 +0000 Subject: [PATCH] Add PDF generation and printing functionality using Google Chrome --- Dockerfile | 18 ++++++++++++ app/hooks.py | 82 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7d15b88..bbd0798 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,6 +55,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 diff --git a/app/hooks.py b/app/hooks.py index 0a92043..1f1f877 100644 --- a/app/hooks.py +++ b/app/hooks.py @@ -5,6 +5,7 @@ import cups from weasyprint import HTML from requests.adapters import Retry import uuid +import subprocess retries = Retry(total=5, backoff_factor=0.1, @@ -26,18 +27,18 @@ def alarminator_api(parsed_body: dict): alarminator_api = os.environ.get('alarminator_api') if os.environ.get('alarminator_api') else "" alarminator_token = os.environ.get('alarminator_token') if os.environ.get('alarminator_token') else "" alarminator_zvies_use_PEALGRP = True if os.environ.get('alarminator_zvies_use_PEALGRP') == 'True' else False + maps_api_key = os.environ.get('MAPS_API_KEY',"") if alarminator_api != "" and alarminator_token != "": if 'ALARMDEPESCHE' in parsed_body: # sendAlarm triggern logging.info("GET zu {}/operations/sendAlarm".format(alarminator_api)) s = requests.Session() s.mount('https://', requests.adapters.HTTPAdapter(max_retries=retries)) - #&object=Kirmesplatz &district=Oberlinxweilerstrasse &subject=THK (TH klein &street=Oberlinxweilerstrasse &ils=\"secur.CAD\" &connector=mailParser&token=ea2110e1-11b9-421f-a53d-96cc0fc82c31 req_string = "" req_string +="?token={}".format(alarminator_token) if 'Einsatzbeginn(Soll)' in parsed_body: req_string +="&alarmdate={}".format(parsed_body['Einsatzbeginn(Soll)'].split(" ")[0]) req_string +="&alarmtime={}".format(parsed_body['Einsatzbeginn(Soll)'].split(" ")[1]) - if 'Auftragsnummer' in parsed_body: + 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 'Sachverhalt' in parsed_body: req_string +="&message={}".format(parsed_body['Sachverhalt']) @@ -48,21 +49,29 @@ def alarminator_api(parsed_body: dict): req_string +="&location={}".format(parsed_body['Einsatzziel']['PLZ / Ort']) if 'Objekt' in parsed_body['Einsatzziel']: req_string +="&object={}".format(parsed_body['Einsatzziel']['Objekt']) + street = [] if 'Strasse' in parsed_body['Einsatzziel']: - req_string +="&street={}".format(parsed_body['Einsatzziel']['Strasse']) + (("\n"+parsed_body['Einsatzziel']['Info ']) if 'Info ' in parsed_body['Einsatzziel'] else "" ) + street.append(parsed_body['Einsatzziel']['Strasse']) + if 'Zusatz Strasse' in parsed_body['Einsatzziel']: + street.append(parsed_body['Einsatzziel']['Zusatz Strasse']) if 'Strasse / Hs.-Nr.' in parsed_body['Einsatzziel']: - req_string +="&street={}".format(parsed_body['Einsatzziel']['Strasse / Hs.-Nr.'] + (("\n"+parsed_body['Einsatzziel']['Info ']) if 'Info ' in parsed_body['Einsatzziel'] else "" )) + street.append(parsed_body['Einsatzziel']['Strasse / Hs.-Nr.']) + if 'Info' in parsed_body['Einsatzziel']: + street.append(parsed_body['Einsatzziel']['Info']) + if street.__len__() > 0: + req_string +="&street={}".format("\n".join(street)) if 'Einsatzmittelliste' in parsed_body: gear = [] for r in parsed_body['Einsatzmittelliste']: - if r['Typ'] != 'PEALGRP': + #if r['Typ'] != 'PEALGRP': + if r['Ressourcen'] not in gear: gear.append(r['Ressourcen']) req_string +="&gear={}".format(';'.join(gear)) - #req_string +="&district={}".format('district') - #req_string +="&floor={}".format('floor') - #req_string +="§ion={}".format('section') + req_string +="&district={}".format('district') + req_string +="&floor={}".format('floor') + req_string +="§ion={}".format('section') req_string +="&keywordRaw={}".format(parsed_body['Einsatzstichwort']) #req_string +="&keywordId={}".format('keywordId') req_string +="&keywordCategory={}".format(parsed_body['Einsatzstichwort'].split("(")[0]) @@ -76,9 +85,30 @@ def alarminator_api(parsed_body: dict): req_string +="&zveis={}".format(';'.join(zveis)) else: req_string +="&zveis={}".format(parsed_body['ALARMDEPESCHE']) + + if maps_api_key != "": + try: + maps_address_param = [] + if 'Objekt' in parsed_body['Einsatzziel']: + maps_address_param.append("{}".format(parsed_body['Einsatzziel']['Objekt'])) + + if street.__len__() > 0: + maps_address_param.append('{}'.format(",".join(street))) + if 'Stadt' in parsed_body['Einsatzziel']: + maps_address_param.append("{}".format(parsed_body['Einsatzziel']['Stadt'])) + if 'PLZ / Ort' in parsed_body['Einsatzziel']: + maps_address_param.append("{}".format(parsed_body['Einsatzziel']['PLZ / Ort'])) + + maps_request = requests.get('https://maps.google.com/maps/api/geocode/json?address={}&key={}'.format(','.join(maps_address_param),maps_api_key)) + if maps_request.json()['results'].__len__() == 1: + req_string +="&lat={}".format(maps_request.json()['results'][0]['geometry']['location']['lat']) + req_string +="&lon={}".format(maps_request.json()['results'][0]['geometry']['location']['lng']) + except Exception as maps_e: + logging.error('error getting maps',maps_e) + # req_string +="&gkx={}".format() if False # req_string +="&gky={}".format() if False - # req_string +="&lat={}".format() if False + # if False # req_string +="&lon={}".format() if False subject = "" if 'Notfallgeschehen' in parsed_body: @@ -88,23 +118,37 @@ def alarminator_api(parsed_body: dict): req_string +="&subject={}".format(subject) req_string +="&ils={}".format("ILS Saar") req_string +="&connector={}".format("MailParser") + if os.environ.get('IS_DEV') and os.environ.get('IS_DEV') == "True": + req_string +="&isTest=1" s.get(alarminator_api+"/operations/sendAlarm/"+req_string) except Exception as e: logging.error("alarminator_api", e) +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) + def cups_print(parsed_body: dict, body: str): - fname = "/tmp/{}.pdf".format(uuid.uuid4()) + # 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 = os.environ.get('printer',"DEFAULT") + printer_arr = os.environ.get('printer',"DEFAULT").split(";") print_num = int(os.environ.get('print_num',0)) - if 'ALARMDEPESCHE' in parsed_body: - with open(fname,"wb") as f: - f.write(HTML(string=body, base_url="").write_pdf()) - for i in range(0, print_num): - conn.printFile (printer, fname, "Alarmfax", {}) - os.remove(fname) + #if printer_arr.__len__() > 0: + generate_pdf(body, fname) + for printer in printer_arr: + print(printer) + if 'ALARMDEPESCHE' in parsed_body: + for i in range(0, print_num): + conn.printFile (printer, fname, "Alarmfax", {}) except Exception as e: + logging.error("cups_print", e) + finally: if os.path.exists(fname): - os.remove(fname) - logging.error("cups_print", e) \ No newline at end of file + os.remove(fname) \ No newline at end of file