sync: Windmill-State übernehmen + neue Reporting-Flows
- Dateien nach Windmill-Naming-Konvention umbenannt (ssh-key_aus_db_testen, flow-fehler_per_nextcloud_talk_melden, bitwarden_(fallback)) - testpause-Schritt aus flow.yaml entfernt (Debugging abgeschlossen) - Neue Flows: f/Reporting/exchange_logins, f/Reporting/run_sql_events - mail_to_talk: Dateinamen nach Windmill-Konvention synchronisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,73 +1,49 @@
|
||||
summary: E-Mails zu Nextcloud Talk
|
||||
description: >
|
||||
Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen
|
||||
an einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert.
|
||||
schema:
|
||||
$schema: "http://json-schema.org/draft-07/schema"
|
||||
type: object
|
||||
properties:
|
||||
imap_config_1:
|
||||
type: object
|
||||
format: resource-imap
|
||||
description: "IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)"
|
||||
imap_config_2:
|
||||
type: object
|
||||
format: resource-imap
|
||||
description: "IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)"
|
||||
nextcloud_config:
|
||||
type: object
|
||||
format: resource-nextcloud
|
||||
description: "Nextcloud Zugangsdaten (Ressource: f/mail_to_talk/nextcloud_talk_config)"
|
||||
required:
|
||||
- imap_config_1
|
||||
- imap_config_2
|
||||
- nextcloud_config
|
||||
Liest ungelesene E-Mails von zwei IMAP-Konten und sendet Benachrichtigungen an
|
||||
einen Nextcloud Talk Raum. E-Mails werden als gelesen markiert.
|
||||
value:
|
||||
modules:
|
||||
- id: fetch_emails_1
|
||||
summary: Ungelesene E-Mails Konto 1 abrufen
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline fetch_emails.py"
|
||||
content: '!inline ungelesene_e-mails_konto_1_abrufen.py'
|
||||
input_transforms:
|
||||
imap_config:
|
||||
type: javascript
|
||||
expr: flow_input.imap_config_1
|
||||
lock: '!inline ungelesene_e-mails_konto_1_abrufen.lock'
|
||||
language: python3
|
||||
stop_after_if:
|
||||
expr: "false"
|
||||
error_message: null
|
||||
expr: 'false'
|
||||
skip_if_stopped: false
|
||||
|
||||
- id: fetch_emails_2
|
||||
summary: Ungelesene E-Mails Konto 2 abrufen
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline fetch_emails.py"
|
||||
content: '!inline ungelesene_e-mails_konto_2_abrufen.py'
|
||||
input_transforms:
|
||||
imap_config:
|
||||
type: javascript
|
||||
expr: flow_input.imap_config_2
|
||||
lock: '!inline ungelesene_e-mails_konto_2_abrufen.lock'
|
||||
language: python3
|
||||
stop_after_if:
|
||||
expr: "false"
|
||||
error_message: null
|
||||
expr: 'false'
|
||||
skip_if_stopped: false
|
||||
|
||||
- id: send_to_talk
|
||||
summary: Jede E-Mail an Nextcloud Talk senden
|
||||
value:
|
||||
type: forloopflow
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: "[...results.fetch_emails_1, ...results.fetch_emails_2]"
|
||||
skip_failures: false
|
||||
parallel: false
|
||||
modules:
|
||||
- id: send_message
|
||||
summary: Nachricht an Nextcloud Talk senden
|
||||
value:
|
||||
type: rawscript
|
||||
language: python3
|
||||
content: "!inline send_message.py"
|
||||
content: '!inline nachricht_an_nextcloud_talk_senden.py'
|
||||
input_transforms:
|
||||
email:
|
||||
type: javascript
|
||||
@@ -75,3 +51,31 @@ value:
|
||||
nextcloud_config:
|
||||
type: javascript
|
||||
expr: flow_input.nextcloud_config
|
||||
lock: '!inline nachricht_an_nextcloud_talk_senden.lock'
|
||||
language: python3
|
||||
iterator:
|
||||
type: javascript
|
||||
expr: '[...results.fetch_emails_1, ...results.fetch_emails_2]'
|
||||
parallel: false
|
||||
skip_failures: false
|
||||
schema:
|
||||
$schema: http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
imap_config_1:
|
||||
type: object
|
||||
description: 'IMAP Konto 1 (Ressource: f/mail_to_talk/imap_config)'
|
||||
format: resource-imap
|
||||
imap_config_2:
|
||||
type: object
|
||||
description: 'IMAP Konto 2 (Ressource: f/mail_to_talk/imap_config_2)'
|
||||
format: resource-imap
|
||||
nextcloud_config:
|
||||
type: object
|
||||
description: 'Nextcloud Zugangsdaten (Ressource:
|
||||
f/mail_to_talk/nextcloud_talk_config)'
|
||||
format: resource-nextcloud
|
||||
required:
|
||||
- imap_config_1
|
||||
- imap_config_2
|
||||
- nextcloud_config
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1 @@
|
||||
# py: 3.12
|
||||
@@ -0,0 +1,85 @@
|
||||
import imaplib
|
||||
import email
|
||||
from email.header import decode_header
|
||||
from typing import TypedDict, Optional, List
|
||||
from datetime import datetime
|
||||
import email.utils
|
||||
|
||||
|
||||
class imap(TypedDict):
|
||||
host: str
|
||||
port: int
|
||||
user: str
|
||||
password: str
|
||||
mailbox: Optional[str]
|
||||
|
||||
|
||||
def decode_str(value) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
parts = decode_header(value)
|
||||
result = []
|
||||
for part, charset in parts:
|
||||
if isinstance(part, bytes):
|
||||
result.append(part.decode(charset or "utf-8", errors="replace"))
|
||||
else:
|
||||
result.append(part)
|
||||
return "".join(result)
|
||||
|
||||
|
||||
def main(imap_config: imap) -> List[dict]:
|
||||
host = imap_config["host"]
|
||||
port = imap_config["port"]
|
||||
user = imap_config["user"]
|
||||
password = imap_config["password"]
|
||||
mailbox = imap_config.get("mailbox") or "INBOX"
|
||||
|
||||
if port == 993:
|
||||
client = imaplib.IMAP4_SSL(host, port)
|
||||
else:
|
||||
client = imaplib.IMAP4(host, port)
|
||||
|
||||
client.login(user, password)
|
||||
client.select(mailbox, readonly=False)
|
||||
|
||||
# Nur ungelesene E-Mails suchen
|
||||
status, data = client.search(None, "UNSEEN")
|
||||
if status != "OK" or not data[0]:
|
||||
client.logout()
|
||||
return []
|
||||
|
||||
uids = data[0].split()
|
||||
emails = []
|
||||
|
||||
for uid in uids:
|
||||
status, msg_data = client.fetch(uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])")
|
||||
if status != "OK":
|
||||
continue
|
||||
|
||||
raw = msg_data[0][1]
|
||||
msg = email.message_from_bytes(raw)
|
||||
|
||||
subject = decode_str(msg.get("Subject", "(Kein Betreff)"))
|
||||
from_raw = msg.get("From", "Unbekannt")
|
||||
from_addr = decode_str(from_raw)
|
||||
|
||||
date_raw = msg.get("Date", "")
|
||||
try:
|
||||
parsed_date = email.utils.parsedate_to_datetime(date_raw)
|
||||
date_str = parsed_date.strftime("%d.%m.%Y %H:%M:%S")
|
||||
except Exception:
|
||||
date_str = date_raw or "Unbekanntes Datum"
|
||||
|
||||
# Als gelesen markieren
|
||||
client.store(uid, "+FLAGS", "\\Seen")
|
||||
|
||||
emails.append({
|
||||
"subject": subject,
|
||||
"from": from_addr,
|
||||
"date": date_str,
|
||||
"uid": int(uid),
|
||||
"account": user,
|
||||
})
|
||||
|
||||
client.logout()
|
||||
return emails
|
||||
Reference in New Issue
Block a user