
    Qi                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZmZ ddlmZ ddlmZmZ dZ ee      j)                         j*                  Zej*                  Zej*                  Zedz  Zed	z  d
z  dz  Zedz  Zedz  ZddZ e       ZddZddZ dddZ!d d!dZ"d"d#dZ#d$dZ$d$dZ%d$dZ& G d de      Z'd%dZ(e)dk(  r e(        yy)&aU  
S.W.A.R.M-Watchdog lokaler Backend-Server.

Ersetzt `python -m http.server` und liefert zusaetzlich API-Endpunkte:
  GET  /<pfad>                     - statische Dateien aus COWORK_ROOT
  POST /api/mail/send              - Outlook-Mail senden (via pywin32 COM)
  POST /api/plaene/upload          - Plan-Datei hochladen
  DELETE /api/plaene/<projekt>/<datei>  - Plan loeschen
  POST /api/plaene/rename          - Plan umbenennen
  POST /api/kontakte/add           - Neuen Kontakt anhaengen
  POST /api/kontakte/update        - Kontakt aendern (index-basiert)
  POST /api/kontakte/delete        - Kontakt entfernen (index-basiert)

Architektur:
  - Ein-Datei-Server auf stdlib (http.server) + optional pywin32 fuer Mail
  - CORS offen fuer http://localhost (eigener Browser)
  - Kein Auth - lokaler Dev-Use only. Port 8765 (nicht nach aussen freigeben!)
    )annotationsN)SimpleHTTPRequestHandlerThreadingHTTPServer)Path)unquoteurlparsei="  02_Projekte042_Pilgerbrunnen02_Adresslistezadressliste_data.jsonz
inbox.jsonzapi_token.txtc                    t         j                  j                  dd      j                         } | r| S t        j                         r*	 t        j                  d      j                         }|r|S 	 ddl}|j                  d      }	 t        j                  |d       t        dt                |S # t        $ r Y Lw xY w# t        $ r}t        d	|        Y d}~|S d}~ww xY w)
zBBearer-Token aus Env, sonst aus api_token.txt, sonst neu erzeugen.WATCHDOG_API_TOKEN utf-8encodingr   N    z+[watchdog-server] Neuer API-Token erzeugt: z9[watchdog-server] Token konnte nicht gespeichert werden: )osenvirongetstrip
TOKEN_FILEexists	read_text	Exceptionsecretstoken_urlsafe
write_textprint)tokentr   newexcs        V   C:\Users\GüvenKaplangil\Desktop\Cowork\04_Fristen-Watchdog\scripts\watchdog_server.py_load_or_create_api_tokenr$   /   s    JJNN/4::<E	$$g$6<<>A  



#CQcG4;J<HI J  		  QI#OPPJQs*   'B2 )C 2	B>=B>	C#
CC#c                    t        j                  d|       } | j                  dd      j                  dd      } t        j                  dd|       } | j                  d      } | r| dv rt        d	      | S )
zBEntfernt Pfad-Traversal, limitiert Zeichen auf sichere Dateinamen.NFC/_\z[\x00-\x1f]r   z .).z..zungueltiger Dateiname)unicodedata	normalizereplaceresubr   
ValueError)names    r#   
_safe_namer2   G   sl      -D<<S!))$4D66."d+D::dD4;&011K    c                    | |z  j                         }| j                         }||j                  vr||k7  rt        d| d|       |S )zBResolve rel relativ zu root, verhindert Traversal ausserhalb root.zPfad z liegt ausserhalb )resolveparentsr0   )rootrelproot_rs       r#   _resolve_withinr;   S   sO    	A\\^FQYY1;5#5fX>??Hr3   c                L   	 ddl }ddl}ddlm}m} |j                          	 |j                  j                  d      j                  d      }|j                  d	      }|j                          || 
      z
  }|j                          ||
      z   }	|j                  }
	 d|
_        |
j                  d       g }d}|
D ]  }|dkD  r n|dz  }	 t        |dd      dk7  r#t        |dd      }t        |dd      }|@	  ||j                   |j"                  |j$                  |j&                  |j(                        }|r> ||j                   |j"                  |j$                  |j&                  |j(                        nd}||k  s||	kD  rt        |dd      xs ddd }|j+                  t        |dd      xs d|j-                  d      |r|j-                  d      nd|rt/        ||z
  j1                         dz        ndt        |dd      xs dt        |dd      xs dt        |dd      xs dt        |dd      xs dt3        t        |dd            t        |dd      t        |dd      t3        t        |d d            |j5                         j7                  d!d"      dd d#        |j9                  d$ %       d|j                         j;                  d&'      |j;                  d('      |	j;                  d('      t=        |      |d)|j?                          S # t        $ r dddcY S w xY w# t        $ r Y w xY w# t        $ r Y w xY w# t        $ r Y w xY w# t        $ r"}dd*| dcY d}~|j?                          S d}~ww xY w# |j?                          w xY w)+zLiest Outlook-Kalender: Termine von (heute - past_days) bis (heute + future_days).
    Inkl. wiederkehrende Termine (via IncludeRecurrences + Restrict).
    r   N)datetime	timedeltaFpywin32 nicht installiert.okerrorOutlook.ApplicationMAPI	   daysTz[Start]i     Class   StartEndBodyr   Subjectz%Y-%m-%dT%H:%M<   Location	OrganizerRequiredAttendeesOptionalAttendeesAllDayEvent
BusyStatus   MeetingStatusIsRecurring

)subjectstartendduration_minlocation	organizerrequired_attendeesoptional_attendeesall_daybusy_statusmeeting_statusis_recurringbody_snippetc                &    | j                  dd      S )Nr\   r   )r   )xs    r#   <lambda>z'outlook_calendar_scan.<locals>.<lambda>   s    quuWb1r3   )keysecondstimespecminutes)rA   
scanned_atfromtocountterminezCalendar-Scan-Fehler: ) win32com.client	pythoncomr=   r>   ImportErrorCoInitializeclientDispatchGetNamespaceGetDefaultFoldernowItemsIncludeRecurrencesSortr   getattryearmonthdayhourminuteappendstrftimeinttotal_secondsboolr   r-   sort	isoformatlenCoUninitialize)	past_daysfuture_dayswin32comrv   r=   r>   outlookcalfrom_dtto_dtitemsout	inspecteditemr\   r]   start_pyend_pybodyr"   s                       r#   outlook_calendar_scanr   ]   s   D0 ?#//**+@ANNvV&&q),,.9)#<<!<<			'+E$JJy! 	D4NI4$/25gt4dE40='

EKKEJJX]XdXdeH]`Xchh		377CHHcjjYfjF g%E)9fb17R$?

&tY;Ar%../?@@F6??+;<BU[C():(I(I(Kb(P$Qab 'j" = C!({B!?!E2*1$8KR*P*VTV*1$8KR*P*VTV#GD-$GH#*4q#A&-dOQ&G$(}e)L$M$(JJL$8$8$Fu$M ' J 	12",,.22I2F%%y%9//9/5X
 	  "G  D&BCCD  		& ! (    F(>se&DEE  "F 	  "s   L A;M# L3 7M# MM#  M<M# >B M>
MM# 	D M)A%M# L0/L03	M <M# ?M  M# 	MMM# MM	M M# M  M# #	N,N	3N4N 	NN N#c           	        	 ddl }ddl}ddlmm}m} |j                          i }	 |j                  j                  d      j                  d      }|j                  d	      }|j                  d
      }	j                          ||       z
  fddfd}
 |
|ddt        |dz  d            \  }} |
|	ddt        |dz  d            \  }}||d<   ||d<   d||j                         j                  d      j                  d      | |d|j                          S # t
        $ r dddcY S w xY w# t        $ r#}dd| |dcY d}~|j                          S d}~ww xY w# |j                          w xY w)ah  Liest Posteingang + Gesendet-Ordner der letzten `since_days` Tage via Outlook-COM.

    Nutzt KEINEN Restrict-Filter (der war unzuverlaessig bei gemischten Locales) -
    iteriert stattdessen alle Items sortiert nach ReceivedTime desc und filtert manuell.
    Sent-Ordner nutzt SentOn als Timestamp, nicht ReceivedTime (Restrict haette sonst leere Liste).
    r   N)r=   r>   timezoneFr?   r@   rC   rD         rF   c                ,   | y	 t        | d      r,| j                   | j                         j                  d      S  | j                  | j
                  | j                  | j                  | j                  | j                        S # t        $ r Y yw xY w)zPpywin32 gibt pywintypes.datetime mit tzinfo zurueck - auf naive local umrechnen.Ntzinfo)r   )hasattrr   
astimezoner-   r   r   r   r   r   secondr   )valr=   s    r#   	coerce_dtzoutlook_scan.<locals>.coerce_dt   s    {3)cjj.D>>+3343@@#))SWWchh

TWT^T^__ s   7B AB 	BBc                2   | j                   }	 |j                  d|z   dz   d       g }d}d}d}d}	|D ]  }
t        |      |k\  r n}|dz  }|dkD  r np	 t	        |
dd       }|dk7  r|dz  };t	        |
|d       } |      }|= t	        |
d	d             xs(  t	        |
d
d             xs  t	        |
dd             }|r|k  r|dz  } nt	        |
dd      xs dd d }|j                  |t	        |
dd      xs dt	        |
dd      xs dt	        |
dd      xs dt	        |
dd      xs dt	        |
dd      xs d|xs j                         j                  d      t        t	        |
dd            t	        |
j                  dd      dkD  t	        |
dd      |j                         j                  dd      d        ||t        |      |||	| j                   j                  dfS # t        $ r Y w xY w# t        $ r}|	dz  }	Y d }~d }~ww xY w)N[]Tr   rH     rI   +   ReceivedTimeSentOnCreationTimerM   r   i N  rN   
SenderNameSenderEmailAddressToCCz%Y-%m-%dT%H:%M:%SUnReadFCount
ImportancerY   rZ   )folderr[   	from_name
from_emailrr   ccreceivedunreadhas_attachments
importancerg   )r   keptskipped_classskipped_oldskipped_errtotal_in_folder)r~   r   r   r   r   r   r}   r   r   Attachmentsr   r-   r   )r   r1   	time_attrmax_nr   r   r   r   r   r   r   clsraw_tstsr   er   cutoffr=   s                   r#   read_folderz!outlook_scan.<locals>.read_folder   s   LLE

3?S0$7 CIMKKs8u$Q	s? !$6Cby%* $T9d;F"6*Bz&wt^T'JK  jyY`aegoquYvOw  j  |E  FM  NR  Tb  dh  Fi  |jb6k#q(#D&"5;VeDDJJ"&#*4B#?#E2%,T<%D%J&-d4H"&M&SQS%dD"5;%dD"5;%'%98<<>$C$CDW$X"&wtXu'E"F+243C3CWa+PST+T&-dL!&D(,

(<(<VT(J  / N iSTar}  OZ  ou  o{  o{  oA  oA  B  B  B]  V ! 1$Ks7   G- G=.A G=C2G=-	G:9G:=	HHHPosteingangr   rV      Gesendetr   inboxsentTrl   rm   )rA   r   r   rp   since
since_daysdiagnosticszOutlook-Scan-Fehler: )rA   rB   r   )r1   strr   r   r   r   returnztuple[list[dict], dict])ru   rv   r=   r>   r   rw   rx   ry   rz   r{   r|   r}   maxr   r   r   )r   limitr   rv   r>   r   r   r   r   r   r   inbox_mailsdiag_in
sent_mails	diag_sentr"   r   r   r=   s                   @@@r#   outlook_scanr      s   D:: KV#//**+@ANNvV((+''*)"<<
	2	Bh  +5-QTUZ^_U_acQdeW +D*hEUVJXZH[ \
I&G'F ",,.22I2F%%y%9$&
 	  "w  D&BCCDp  a(=cU&CT_``  "a 	  "sA   D% C*D9 %D65D69	E%E 
E%E(  E%%E( (E:c                \   	 ddl }ddl}|j                          	 |j                  j                  d      }|j                  d      }	| xs d|	_        |r||	_        |xs d|	_	        |r|xs d|	_
        n|xs d|	_        g }
	 |xs g D ]C  }t        |t              rmt        j                  j!                  |      s2dd| dc |
D ]  }|j#                           	 |j'                          S |	j(                  j+                  |       t        |t,              st/        |j1                  d	      xs d
      }|j1                  d      xs d}t3        j4                  |      }t6        dz  dt        j8                          d| z  }|j;                  |       |
j=                  |       |	j(                  j+                  t        |             F |	j?                  d       ddd|
D ]  }|j#                           	 |j'                          S # t        $ r dddcY S w xY w# t$        $ r Y gw xY w# t$        $ r Y Vw xY w# |
D ]!  }|j#                          # t$        $ r Y w xY w w xY w# t@        $ r"}dd| dcY d}~|j'                          S d}~ww xY w# |j'                          w xY w)zSendet eine Mail via Outlook-Desktop-COM.

    attachments: Liste von Pfaden ODER {"filename": str, "data_base64": str} fuer Base64-Content.
    r   NFzHpywin32 nicht installiert. Installiere mit: py -3 -m pip install pywin32r@   rC   r   zAnhang nicht gefunden: filename
anhang.bindata_base64scripts
__mailtmp_r(   TzNMail in Outlook geoeffnet (Display). Zum direkten Versand: mail.Send() nutzen.)rA   detailzOutlook-COM-Fehler: )!ru   rv   rw   rx   ry   rz   
CreateItemr   r   rN   HTMLBodyrM   
isinstancer   r   pathr   unlinkOSErrorr   r   Adddictr2   r   base64	b64decodeWATCHDOG_DIRgetpidwrite_bytesr   Displayr   )rr   r[   r   r   attachmentshtmlr   rv   r   mail
temp_filesattffndata_b64datatempr"   s                     r#   outlook_sendr     su   
r %#//**+@A!!!$(DG}" JBDM
DI!#
	%#)r)c3'77>>#.&+8OPSu6UVV  XXZ   	  "' $$((-T*#CGGJ$7$G<HB"ww}5;H!++H5D')3
299;-qQSPT6UUD$$T*%%d+$$((T3 * LL*z{XXZ   	  "S  r&pqqrJ $$w$$  XXZ$$    D(<SE&BCC  "D 	  "s   H A&I+ AI  I+ 	H!I+ -,I  C
I  $I+ )H19I+ HH!	H.*I+ -H..I+ 1	H=:I+ <H==I+  I(II(	I#	 I("I#	#I((I+ +	J4J;J<J JJ J+c           
        	 ddl }|j                  | dd      }g }g }d\  }}|j                  D ]  }||   }	g }
d}|	j	                  d	      D ]x  }||k\  r|
j                  d
g        n_|d| D cg c]  }|dn
t        |       }}|r#|d   dk(  r|j                          |r	|d   dk(  r|sc|
j                  |       |dz  }z |
st        d |
D              }|
D cg c]  }|dg|t        |      z
  z  z    }}|d   }t        |      dkD  r|dd ng }d|z   dz   }|ddj                  |      z   dz   z  }|ddj                  dg|z        z   dz   z  }|D ]#  }|ddj                  d |D              z   dz   z  }% |j                  |       |j                  |t        |      |d        dj                  |      }dd|dd |dd ||ddS # t        $ r dddcY S w xY wc c}w c c}w )uQ   openpyxl: alle Sheets als Markdown-Tabellen, Zellgrenze 200 Zeilen × 30 Spalten.r   NFz1openpyxl nicht installiert (pip install openpyxl)r@   T)	data_only	read_only)      )values_onlyu    … (weitere Zeilen ausgelassen)r   rH   c              3  2   K   | ]  }t        |        y wN)r   ).0rs     r#   	<genexpr>z _extract_xlsx.<locals>.<genexpr>i  s     ,t!s1vts   z
## Sheet: 

z|  | z |
z---c              3  r   K   | ]/  }t        |      j                  d d      j                  dd       1 yw)|z\|rZ    N)r   r-   )r	  cs     r#   r  z _extract_xlsx.<locals>.<genexpr>q  s0     #][\VWCFNN3$>$F$FtS$Q[\s   57)r1   rowscolsxlsx@ r   )sheetsr   rA   kindtextpreviewmeta)openpyxlrw   load_workbook
sheetnames	iter_rowsr   r   popr   r   join)streamr   r  wbparts
sheet_metaMAX_ROWSMAX_COLSsheetwsr  n_rowsrowr  cellsmax_colsr
  normheaderr   mdr  s                         r#   _extract_xlsxr1  M  s_   [ 
		$$		GBEJ HhY<<D<1C!?@A<?	NKNqAIb3q61NEKE"IO		 E"IOE"! 2 ,t,,8<=1RDHs1v-..=at9q=tABxbE!F*
dUZZ''&00
dUZZ( 233f<<A$#][\#]]]`fffB R5#d)XNO9 : ;;uDWf$tPS*is  BJ  _K  L  LI  [&YZZ[ L >s   G 1G*"G/G'&G'c           	        	 ddl m}  ||       }g }|j                  D ]4  }|j                  xs dj                         }|s$|j                  |       6 |j                  D ]  }|j                  d       |j                  D ]g  }|j                  D 	cg c]0  }	|	j                  xs dj                         j                  dd      2 }
}	|j                  d	j                  |
             i  dj                  |      }d
d|dd |dd t        |j                        t        |j                        |ddS # t        $ r dddcY S w xY wc c}	w )z0python-docx: Absaetze + Tabellen als Plain-Text.r   )DocumentFz7python-docx nicht installiert (pip install python-docx)r@   r   rZ   r  r  TdocxNr  r   )
paragraphstablesr   r  )r4  r3  rw   r5  r  r   r   r6  r  r,  r-   r!  r   )r"  r   r3  docr$  r9   r    tblr+  r  r,  r  s               r#   _extract_docxr9  w  s]   a! 6
CE^^VV\r  "LLO  zzR88CHK		R	1affl))+33D#>	ERLLE*+  
 99UDWf$tPS*mpqtqq  nA  MP  QT  Q[  Q[  M\  jr  _s  t  t  a&_``a Ss   D6 5E
6EEc                   	 ddl m}  ||       }g }t        |j                        D ]S  \  }}	 |j                         xs d}|j                         s,|j                  d|dz    d	|j                                 U d
j                  |      }dd|dd |dd t        |j                        |ddS # t        $ r dddcY S w xY w# t        $ r d}Y w xY w)zpypdf: Text aus allen Seiten.r   )	PdfReaderFz+pypdf nicht installiert (pip install pypdf)r@   r   z
--- Seite rH   z ---
r  TpdfNr  r   )pagesr   r  )pypdfr;  rw   	enumerater=  extract_textr   r   r   r!  r   )	r"  r   r;  readerr$  ipager    r  s	            r#   _extract_pdfrD    s    U# vFEV\\*4	!!#)rA 779LL:acU&<= + ;;uDtGV}dsgjkqkwkwgx  GO  ^P  Q  Q  U&STTU  	A	s"   B2 C2CCCCc                       e Zd Z fdZd Zd ZddZddZddZddZ	d dZ
d d	Zd d
Zd dZd dZd dZd dZd dZd dZd dZd dZd dZd dZd!dZd dZddZd"dZd dZd dZd dZ xZS )#WatchdogHandlerc                B    t        |   |dt        t              i| y )N	directory)super__init__r   COWORK_ROOT)selfargskwargs	__class__s      r#   rJ  zWatchdogHandler.__init__  s    $E#k*:EfEr3   c                     y r   rL  formatrM  s      r#   log_messagezWatchdogHandler.log_message      r3   c                     y r  rQ  rR  s      r#   	log_errorzWatchdogHandler.log_error  rU  r3   c                   t        j                  |d      j                  d      }| j                  |       | j	                  dd       | j	                  dt        t        |                   | j	                  dd       | j	                  d	d
       | j	                  dd       | j	                  dd       | j                          | j                  j                  |       y )NF)ensure_asciir   Content-Typezapplication/json; charset=utf-8Content-LengthAccess-Control-Allow-Origin*Access-Control-Allow-MethodsGET, POST, PUT, DELETE, OPTIONSAccess-Control-Allow-HeaderszCache-Controlzno-store)
jsondumpsencodesend_responsesend_headerr   r   end_headerswfilewrite)rL  codepayloadr   s       r#   _json_responsezWatchdogHandler._json_response  s    zz'6==gF4 )JK)3s4y>:6<79Z[7H*5

r3   c                `   t        | j                  j                  d      xs d      }|r| j                  j	                  |      nd}|j                  d      r|dd  }	 t        j                  |j                  d      xs d      S # t        j                  $ r}t        d|       d }~ww xY w)	Nr[  r   r3   s   ﻿   r   z{}zUngueltiger JSON-Body: )r   headersr   rfileread
startswithra  loadsdecodeJSONDecodeErrorr0   )rL  lengthrawr"   s       r#   
_read_jsonzWatchdogHandler._read_json  s    T\\%%&67<1=)/djjoof%S>>/*ab'C	>::cjj19T::## 	>6se<==	>s   'B B-B((B-c                `    t         sy| j                  j                  dd      }|dt          k(  S )zWPruefe Authorization: Bearer <token>. Token aus WATCHDOG_API_TOKEN env / api_token.txt.FAuthorizationr   zBearer )	API_TOKENrn  r   )rL  auths     r#   _check_bearerzWatchdogHandler._check_bearer  s0    ||4,,,r3   c                   g }t         j                         rC	 t        t         dd      5 }t        j                  |      }ddd       t        |t              sg }|j                  |       t         j                  d      }t        |dd      5 }t        j                  ||dd	       ddd       |j                  t                t        |      S # 1 sw Y   xY w# t        $ r g }Y w xY w# 1 sw Y   FxY w)
z(Append entry to inbox.json (atomic-ish).r
  r   r   Nz.tmpwFrV   rY  indent)
INBOX_PATHr   openra  loadr   listr   r   with_suffixdumpr-   r   )rL  entryr   r   tmps        r#   _append_inboxzWatchdogHandler._append_inbox  s    *cG< IIaLE =!%.E 	U$$V,#sW-IIeQU1= .J5z =<   .-s4   C C C C-CC C*)C*-C6c                   | j                         s| j                  dddd      S | j                         }|j                  d      r|j                  d      s| j                  dddd      S d	d
lm} |j                         }|j                  d      xs d|j                  d      dd z   |j                         |j                  d      xs dd|j                  d      |j                  dd      |j                  dd      |j                  d      xs |j                  dd      |j                  dd      |j                  dd      |j                  d      |j                  d      xs |j                  d      |j                  dd      |j                  d      xs g d}| j                  |      }| j                  d d!|d   |d"      S )#u  POST /api/inbox/rundgang
        Body: {projekt, ort?, gewerk?, text, fotos?[base64..], person?, personEmail?, personFirma?, datum?, status?}
        Auth: Bearer <API_TOKEN>
        Schreibt Eintrag in inbox.json — Browser-Watcher sync zu IndexedDB.
          F!Bearer-Token fehlt oder ungueltigr@   projektr    z(Felder 'projekt' und 'text' sind Pflichtr   r=   idzwa-%Y%m%dT%H%M%S%fNsourceopenclawrundgangortr   gewerkperson
personNamepersonEmailpersonFirmadatum%Y-%m-%dstatusoffenfotos)r  r   r  typer  r  r  r  r  r  r  r  r  r  r  TrA   r  queued	r|  rk  rw  r   r=   r}   r   r   r  rL  r   r=   r}   r  ns         r#   _handle_inbox_rundgangz&WatchdogHandler._handle_inbox_rundgang  s    !!#&&s5Cf,ghh xx	"$((6*:&&s5Cm,noo%lln((4.PECLL9J,KCR,P$P--/hhx(6Jxx	*88E2&hhx,((8,Jr0J88M2688M26HHV$XXg&B#,,z*Bhhx1XXg&,"
  u%""3t5;RS(TUUr3   c                   | j                         s| j                  dddd      S | j                         }|j                  d      r|j                  d      s| j                  dddd      S d	d
lm} |j                         }|j                  d      xs d|j                  d      dd z   |j                         |j                  d      xs dd|j                  d      |j                  d      |j                  d      xs d|j                  d      xs d|j                  d      xs d|j                  d      xs ddd	}| j                  |      }| j                  dd|d   |d      S )zPOST /api/inbox/frist
        Body: {projekt, aufgabe, frist (YYYY-MM-DD oder Klartext), verantw?, quelle?}
        Auth: Bearer <API_TOKEN>
        r  Fr  r@   r  aufgaber  z+Felder 'projekt' und 'aufgabe' sind Pflichtr   r  r  zfr-r  Nr  r  r  fristPruefenverantwGueven Kaplangilquellewhatsapp)typref)	r  r   r  r  r  r  r  r  r  r  Tr  r  r  s         r#   _handle_inbox_fristz#WatchdogHandler._handle_inbox_frist  sO   
 !!#&&s5Cf,ghh xx	"$((9*=&&s5Cp,qrr%lln((4.PECLL9J,KCR,P$P--/hhx(6Jxx	*xx	*XXg&3)xx	*@.@hhx(hJtxxPXGYGg]g,h

 u%""3t5;RS(TUUr3   c                    | j                  d       | j                  dd       | j                  dd       | j                  dd       | j                          y )N   r\  r]  r^  r_  r`  rZ  )rd  re  rf  rL  s    r#   
do_OPTIONSzWatchdogHandler.do_OPTIONS  sP    36<79Z[7Hr3   c                   t        | j                        j                  }	 |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j	                         S |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S |d	k(  r| j                         S |d
k(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S |dk(  r| j                         S | j!                  ddd| d       y # t"        $ r"}| j!                  ddd| d       Y d }~y d }~ww xY w)Nz/api/mail/sendz/api/mail/scanz/api/calendar/scanz/api/file/savez/api/status/savez/api/file/extractz/api/mail/stage-attachmentsz/api/plaene/uploadz/api/plaene/renamez/api/kontakte/addz/api/kontakte/updatez/api/kontakte/deletez/api/inbox/rundgangz/api/inbox/frist  FzUnbekannter POST-Endpunkt: r@   r   Server-Fehler: )r   r   _handle_mail_send_handle_mail_scan_handle_calendar_scan_handle_file_save_handle_status_save_handle_file_extract_handle_mail_stage_handle_plan_upload_handle_plan_rename_handle_kontakt_add_handle_kontakt_update_handle_kontakt_deleter  r  rk  r   )rL  r   r"   s      r#   do_POSTzWatchdogHandler.do_POST  s   		"'' 	V''--//''--//++1133''--//))//11**002244..00++//11++//11**//11--2244--2244,,2244))//11E>YZ^Y_<`%ab 	VEocU<S%TUU	Vsj   E  E  E   E  5E  
E  E  4E  	E  E  3E  E  E  2E  E   	F)FFc                   t        | j                        j                  }	 t        j                  d|      }|rFt	        |j                  d            }t	        |j                  d            }| j                  ||      S | j                  ddd| d       y # t        $ r"}| j                  ddd	| d       Y d }~y d }~ww xY w)
Nz^/api/plaene/([^/]+)/(.+)$rH   rV   r  FzUnbekannter DELETE-Endpunkt: r@   r   r  )	r   r   r.   matchr   group_handle_plan_deleterk  r   )rL  r   mr  dateir"   s         r#   	do_DELETEzWatchdogHandler.do_DELETEC  s    		"''	V6=A!!''!*-
+//??E>[\`[a<b%cd 	VEocU<S%TUU	Vs   AB ?B 	C!B>>Cc                    | j                         }t        |j                  dd            }t        |j                  dd            }t        ||      }| j	                  |j                  d      rd|       y d|       y )	Nr   rm  r   2   )r   r   rA   r  r   )rw  r   r   r   rk  )rL  r   r   r   results        r#   r  z!WatchdogHandler._handle_mail_scanP  sf     ,23
DHHWb)*5A6::d#3CfEfEr3   c                    | j                         }t        |j                  dd            }t        |j                  dd            }t        ||      }| j	                  |j                  d      rd|       y d|       y )	Nr   rV   r      )r   r   rA   r  r   )rw  r   r   r   rk  )rL  r   pastfuturer  s        r#   r  z%WatchdogHandler._handle_calendar_scanX  sf     488K+,TXXmR01&6J6::d#3CfEfEr3   c                   | j                         }|j                  d      xs d}|s| j                  dddd      S t        ||j                  d      xs d|j                  d      xs d|j                  d	      xs d|j                  d
      xs g t	        |j                  d                  }| j                  |j                  d      rd|       y d|       y )Nrr   r   r  FzFeld 'to' fehltr@   r   r[   r   r   r   )rr   r   r[   r   r   r   rA   r  r   )rw  r   rk  r   r   )rL  r   rr   r  s       r#   r  z!WatchdogHandler._handle_mail_send_  s     XXd^!r&&s5CT,UVVxx~#HHY'-2&!'R/52dhhv&'
 	6::d#3CfEfEr3   c           
        dd l }ddlm} | j                         }|j                  d      xs g }|s| j	                  dddd      S |j                         j                  d      }t        d	z  |z  }|j                  d
d
       g }|D ]l  }	 t        |j                  d      xs d      }	||	z  j                  t        j                  |j                  d      xs d             |j                  |	       n 	 |j                  dt!        |      g       | j	                  dd
t!        |      t#        |      |d       y # t        $ r'}
| j	                  ddd	 d|
 d      cY d }
~
c S d }
~
ww xY w# t        $ r Y hw xY w)Nr   r  r   r  FzKeine Anhaenger@   z%Y-%m-%d_%H%M%Szmail-outgoingTr6   exist_okr   r   r   r   r   zStage-Fehler z: zexplorer.exer  )rA   	stage_dirrs   files)
subprocessr=   rw  r   rk  r}   r   r   mkdirr2   r   r   r   r   r   Popenr   r   )rL  r  r=   r   r   stampr  savedar   r"   s              r#   r  z"WatchdogHandler._handle_mail_stageo  sr   % hh}-3&&s5CS,TUU''(9: ?2U:	t4Aej 1 A\BR,,V-=-=aeeM>R>XVX-YZR 	 	nc)n=> 	C3y>TWX]T^in!op  e**3uWYVZZ\]`\aGb0cdde
  		s1   A)D?9E2 ?	E/E*"E/*E/2	E>=E>c           	        ddl m } | j                         }|j                  d      }t        |t              s| j                  dddd      S t        dz  d	z  }|j                  d
      xs |j                         j                  d      }d||j                  d      xs dd|j                         j                  d      |d}	 |j                  t        j                  |dd      d       | j                  ddt        |j                  t                    j!                  dd      t#        |      |d   d       y# t        $ r"}| j                  ddd| d      cY d}~S d}~ww xY w) u)  Speichert den Status der Fristen direkt in status_latest.json — unabhaengig von der FSS-File-Picker-Verknuepfung.
        Body: { "fristen": { "<rowid>": { "status": "...", "notiz": "..." }, ... } }
        Der naechste Generator-Lauf liest die Datei und verschiebt erledigte Fristen ins Archiv.r   r  fristenr  Fz!Body muss { fristen: {...} } seinr@   z04_Fristen-Watchdogzstatus_latest.jsonstichtagr  rH   
bearbeiterr  watchdog_client_autosaverl   rm   )versionr  r  schreibender	updatedAtr  rV   r  r   r   r   Schreib-Fehler: Nr  Tr)   r'   r  )rA   pfadrs   r  )r=   rw  r   r   r   rk  rK  r}   r   r   r   ra  rb  r   r   relative_tor-   r   )rL  _dtr   r  targetr  rj  r"   s           r#   r  z#WatchdogHandler._handle_status_save  st    	- ((9%'4(&&s5Cf,ghh447KK88J'I3779+=+=j+I ((<0F4F6,,i,@
	^djjuQOZab 	Cc&:L:L[:Y6Z6b6bcgil6mx{  }D  yE  T[  \g  Th  "i  	j  	^&&s5EUVYUZC[,\]]	^s   )D? ?	E*E%E*%E*c                   ddl }| j                         }|j                  d      xs dj                         }|j                  d      xs d}|r|s| j	                  dddd	      S 	 t        j                  |      }t        |      dkD  r| j	                  dddd	      S |j                         }	 |j                  d      s|j                  d      r+| j	                  dt        |j                  |      |            S |j                  d      r+| j	                  dt        |j                  |      |            S |j                  d      r+| j	                  dt        |j                  |      |            S |j                  d      sD|j                  d      s3|j                  d      s"|j                  d      s|j                  d      r<|j                  dd      }| j	                  ddd|dd |dd dt        |      id       S | j	                  d!dd"| d	      S # t        $ r"}| j	                  ddd
| d	      cY d}~S d}~ww xY w# t        $ r"}| j	                  ddd#| d	      cY d}~S d}~ww xY w)$aI  Extrahiert Text/Tabellen-Inhalt aus Office/PDF-Dateien.
        Body: { "filename": "x.xlsx", "data_base64": "..." }
        Antwort: { "ok": true, "kind": "xlsx|docx|pdf", "text": "...", "preview": "...", "meta": {...} }
        Dadurch kann die Sidebar-KI Excel-/Word-Inhalte lesen, ohne dass man sie vorher in PDF konvertiert.r   Nr   r   r   r  Fz#filename + data_base64 erforderlichr@   zBase64-Fehler: i  i  zDatei > 25 MBz.xlsxz.xlsmr  z.docxz.pdfz.csvz.txtz.mdz.jsonz.logr   r-   )errorsTr  r  r   bytesr  i  zFormat nicht unterstuetzt: zExtraktions-Fehler: )iorw  r   r   rk  r   r   r   r   lowerendswithr1  BytesIOr9  rD  rs  )	rL  _ior   r   b64rv  r"   r  txts	            r#   r  z$WatchdogHandler._handle_file_extract  s   
 	 HHZ(.B557hh}%+s&&s5Ch,ijj	]""3'C s8&&&&s5?,STT 	b~~g&%..*A**3ckk#>NPX0YZZ~~g&**3ckk#>NPX0YZZ~~f%**3S[[=Mx0XYY~~f%)?5>>RWCX\a\j\jkr\sw|  xF  xF  GM  xNjjj;**3tVUXY`Z`Uanqrvsvnw  CJ  LO  PS  LT  BU  1V  W  W&&s5E`ai`jCk,lmm!  	]&&s5_UXTYCZ,[\\	]"  	b&&s5EYZ]Y^C_,`aa	bsV   'H 0AI =;I 9;I 5BI I 	I	'I>I	I		I7I2,I72I7c                J   | j                         }|j                  d      xs dj                         }|j                  d      xs d}|r|s| j                  dddd      S 	 t	        t
        |      }g d}t        |j                  t
                    j                  d	d
      t        fd|D              s| j                  ddd d      S |j                  j                  dd       	 |j                  t        j                  |             | j                  dd|j#                         j$                  d       y # t        $ r(}| j                  ddt        |      d      cY d }~S d }~ww xY w# t         $ r"}| j                  ddd| d      cY d }~S d }~ww xY w)Ntarget_pathr   r   r  Fz&target_path + data_base64 erforderlichr@   )z02_Projekte/z04_Fristen-Watchdog/fotos/z04_Fristen-Watchdog/uploads/r)   r'   c              3  @   K   | ]  }j                  |        y wr  )rq  )r	  r9   	rel_posixs     r#   r  z4WatchdogHandler._handle_file_save.<locals>.<genexpr>  s     E4Dq9''*4Ds   z(Zielpfad ausserhalb erlaubter Bereiche: Tr  r   r  r  rA   r  size)rw  r   r   rk  r;   rK  r0   r   r  r-   anyparentr  r   r   r   r   statst_size)rL  r   
target_relr  r  r"   allowed_prefixesr  s          @r#   r  z!WatchdogHandler._handle_file_save  s    hh}-3::<
hh}%+&&s5Ck,lmm	N$[*=F j**;78@@sK	E4DEE&&s5EmnwmxCy,z{{D48	^v//45 	CiI^I^!_`  	N&&s53s8,LMM	N  	^&&s5EUVYUZC[,\]]	^s<   $E 0$E7 	E4E/)E4/E47	F" FF"F"c           	     D   | j                         }|j                  d      xs d}|j                  d      xs d}|j                  d      xs d}|r|r|s| j                  dddd      S t        t	        |      z  d	z  }|j                  d
d
       t	        |      }||z  }|j                  t        j                  |             | j                  dd
t        |j                  t                    j                  dd      |j                         j                  d       y )Nr  r   r   r   r  Fz+projekt, filename, data_base64 erforderlichr@   	   00_PläneTr  r  r)   r'   r  )rw  r   rk  PROJEKTE_DIRr2   r  r   r   r   r   r  rK  r-   r   r  )rL  r   r  r   r   projekt_dirsafe_fnr  s           r#   r  z#WatchdogHandler._handle_plan_upload  s    ((9%+88J'-288M*0bhh&&s5Cp,qrr"Z%88;F$6X&w&6++H56Cc&:L:L[:Y6Z6b6bcgil6mw}  xC  xC  xE  xM  xM  "N  	Or3   c           	        t         t        |      z  dz  }|j                         s| j                  ddd| d      S t	        ||      }|j                         r|j                         s| j                  ddd| d      S |j                          | j                  ddt        |j                  t                    j                  d	d
      d       y )Nr  r  Fz$Projekt-Plan-Ordner nicht gefunden: r@   Datei nicht gefunden: r  Tr)   r'   )rA   	geloescht)r  r2   r   rk  r;   is_filer   r   r  rK  r-   )rL  r  r  r  r  s        r#   r  z#WatchdogHandler._handle_plan_delete  s    "Z%88;F!!#&&s5EijqirCs,tuu e4}}fnn&6&&s5E[\a[bCc,deeC3v?Q?QR]?^;_;g;ghlnq;r!str3   c                   | j                         }t        |j                  d      xs d      }|j                  d      xs d}t        |j                  d      xs d      }|r|r|s| j                  dddd      S t        |z  d	z  }t        ||      }|j                         s| j                  d
dd| d      S ||z  }|j                  |       | j                  dd||d       y )Nr  r   alt_nameneu_namer  Fz(projekt, alt_name, neu_name erforderlichr@   r  r  r
  r  T)rA   altneu)rw  r2   r   rk  r  r;   r   rename)rL  r   r  r  r  r  alt_pathneu_paths           r#   r  z#WatchdogHandler._handle_plan_rename  s     TXXi06B7hhz"(b*-34c&&s5Cm,noo"W,{:";4 &&s5E[\_[`Ca,bcc$!CS!EFr3   c                    t         j                         si g dS t        j                  t         j	                  d            S )N)r  kontakter   r   )ADRESSLISTE_PATHr   ra  rr  r   r  s    r#   _load_adresslistez!WatchdogHandler._load_adressliste  s7    &&(B//zz*44g4FGGr3   c                    t         j                  j                  dd       t         j                  t	        j
                  |dd      d       y )NTr  FrV   r  r   r   )r  r  r  r   ra  rb  )rL  r   s     r#   _save_adresslistez!WatchdogHandler._save_adressliste  s=    %%dT%B##DJJt%PQ$R]d#er3   c                   | j                         }|j                  d      xs i }|j                  d      s&|j                  d      s| j                  dddd      S | j                         }|j	                  dg       j                  |       | j                  |       | j                  d	d
t        |d         dz
  d       y )Nkontaktr  firmar  Fzperson oder firma erforderlichr@   r  r  TrH   rA   index)rw  r   rk  r  
setdefaultr   r  r   )rL  r   r  adrs       r#   r  z#WatchdogHandler._handle_kontakt_add  s     ((9%+{{8$W[[-A&&s5Cc,dee$$&z2&--g6s#Cs3z?7Ka7O!PQr3   c                z   | j                         }t        |j                  dd            }|j                  d      xs i }| j                         }|j                  d      xs g }|dk  s|t	        |      k\  r| j                  ddd| d	      S |||<   | j                  |       | j                  d
d|d       y )Nr  r  r  r  r   r  FUngueltiger Index r@   r  Tr  )rw  r   r   r  r   rk  r  )rL  r   idxr  r!  r  s         r#   r  z&WatchdogHandler._handle_kontakt_update  s     $((7B'(((9%+$$&77:&,"7cS]*&&s5EWX[W\C],^__s#Cs!;<r3   c                h   | j                         }t        |j                  dd            }| j                         }|j                  d      xs g }|dk  s|t	        |      k\  r| j                  ddd| d      S |j                  |      }| j                  |       | j                  d	d
|d       y )Nr  r  r  r   r  Fr#  r@   r  T)rA   entfernt)rw  r   r   r  r   rk  r   r  )rL  r   r$  r!  r  removeds         r#   r  z&WatchdogHandler._handle_kontakt_delete!  s     $((7B'($$&77:&,"7cS]*&&s5EWX[W\C],^__,,s#s#C'!BCr3   )ri  r   rj  r   r   None)r   r   )r   r   )r  r   r   r   r   r(  )r  r   r  r   r   r(  )r   r   r   r(  )__name__
__module____qualname__rJ  rT  rW  rk  rw  r|  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  __classcell__)rO  s   @r#   rF  rF    s    F
	>-$V@V6"VJ
VFFF q4j2bBa.OuG H
fR
=	Dr3   rF  c                 >   t        dt                t        dt                t        dt                t        d       t        d       t	        dt        ft
              } 	 | j                          y # t        $ r t        d       | j                          Y y w xY w)Nz3SWARM-Watchdog-Server startet auf http://localhost:z
Root:     z
Watchdog: z8API:      /api/mail/send, /api/plaene/*, /api/kontakte/*z(Strg+C zum Beenden)z	127.0.0.1z	
Beendet.)	r   PORTrK  r   r   rF  serve_foreverKeyboardInterruptserver_close)servers    r#   mainr4  -  s    	?v
FG	J{m
$%	J|n
%&	DF	
 ! +t!4oFF ls   $A5 5$BB__main__)r   r   )r1   r   r   r   )r7   r   r8   r   r   r   )rV   r  )r   r   r   r   r   r   )   P   )r   r   r   r   r   r   )r   NF)rr   r   r[   r   r   r   r   r   r   zlist | Noner   r   r   r   )r   r   r   r   r)  )*__doc__
__future__r   r   ra  r   r.   shutilsysr+   http.serverr   r   pathlibr   urllib.parser   r   r/  __file__r5   r  
SCRIPT_DIRr   rK  r  r  r  r   r$   rz  r2   r;   r   r   r   r1  r9  rD  rF  r4  r*  rQ  r3   r#   <module>rA     s  & #   	 	  
  E  * (^##%,,
  !!]*"558HHKbb  L(
O+
* &'		K#^f#R1#p(LTt(Q&LD. LD^ zF r3   