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' -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