#!/usr/bin/env bash # Holt alle Zammad-Articles der letzten X Tage, gefiltert nach type_id und created_by_id. # # Aufruf: ZAMMAD_TOKEN=xxx ./zammad_articles.sh [TAGE] [CREATED_BY_ID] [TYPE_ID] # Beispiel: ZAMMAD_TOKEN=xxx ./zammad_articles.sh 2 1764 1 > mails.json # # Voraussetzungen: curl, jq, API-Token mit Admin-Rechten (für /ticket_articles) set -euo pipefail BASE_URL="${ZAMMAD_URL:-https://aikava.zammad.com}" TOKEN="${ZAMMAD_TOKEN:?Bitte ZAMMAD_TOKEN setzen}" DAYS="${1:-2}" CREATED_BY="${2:-1764}" TYPE_ID="${3:-1}" PER_PAGE=1000 MAX_PAGE=1000000 # Schutz gegen Endlosschleifen # Call-Zähler über eine Temp-Datei, da fetch_page oft in $(...) läuft CALLS_FILE=$(mktemp) trap 'rm -f "$CALLS_FILE"' EXIT calls() { wc -l < "$CALLS_FILE" | tr -d ' '; } # Stichtag in UTC (GNU date oder BSD/macOS date) # CALENDAR=1: ab lokaler Mitternacht vor (TAGE-1) Tagen, also TAGE=2 -> gestern 00:00 bis jetzt # sonst: rollierend, jetzt minus TAGE*24h if date -u -d "now" >/dev/null 2>&1; then if [ "${CALENDAR:-0}" = "1" ]; then TS=$(date -d "$((DAYS - 1)) days ago 00:00" +%s) else TS=$(date -d "${DAYS} days ago" +%s) fi CUTOFF=$(date -u -d "@${TS}" +%Y-%m-%dT%H:%M:%S) else if [ "${CALENDAR:-0}" = "1" ]; then TS=$(date -v-"$((DAYS - 1))"d -v0H -v0M -v0S +%s) else TS=$(date -v-"${DAYS}"d +%s) fi CUTOFF=$(date -u -r "${TS}" +%Y-%m-%dT%H:%M:%S) fi log() { echo "$*" >&2; } fetch_page() { echo "$1" >> "$CALLS_FILE" curl -sf -H "Authorization: Token token=${TOKEN}" \ "${BASE_URL}/api/v1/ticket_articles?page=$1&per_page=${PER_PAGE}" } page_len() { fetch_page "$1" | jq 'length'; } log "Stichtag: ${CUTOFF}Z | created_by_id=${CREATED_BY} | type_id=${TYPE_ID}" # 1) Letzte nicht-leere Seite per Binärsuche finden if [ "$(page_len 1)" -eq 0 ]; then log "Keine Articles gefunden. (API-Calls: $(calls))" echo "[]" exit 0 fi lo=1; hi=2 while [ "$hi" -le "$MAX_PAGE" ] && [ "$(page_len "$hi")" -gt 0 ]; do lo=$hi; hi=$((hi * 2)) done while [ $((hi - lo)) -gt 1 ]; do mid=$(( (lo + hi) / 2 )) if [ "$(page_len "$mid")" -gt 0 ]; then lo=$mid; else hi=$mid; fi done SEARCH_CALLS=$(calls) log "Letzte Seite: ${lo} (Binärsuche: ${SEARCH_CALLS} Calls)" # 2) Rückwärts lesen bis zum Stichtag und filtern results="[]" p=$lo while [ "$p" -ge 1 ]; do batch=$(fetch_page "$p") matches=$(jq --arg c "$CUTOFF" --argjson u "$CREATED_BY" --argjson t "$TYPE_ID" \ '[.[] | select(.created_at >= $c and .type_id == $t and .created_by_id == $u)]' <<<"$batch") results=$(jq -s 'add' <(echo "$results") <(echo "$matches")) oldest=$(jq -r '.[0].created_at // empty' <<<"$batch") log "Seite ${p}: $(jq 'length' <<<"$matches") Treffer (ältester Article: ${oldest})" [[ -z "$oldest" || "$oldest" < "$CUTOFF" ]] && break p=$((p - 1)) done TOTAL_CALLS=$(calls) log "Gesamt: $(jq 'length' <<<"$results") Articles" log "API-Calls: ${TOTAL_CALLS} (Binärsuche: ${SEARCH_CALLS}, Rückwärts lesen: $((TOTAL_CALLS - SEARCH_CALLS)))" jq 'sort_by(.id)' <<<"$results"