diff --git a/scripts/jobber-cron.env.example b/scripts/jobber-cron.env.example index ff5a6a4..7cb95b3 100644 --- a/scripts/jobber-cron.env.example +++ b/scripts/jobber-cron.env.example @@ -6,6 +6,9 @@ TELEGRAM_BOT_TOKEN="" TELEGRAM_CHAT_ID="" JOBOPS_URL="http://127.0.0.1:3005" +# Optional: cap how many job lines (title + link) are appended to the Telegram message (default 25). +# JOB_TELEGRAM_MAX_JOBS=25 + # Optional — only if BASIC_AUTH_USER / BASIC_AUTH_PASSWORD are set in Jobber .env # BASIC_AUTH_USER="" # BASIC_AUTH_PASSWORD="" diff --git a/scripts/jobber-pipeline-telegram.sh b/scripts/jobber-pipeline-telegram.sh index fb32402..6dd72f2 100755 --- a/scripts/jobber-pipeline-telegram.sh +++ b/scripts/jobber-pipeline-telegram.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Run Jobber pipeline, wait until it finishes, send summary to Telegram. +# Run Jobber pipeline, wait until it finishes, send summary + job links to Telegram. # Secrets: copy scripts/jobber-cron.env.example to /root/.jobber-cron.env (chmod 600). set -euo pipefail @@ -15,37 +15,110 @@ source "$ENV_FILE" : "${TELEGRAM_CHAT_ID:?Set TELEGRAM_CHAT_ID in $ENV_FILE}" BASE="${JOBOPS_URL:-http://127.0.0.1:3005}" +MAX_JOBS="${JOB_TELEGRAM_MAX_JOBS:-25}" AUTH=() if [[ -n "${BASIC_AUTH_USER:-}" && -n "${BASIC_AUTH_PASSWORD:-}" ]]; then AUTH=(-u "${BASIC_AUTH_USER}:${BASIC_AUTH_PASSWORD}") fi -send_tg() { +tg_html_escape() { + printf '%s' "$1" | sed -e 's/&/\&/g' -e 's//\>/g' +} + +tg_href_escape() { + printf '%s' "$1" | sed -e 's/&/\&/g' -e 's/"/\"/g' +} + +send_tg_html() { local msg="$1" curl -sS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -H "Content-Type: application/json" \ - -d "$(jq -n --arg c "$TELEGRAM_CHAT_ID" --arg t "$msg" '{chat_id: $c, text: $t}')" >/dev/null + -d "$(jq -n \ + --arg c "$TELEGRAM_CHAT_ID" \ + --arg t "$msg" \ + '{chat_id: $c, text: $t, parse_mode: "HTML", disable_web_page_preview: true}')" >/dev/null } fetch_status() { curl -sS "${AUTH[@]}" "${BASE}/api/pipeline/status" } +fetch_jobs_list() { + curl -sS "${AUTH[@]}" "${BASE}/api/jobs?view=list" +} + +build_job_lines_html() { + local jobs_json="$1" + local started="$2" + local completed="$3" + local max_n="$4" + + echo "$jobs_json" | jq -c --arg s "$started" --arg e "$completed" --argjson max "$max_n" ' + def pickurl: + if (.jobUrl // "") != "" then .jobUrl + elif (.applicationLink // "") != "" then .applicationLink + else "" end; + def pickrows($all): + ($all | map(select($s != "" and $e != "" and (.discoveredAt >= $s) and (.discoveredAt <= $e)))) as $win | + if ($win | length) > 0 then $win + else ($all | map(select($s != "" and (.discoveredAt >= $s)))) + end; + (.data.jobs // []) as $all | + if ($all | length) == 0 then + {total: 0, lines: []} + else + (pickrows($all) | sort_by(.discoveredAt) | reverse) as $sorted | + ($sorted | length) as $total | + ($sorted | .[0:max]) as $slice | + { + total: $total, + lines: [ + $slice[] | + { + title: (.title // "Untitled"), + url: pickurl, + employer: (.employer // "") + } + ] + } + end + ' +} + +append_lines_from_json() { + local sel="$1" + local -n _out="$2" + local item line title url emp + while IFS= read -r item; do + [[ -z "$item" || "$item" == "null" ]] && continue + title="$(echo "$item" | jq -r '.title // "Untitled"')" + url="$(echo "$item" | jq -r '.url // ""')" + emp="$(echo "$item" | jq -r '.employer // ""')" + if [[ -n "$url" && "$url" != "null" ]]; then + line="$(tg_html_escape "$emp") — $(tg_html_escape "$title")" + else + line="$(tg_html_escape "$emp") — $(tg_html_escape "$title") (no URL)" + fi + _out+=$'\n'"${line}" + done < <(echo "$sel" | jq -c '.lines[]? // empty') +} + body="$(fetch_status)" if ! echo "$body" | jq -e '.ok == true' >/dev/null 2>&1; then - send_tg "Jobber: /api/pipeline/status failed (before run). Check container." + send_tg_html "Jobber: /api/pipeline/status failed (before run). Check container." exit 1 fi if echo "$body" | jq -e '.data.isRunning == true' >/dev/null 2>&1; then - send_tg "Jobber: pipeline already running; skipping scheduled run." + send_tg_html "Jobber: pipeline already running; skipping scheduled run." exit 0 fi resp="$(curl -sS "${AUTH[@]}" -X POST "${BASE}/api/pipeline/run" \ -H "Content-Type: application/json" -d '{}')" if ! echo "$resp" | jq -e '.ok == true' >/dev/null 2>&1; then - send_tg "Jobber: POST /api/pipeline/run failed: $(echo "$resp" | jq -c .)" + _fail_json="$(echo "$resp" | jq -c . 2>/dev/null || echo "$resp")" + send_tg_html "Jobber: POST /api/pipeline/run failed: $(tg_html_escape "$_fail_json")" exit 1 fi @@ -54,7 +127,7 @@ for _ in $(seq 1 720); do sleep 30 body="$(fetch_status)" if ! echo "$body" | jq -e '.ok == true' >/dev/null 2>&1; then - send_tg "Jobber: status check failed mid-run." + send_tg_html "Jobber: status check failed mid-run." exit 1 fi running="$(echo "$body" | jq -r '.data.isRunning')" @@ -66,12 +139,37 @@ for _ in $(seq 1 720); do disc="$(echo "$lr" | jq -r '.jobsDiscovered // 0')" proc="$(echo "$lr" | jq -r '.jobsProcessed // 0')" err="$(echo "$lr" | jq -r '.errorMessage // empty')" - msg="Jobber pipeline finished: ${st}. Discovered: ${disc}, processed: ${proc}." - [[ -n "$err" ]] && msg="${msg}"$'\n'"Error: ${err}" - send_tg "$msg" + started="$(echo "$lr" | jq -r '.startedAt // ""')" + completed="$(echo "$lr" | jq -r '.completedAt // ""')" + + msg="Jobber pipeline: $(tg_html_escape "$st")" + msg+=$'\n'"Discovered: ${disc}, processed: ${proc}." + [[ -n "$err" ]] && msg+=$'\n'"Error: $(tg_html_escape "$err")" + + jobs_resp="$(fetch_jobs_list)" + if echo "$jobs_resp" | jq -e '.ok == true' >/dev/null 2>&1; then + sel="$(build_job_lines_html "$jobs_resp" "$started" "$completed" "$MAX_JOBS")" || + sel='{"total":0,"lines":[]}' + total="$(echo "$sel" | jq -r '.total // 0')" + shown="$(echo "$sel" | jq -r '.lines | length')" + if [[ "$total" -gt 0 ]]; then + msg+=$'\n\n'"Jobs in this run (showing ${shown} of ${total}):" + append_lines_from_json "$sel" msg + rest=$((total - shown)) + if [[ "$rest" -gt 0 ]]; then + msg+=$'\n\n'"…and ${rest} more not shown." + fi + else + msg+=$'\n\n'"No job rows matched this run window (time filter). Open the app for the full list." + fi + else + msg+=$'\n\n'"Could not load GET /api/jobs for links." + fi + + send_tg_html "$msg" exit 0 fi done -send_tg "Jobber: timed out waiting for pipeline (6h). Check server." +send_tg_html "Jobber: timed out waiting for pipeline (6h). Check server." exit 1