Compare commits

...

10 Commits

10 changed files with 27651 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
#!/bin/bash
## crontab
## 0 4 * * * bash /data/scripts/sslcerts/create_sites.sh > /data/scripts/sslcerts/sites.txt
## 0 5 * * * bash /data/scripts/sslcerts/checksslcerts.sh 2>&1 > /tmp/checksslcert.log
#!/bin/bash
sites=$(cat /data/scripts/sslcerts/sites.txt)
export SITE_SSL_PORT="443"
for site in `echo $sites`
do
export SITE_URL=$site
notAfter=$(/usr/bin/openssl s_client -connect ${SITE_URL}:${SITE_SSL_PORT} \
-servername ${SITE_URL} 2> /dev/null | /usr/bin/openssl x509 -noout -dates | grep notAfter)
not_after=$(echo "$notAfter" | cut -d= -f2-)
now_ts=$(date +%s)
expiry_ts=$(date -d "$not_after" +%s)
diff_sec=$((expiry_ts - now_ts))
diff_days=$((diff_sec / 86400))
if [ "$not_after" != "" ]
then
if [ "$diff_days" -lt 14 ]
then
echo "send warning für $site ($diff_days)"
url="https://msg.rproxy.conet-services.de/message.php"
# JSON-Daten für den POST-Request
json_data=$(cat <<EOF
{
"msg": "Certificate for $site is about to expire in $diff_days day(s)",
"subject": "Certificate expiration warning",
"x_conet_src": "conetadm@conlxscript1:/data/scripts/sslcerts/checksslcerts.sh",
"from_descr": "CONET Monitoring",
"from_email": "noreply@conet-services.de",
"to_email": "help@conet.de",
"reply_mail": "support-con@tasks.conet.de"
}
EOF
)
# Senden der POST-Anfrage mit curl
response=$(curl -s -X POST $url \
-H "Content-Type: application/json" \
-d "$json_data"
)
fi
printf "%02d days" "$diff_days"
echo -n "@"
printf "%30s" "$not_after"
echo -n ":"
printf "%40s\n" "$site"
echo "delete from sslcerts where cn='$site'" | /usr/bin/mysql -u conetadm -p'Conet12#' -Dscripts
echo "INSERT INTO sslcerts (expiration, datestring, cn) VALUES ('$diff_days', '$not_after', '$site') ON DUPLICATE KEY UPDATE cn = VALUES(cn);" | /usr/bin/mysql -u conetadm -p'Conet12#' -Dscripts
else
echo "cannot get certificate from $site"
fi
done

View File

@@ -0,0 +1,14 @@
#!/bin/bash
domains="conet-services.de tenoc.de hosting-ffm.de bankhaus-scheich.de"
for domain in `echo $domains`
do
curl -s "https://crt.sh/?q=%25.$domain&output=json" \
| grep -oE '"name_value":"[^"]+"' \
| cut -d':' -f2 \
| tr -d '"' \
| sed 's/\\n.*//' \
| grep -v '\*' \
| sort -u
done

5
check_sslcerts/sites.txt Normal file
View File

@@ -0,0 +1,5 @@
andregeissler.de
agserver.de
cloud.agserver.de

593
json_msg_2_mail/complete Normal file
View File

@@ -0,0 +1,593 @@
CREATE TABLE message (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
msg TEXT NULL,
subject VARCHAR(255) NULL,
x_conet_src VARCHAR(255) NULL,
in_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
sent TINYINT(1) NULL DEFAULT 0,
from_descr VARCHAR(255) NULL,
from_email VARCHAR(255) NULL,
to_descr VARCHAR(255) NULL,
to_email VARCHAR(255) NULL,
reply_mail VARCHAR(255) NULL,
sent_time TIMESTAMP NULL
);
DELIMITER //
CREATE TRIGGER update_sent_time
BEFORE UPDATE ON message
FOR EACH ROW
BEGIN
IF NEW.sent <> OLD.sent THEN
IF NEW.sent = TRUE THEN
SET NEW.sent_time = CURRENT_TIMESTAMP;
ELSE
SET NEW.sent_time = NULL;
END IF;
END IF;
END;
//
DELIMITER ;
##########################################################################################
{
"msg": "Hallo Welt",
"subject": "Test",
"x_conet_src": "app_42",
"from_descr": "Max Mustermann",
"from_email": "max@example.com",
"to_descr": "Lisa Beispiel",
"to_email": "lisa@example.com",
"reply_mail": "antwort@example.com"
}
##########################################################################################
pip install requests
########################################################################################## send_message.py
import requests
import json
url = "http://yourserver.com/api/message.php" # ändere das auf deine URL
payload = {
"msg": "Hallo Welt",
"subject": "Test",
"x_conet_src": "app_42",
"from_descr": "Max Mustermann",
"from_email": "max@example.com",
"to_descr": "Lisa Beispiel",
"to_email": "lisa@example.com",
"reply_mail": "antwort@example.com"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, data=json.dumps(payload), headers=headers)
print(f"Status: {response.status_code}")
print(f"Antwort: {response.text}")
##########################################################################################
cpan LWP::UserAgent JSON
########################################################################################## send_message.pl
#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use JSON;
my $url = 'http://yourserver.com/api/message.php'; # anpassen!
my %data = (
msg => "Hallo Welt",
subject => "Test",
x_conet_src => "app_42",
from_descr => "Max Mustermann",
from_email => "max@example.com",
to_descr => "Lisa Beispiel",
to_email => "lisa@example.com",
reply_mail => "antwort@example.com"
);
my $json = encode_json(\%data);
my $ua = LWP::UserAgent->new;
my $response = $ua->post(
$url,
'Content-Type' => 'application/json',
Content => $json
);
if ($response->is_success) {
print "Antwort: " . $response->decoded_content . "\n";
} else {
die "Fehler: " . $response->status_line;
}
########################################################################################## /var/www/html/api/message.php
<?php
// Ganz oben im PHP-Skript, vor allem anderen:
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
// DB-Zugangsdaten
$host = 'localhost';
$db = 'deine_datenbank';
$user = 'dein_user';
$pass = 'dein_passwort';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
// Verbindung aufbauen
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Verbindung fehlgeschlagen: " . $e->getMessage()]);
exit;
}
// JSON-Daten einlesen
$input = file_get_contents("php://input");
$data = json_decode($input, true);
// Pflichtfelder prüfen
$required = ['msg', 'subject', 'x_conet_src', 'from_descr', 'from_email', 'to_descr', 'to_email', 'reply_mail'];
foreach ($required as $field) {
if (!isset($data[$field])) {
http_response_code(400);
echo json_encode(["error" => "Feld fehlt: $field"]);
exit;
}
}
// Einfügen
try {
$sql = "INSERT INTO message (
msg, subject, x_conet_src,
from_descr, from_email,
to_descr, to_email, reply_mail
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
$data['msg'],
$data['subject'],
$data['x_conet_src'],
$data['from_descr'],
$data['from_email'],
$data['to_descr'],
$data['to_email'],
$data['reply_mail']
]);
echo json_encode([
"success" => true,
"inserted_id" => $pdo->lastInsertId()
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}
?>
########################################################################################## send_message.sh
#!/bin/bash
# URL des PHP-Servers
url="http://yourserver.com/api/message.php"
# JSON-Daten für den POST-Request
json_data=$(cat <<EOF
{
"msg": "Hallo Welt",
"subject": "Test",
"x_conet_src": "app_42",
"from_descr": "Max Mustermann",
"from_email": "max@example.com",
"to_descr": "Lisa Beispiel",
"to_email": "lisa@example.com",
"reply_mail": "antwort@example.com"
}
EOF
)
# Senden der POST-Anfrage mit curl
response=$(curl -s -X POST $url \
-H "Content-Type: application/json" \
-d "$json_data")
# Ausgabe der Antwort
echo "Antwort des Servers:"
echo "$response"
########################################################################################## /var/www/html/api/message.php
<?php
// DB-Zugangsdaten
$host = 'localhost';
$db = 'deine_datenbank';
$user = 'dein_user';
$pass = 'dein_passwort';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
// Verbindung aufbauen
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => "Verbindung fehlgeschlagen: " . $e->getMessage()]);
exit;
}
// CORS und Content-Type für JSON-Response
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
// Methode prüfen (GET oder POST)
$method = $_SERVER['REQUEST_METHOD'];
if ($method == 'POST') {
// JSON-Daten einlesen
$input = file_get_contents("php://input");
$data = json_decode($input, true);
// Pflichtfelder prüfen
$required = ['msg', 'subject', 'x_conet_src', 'from_descr', 'from_email', 'to_descr', 'to_email', 'reply_mail'];
foreach ($required as $field) {
if (!isset($data[$field])) {
http_response_code(400);
echo json_encode(["error" => "Feld fehlt: $field"]);
exit;
}
}
// Einfügen
try {
$sql = "INSERT INTO message (
msg, subject, x_conet_src,
from_descr, from_email,
to_descr, to_email, reply_mail
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
$data['msg'],
$data['subject'],
$data['x_conet_src'],
$data['from_descr'],
$data['from_email'],
$data['to_descr'],
$data['to_email'],
$data['reply_mail']
]);
echo json_encode([
"success" => true,
"inserted_id" => $pdo->lastInsertId()
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}
} elseif ($method == 'GET') {
// Alle Nachrichten abfragen
try {
$stmt = $pdo->query("SELECT * FROM message");
$messages = $stmt->fetchAll();
echo json_encode([
"success" => true,
"messages" => $messages
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
}
} else {
// Nicht unterstützte HTTP-Methode
http_response_code(405);
echo json_encode(["error" => "Methode nicht erlaubt."]);
}
?>
########################################################################################## send_pending_messages.pl
#!/usr/bin/perl
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use Mail::Sender;
# Datenbankverbindung
my $dsn = "DBI:mysql:deine_datenbank:localhost";
my $user = "dein_user";
my $pass = "dein_passwort";
my $dbh = DBI->connect($dsn, $user, $pass, { RaiseError => 1, AutoCommit => 1 })
or die "Verbindung zur Datenbank fehlgeschlagen: $DBI::errstr";
# Nachrichten mit sent = 0 abfragen
my $sth = $dbh->prepare("SELECT * FROM message WHERE sent = 0");
$sth->execute();
# SMTP-Konfiguration
my $smtp_server = 'smtp.example.com';
my $default_from = 'noreply@example.com';
while (my $row = $sth->fetchrow_hashref) {
my $to = $row->{to_email};
my $subject = $row->{subject};
my $body = $row->{msg};
my $from = $row->{from_email} || $default_from;
my $reply = $row->{reply_mail};
my $x_src = $row->{x_conet_src} || 'undefined';
# E-Mail senden
my $sender = Mail::Sender->new({
smtp => $smtp_server,
from => $from,
});
my $result = $sender->MailMsg({
to => $to,
subject => $subject,
msg => $body,
headers => {
'Reply-To' => $reply,
'X-Conet-Src' => $x_src,
}
});
if ($result) {
print "E-Mail an $to gesendet.\n";
$dbh->do("UPDATE message SET sent = 1 WHERE id = ?", undef, $row->{id});
} else {
print "Fehler beim Senden der E-Mail an $to.\n";
}
}
$sth->finish();
$dbh->disconnect();
########################################################################################## send_pending_messages.php
<?php
$host = 'localhost';
$db = 'deine_datenbank';
$user = 'dein_user';
$pass = 'dein_passwort';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
echo "Verbindung fehlgeschlagen: " . $e->getMessage();
exit;
}
$sql = "SELECT * FROM message WHERE sent = 0";
$stmt = $pdo->query($sql);
$messages = $stmt->fetchAll();
foreach ($messages as $message) {
$to = $message['to_email'];
$subject = $message['subject'];
$body = $message['msg'];
$from = $message['from_email'];
$reply = $message['reply_mail'];
$x_src = $message['x_conet_src'] ?? 'undefined';
$headers = "From: $from\r\n";
$headers .= "Reply-To: $reply\r\n";
$headers .= "X-Conet-Src: $x_src\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
if (mail($to, $subject, $body, $headers)) {
$update = $pdo->prepare("UPDATE message SET sent = 1 WHERE id = ?");
$update->execute([$message['id']]);
echo "E-Mail an $to gesendet.\n";
} else {
echo "Fehler beim Senden der E-Mail an $to.\n";
}
}
########################################################################################## check_and_send_emails.sh
#!/bin/bash
# PHP-Skript ausführen
php /path/to/send_pending_messages.php
########################################################################################## /etc/systemd/system/email_sender.service
[Unit]
Description=Send Pending Emails
[Service]
ExecStart=/path/to/check_and_send_emails.sh
User=www-data
Group=www-data
Environment=PATH=/usr/bin:/usr/local/bin
WorkingDirectory=/path/to/directory
[Install]
WantedBy=multi-user.target
########################################################################################## /etc/systemd/system/email_sender.timer
[Unit]
Description=Run Email Sender every 5 minutes
[Timer]
OnUnitActiveSec=5min
Unit=email_sender.service
[Install]
WantedBy=timers.target
##########################################################################################
sudo systemctl daemon-reload
sudo systemctl enable email_sender.service
sudo systemctl enable email_sender.timer
sudo systemctl start email_sender.timer
sudo systemctl status email_sender.timer
sudo journalctl -u email_sender.service
########################################################################################## GET-Anfrage zum Abrufen aller Nachrichten
curl -X GET http://yourserver.com/api/message.php
########################################################################################## view_messages.php
<?php
// DB-Zugangsdaten
$host = 'localhost';
$db = 'deine_datenbank';
$user = 'dein_user';
$pass = 'dein_passwort';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
die("Datenbankverbindung fehlgeschlagen: " . $e->getMessage());
}
// Filter/Suchparameter
$search = $_GET['search'] ?? '';
$sent = isset($_GET['sent']) ? (int) $_GET['sent'] : null;
$sort = in_array($_GET['sort'] ?? '', ['id', 'subject', 'in_time', 'sent']) ? $_GET['sort'] : 'in_time';
$order = ($_GET['order'] ?? '') === 'asc' ? 'ASC' : 'DESC';
// SQL-Query dynamisch bauen
$sql = "SELECT * FROM message WHERE 1";
$params = [];
if (in_array($range, ['7', '30', '180'])) {
$sql .= " AND in_time >= DATE_SUB(NOW(), INTERVAL :range DAY)";
$params['range'] = (int)$range;
}
if ($search !== '') {
$sql .= " AND (subject LIKE :search OR msg LIKE :search OR from_email LIKE :search)";
$params['search'] = "%$search%";
}
if ($sent !== null && in_array($sent, [0, 1])) {
$sql .= " AND sent = :sent";
$params['sent'] = $sent;
}
$sql .= " ORDER BY $sort $order";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$messages = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Nachrichtenübersicht</title>
<style>
body { font-family: sans-serif; margin: 2em; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 0.5em; border: 1px solid #ccc; text-align: left; }
th a { text-decoration: none; color: #000; }
</style>
</head>
<body>
<h1>Nachrichtenübersicht</h1>
<form method="get" style="margin-bottom: 1em;">
<input type="text" name="search" placeholder="Suche..." value="<?= htmlspecialchars($search) ?>">
<select name="sent">
<option value="">-- gesendet? --</option>
<option value="0" <?= $sent === 0 ? 'selected' : '' ?>>nicht gesendet</option>
<option value="1" <?= $sent === 1 ? 'selected' : '' ?>>gesendet</option>
</select>
<select name="range">
<option value="">Alle</option>
<option value="7" <?= $range === '7' ? 'selected' : '' ?>>Letzte 7 Tage</option>
<option value="30" <?= $range === '30' ? 'selected' : '' ?>>Letzte 30 Tage</option>
<option value="180" <?= $range === '180' ? 'selected' : '' ?>>Letzte 180 Tage</option>
</select>
<button type="submit">Filtern</button>
</form>
<table>
<thead>
<tr>
<?php
$cols = ['id' => 'ID', 'subject' => 'Betreff', 'in_time' => 'Eingang', 'sent' => 'Gesendet'];
foreach ($cols as $key => $label):
$next_order = ($sort === $key && $order === 'ASC') ? 'desc' : 'asc';
$link = "?search=" . urlencode($search) . "&sent=" . urlencode($sent ?? '') . "&sort=$key&order=$next_order";
echo "<th><a href=\"$link\">$label</a></th>";
endforeach;
?>
<th>Von</th>
<th>An</th>
<th>X-Conet-Src</th>
</tr>
</thead>
<tbody>
<?php foreach ($messages as $msg): ?>
<tr>
<td><?= htmlspecialchars($msg['id']) ?></td>
<td><?= htmlspecialchars($msg['subject']) ?></td>
<td><?= htmlspecialchars($msg['in_time']) ?></td>
<td><?= $msg['sent'] ? '✅' : '❌' ?></td>
<td><?= htmlspecialchars($msg['from_email']) ?></td>
<td><?= htmlspecialchars($msg['to_email']) ?></td>
<td><?= htmlspecialchars($msg['x_conet_src']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,3 @@
DB andregeissler_msg
USR andregeissler_msg
PW G0H1yrhEtQ

View File

@@ -0,0 +1,9 @@
Nimmt das Suchergebnis von
find . -name "*.pdf" -exec pdfgrep "Tattag|Tatzeit|Fahrzeug Kennzeichen|Marke|Tatort" {} > /home/andre/Dokumente/pdfs.txt +
in "pdfs.txt"
und erstellt daraus "anzeigen.csv" und "anzeigen.xlsx"
Liefert der find nur Inhalte von neuen Dateien und ist anzeigen.csv vorhanden, wird zunächst die csv eingelesen und um die neuen Einträge ergänzt. Doppplungen und Verluste gibt es damit nicht

File diff suppressed because it is too large Load Diff

Binary file not shown.

22312
owi pdfs nach ods/pdfs.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use open qw(:std :utf8);
use Excel::Writer::XLSX;
my $input = 'pdfs.txt';
my $csv_out = 'anzeigen.csv';
my $xlsx_out = 'anzeigen.xlsx';
my $sep = ';';
# Hilfsfunktion: trim
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s // '' }
# --- Bestehende CSV einlesen (falls vorhanden) ---
my %entries;
if (-e $csv_out) {
open(my $old, '<:encoding(UTF-8)', $csv_out) or die "Kann $csv_out nicht lesen: $!";
my $hdr = <$old>; # Kopfzeile überspringen
while (<$old>) {
chomp;
next unless length($_);
my @cols = split /$sep/, $_;
# trim alle Felder
@cols = map { trim($_) } @cols;
my ($datum, $start, $ende, $tatort, $kennz, $marke, $file) = @cols;
$entries{$file} = {
Datum => $datum // '',
'Tatzeit(Anfang)' => $start // '',
'Tatzeit(Ende)' => $ende // '',
Tatort => $tatort // '',
Kennzeichen => $kennz // '',
Marke => $marke // '',
Datei => $file // ''
};
}
close $old;
print "📂 Bestehende CSV geladen: " . scalar(keys %entries) . " Einträge\n";
}
# --- Neue Daten aus pdfs.txt einlesen ---
open(my $in, '<:encoding(UTF-8)', $input) or die "Kann $input nicht öffnen: $!";
while (<$in>) {
chomp;
next unless /:/;
my ($file, $content) = split(/:/, $_, 2);
$file = trim($file);
$content = trim($content);
# initialisieren falls neu
$entries{$file} //= {
Datum => '',
'Tatzeit(Anfang)' => '',
'Tatzeit(Ende)' => '',
Tatort => '',
Kennzeichen => '',
Marke => '',
Datei => $file
};
# Kennzeichen
if ($content =~ /Fahrzeug Kennzeichen\s+(.+)/i) {
$entries{$file}->{Kennzeichen} = trim($1);
next;
}
# Marke
if ($content =~ /Marke des Fahrzeuges\s+(.+)/i) {
$entries{$file}->{Marke} = trim($1);
next;
}
# Tatort
if ($content =~ /Tatort\s+(.+)/i) {
$entries{$file}->{Tatort} = trim($1);
next;
}
# Datum in der Form DD.MM.YYYY auf derselben Zeile
if ($content =~ /Tattag\s*\(Datum\)\s*([0-3]?\d\.[01]?\d\.\d{4})/) {
my $d = $1;
if ($d =~ /(\d{2})\.(\d{2})\.(\d{4})/) {
$entries{$file}->{Datum} = sprintf("%04d-%02d-%02d", $3, $2, $1);
}
# es könnten zusätzlich Zeiten auf dieser Zeile sein -> weiter prüfen unten
}
# Falls die Zeile "Tattag (Datum) (Tatzeit Bis)" ohne Datum, wir lassen Datum unverändert (nicht überschreiben)
# Zeiten: überall nach HH:MM suchen (erkennt auch "17:30- 17:49", "11:35 - 11:40 Uhr", "21:13" etc.)
if ($content =~ /Tatzeit|Tatzeit|Tatzeit.*|Tattag.*Tatzeit/i or $content =~ /(\d{1,2}:\d{2})/) {
my @times = ($content =~ /(\d{1,2}:\d{2})/g);
# Falls Zeiten gefunden wurden, setzen (überschreiben)
if (@times) {
$entries{$file}->{'Tatzeit(Anfang)'} = $times[0];
$entries{$file}->{'Tatzeit(Ende)'} = $times[1] // ''; # falls keine Endzeit, leer lassen
}
}
# Manchmal steht das Datum ohne Label (seltener), versuchen wir ein generelles Datumsmuster in content
if (!$entries{$file}->{Datum} && $content =~ /(\d{2}\.\d{2}\.\d{4})/) {
my $d = $1;
if ($d =~ /(\d{2})\.(\d{2})\.(\d{4})/) {
$entries{$file}->{Datum} = sprintf("%04d-%02d-%02d", $3, $2, $1);
}
}
}
close $in;
# --- Sortieren: neueste zuerst; falls Datum leer, ans Ende ---
my @sorted = sort {
my $da = $entries{$a}->{Datum} // '';
my $db = $entries{$b}->{Datum} // '';
# leere Datum nach unten
return $da cmp $db if $da eq '' || $db eq '';
($db cmp $da) || ( ($entries{$b}->{'Tatzeit(Anfang)'} // '') cmp ($entries{$a}->{'Tatzeit(Anfang)'} // '') )
} keys %entries;
# --- CSV schreiben (gewünschte Reihenfolge) ---
open(my $csv, '>:encoding(UTF-8)', $csv_out) or die "Kann $csv_out nicht schreiben: $!";
print $csv "Datum${sep}Tatzeit(Anfang)${sep}Tatzeit(Ende)${sep}Tatort${sep}Kennzeichen${sep}Marke${sep}Datei\n";
for my $e (@sorted) {
my $row = join($sep, map { $entries{$e}->{$_} // '' } qw(Datum Tatzeit(Anfang) Tatzeit(Ende) Tatort Kennzeichen Marke Datei));
print $csv "$row\n";
}
close $csv;
print "✅ CSV-Datei '$csv_out' erstellt.\n";
# --- XLSX erzeugen ---
my $workbook = Excel::Writer::XLSX->new( $xlsx_out );
my $worksheet = $workbook->add_worksheet();
# Formatierungen
my $bold_format = $workbook->add_format( bold => 1, bg_color => '#D9D9D9' );
my $date_format = $workbook->add_format( num_format => 'yyyy-mm-dd' );
my $time_format = $workbook->add_format( num_format => 'hh:mm' );
# Kopfzeile
my @header = qw(Datum Tatzeit(Anfang) Tatzeit(Ende) Tatort Kennzeichen Marke Datei);
$worksheet->write_row( 'A1', \@header, $bold_format );
# Datenzeilen
my $row_index = 1;
for my $e (@sorted) {
my @row_data = (
$entries{$e}->{Datum} // '',
$entries{$e}->{'Tatzeit(Anfang)'} // '',
$entries{$e}->{'Tatzeit(Ende)'} // '',
$entries{$e}->{Tatort} // '',
$entries{$e}->{Kennzeichen} // '',
$entries{$e}->{Marke} // '',
$entries{$e}->{Datei} // '',
);
for my $col (0..$#row_data) {
my $value = $row_data[$col];
# Datum als echtes Excel-Datum (ISO + T)
if ($col == 0 && $value =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
my $iso = sprintf("%04d-%02d-%02dT00:00:00", $1, $2, $3);
$worksheet->write_date_time($row_index, $col, $iso, $date_format);
}
# Zeit als echte Excel-Zeit (Bruchteil eines Tages)
elsif (($col == 1 || $col == 2) && $value =~ /^(\d{1,2}):(\d{2})$/) {
my ($h,$m) = ($1,$2);
my $time_fraction = ($h*3600 + $m*60)/86400;
$worksheet->write_number($row_index, $col, $time_fraction, $time_format);
}
else {
# Text (leer als leere Zelle)
$worksheet->write_string($row_index, $col, $value // '');
}
}
$row_index++;
}
# Optional: Autofit (nur grobe Breiten setzen)
$worksheet->set_column(0, 0, 12); # Datum
$worksheet->set_column(1, 2, 10); # Zeiten
$worksheet->set_column(3, 3, 30); # Tatort
$worksheet->set_column(4, 5, 15); # Kennzeichen, Marke
$worksheet->set_column(6, 6, 60); # Datei
$workbook->close;
print "📊 XLSX-Datei '$xlsx_out' erstellt alle Daten sichtbar und formatiert.\n";