Skip to content

Instantly share code, notes, and snippets.

@gphg
Last active October 6, 2025 10:36
Show Gist options
  • Save gphg/22a1bfa148a9e8ebe8d72558c07bf7f3 to your computer and use it in GitHub Desktop.
Save gphg/22a1bfa148a9e8ebe8d72558c07bf7f3 to your computer and use it in GitHub Desktop.
POSIX-powered web page monitor. 🀏
*.txt
*.html
#!/usr/bin/bash
# --- Configuration & Environment Variables ---
# Set by user via environment:
# BREAK_ON_FOUND: 1 to stop monitoring after the first alert, 0 (default) to continue.
# NOTIFICATION_MEDIA: Path to the audio file for Termux alerts.
# CUSTOM_CHECK_PATTERN: The pattern to search for in the custom failure check. Defaults to "thumbnail".
# --- Argument Handling ---
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <URL> <SEARCH_TEXT> [INTERVAL_SECONDS]"
echo " <URL> - The web page URL to monitor (e.g., https://example.com)."
echo " <SEARCH_TEXT> - The text to search for (case-insensitive)."
echo " [INTERVAL_SECONDS] - Optional. The base time in seconds for the interval. Must be a positive integer. Defaults to 300."
exit 1
fi
# Assign arguments to variables
URL="$1"
SEARCH_TEXT="$2"
BASE_INTERVAL=${3:-300}
# --- Input Validation ---
if ! [[ "$BASE_INTERVAL" =~ ^[0-9]+$ ]] || (( BASE_INTERVAL == 0 )); then
echo "Error: The interval must be a positive integer."
echo "Usage: $0 <URL> <SEARCH_TEXT> [INTERVAL_SECONDS]"
exit 1
fi
# The file to store and retrieve cookies from
COOKIE_FILE="cookies.txt"
# New: Files to store the persistent User-Agent and Language
UA_FILE="user_agent.txt"
LANG_FILE="accept_lang.txt"
# Stop the loop if found
BREAK_ON_FOUND="${BREAK_ON_FOUND:-0}"
# Configuration variables (defaults if not set by user)
NOTIFICATION_MEDIA="${NOTIFICATION_MEDIA:-}"
CUSTOM_CHECK_PATTERN="${CUSTOM_CHECK_PATTERN:-thumbnail}"
# --- Core Functions ---
# Function to get or set a persistent User-Agent string
get_persistent_user_agent() {
if [[ -f "$UA_FILE" ]] && [[ -s "$UA_FILE" ]]; then
USER_AGENT=$(<"$UA_FILE")
echo "$USER_AGENT"
return
fi
local agents=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.113 Mobile Safari/537.36"
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0; Trident/5.0)"
)
local count=${#agents[@]}
local random_index=$(( RANDOM % count ))
echo "${agents[random_index]}" > "$UA_FILE"
echo "${agents[random_index]}"
}
# Function: Gets or sets a persistent Accept-Language header
get_persistent_accept_language() {
if [[ -f "$LANG_FILE" ]] && [[ -s "$LANG_FILE" ]]; then
LANG_HEADER=$(<"$LANG_FILE")
echo "$LANG_HEADER"
return
fi
local languages=(
"en-US,en;q=0.9" # Standard US English
"id-ID,id;q=0.9,en-US;q=0.8" # Indonesian preference
"en-GB,en;q=0.9" # UK English
)
local count=${#languages[@]}
local random_index=$(( RANDOM % count ))
echo "${languages[random_index]}" > "$LANG_FILE"
echo "${languages[random_index]}"
}
__termux_notify() {
local title="$1"
local body="$2"
local mute="$3"
if command -v termux-notification >/dev/null && command -v termux-media-player >/dev/null; then
termux-notification --sound -t "$title" -c "$body"
if [[ -n "$NOTIFICATION_MEDIA" && "$mute" != "true" ]]; then
sleep 3
termux-media-player play "$NOTIFICATION_MEDIA"
fi
else
echo "Termux API commands not found. Install with 'pkg install termux-api'."
fi
}
notify() {
local title="$1"
local body="$2"
local mute="${3:-false}"
case "$OSTYPE" in
linux-gnu)
notify-send "$title" "$body"
;;
msys | mingw*)
powershell.exe -c "[System.Windows.MessageBox]::Show('$body', '$title')"
;;
linux-android)
__termux_notify "$title" "$body" "$mute"
;;
*)
echo "Alert! The text was found. $OSTYPE is not supported for native notifications."
;;
esac
}
# --- Initialization (Cold Path) ---
# Get the persistent User-Agent and Accept-Language once at startup
PERSISTENT_USER_AGENT=$(get_persistent_user_agent)
PERSISTENT_ACCEPT_LANG=$(get_persistent_accept_language)
# Define the standard Accept header for the request (fixed, as a browser's is)
ACCEPT_HEADER="text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
DNT_HEADER="1"
# --- Script Logic ---
echo "Starting web page monitor..."
echo "URL: $URL"
echo "Searching for: '$SEARCH_TEXT' (case-insensitive)"
echo "Persistent User-Agent: $PERSISTENT_USER_AGENT"
echo "Persistent Accept-Language: $PERSISTENT_ACCEPT_LANG"
echo "Referer set to: $URL (simulating page refresh)"
echo "Break on Found: $BREAK_ON_FOUND"
echo "Press Ctrl+C to stop."
# Check if cookie file exists.
if [ ! -f "$COOKIE_FILE" ]; then
touch "$COOKIE_FILE"
fi
while true; do
echo "---"
echo "Checking at $(date)..."
# --- Random Interval Calculation ---
RANDOM_ADJUSTMENT=$(( $RANDOM % 41 - 20 ))
RANDOM_INTERVAL=$(( BASE_INTERVAL * (100 + RANDOM_ADJUSTMENT) / 100 ))
if (( RANDOM_INTERVAL <= 0 )); then
RANDOM_INTERVAL=1
fi
echo "Sleep for $RANDOM_INTERVAL seconds (Base: $BASE_INTERVAL)"
# GET request using all persistent and standard headers
RESPONSE_BODY=$(curl --compressed -sL \
--cookie "$COOKIE_FILE" \
--cookie-jar "$COOKIE_FILE" \
-A "$PERSISTENT_USER_AGENT" \
-H "Accept: $ACCEPT_HEADER" \
-H "Accept-Language: $PERSISTENT_ACCEPT_LANG" \
-H "DNT: $DNT_HEADER" \
-H "Referer: $URL" \
"$URL")
# Write down to a file for checkup.
echo "$RESPONSE_BODY" > __monitor_body.html
# Check 1: Primary Search Text Found
if grep -iq "$SEARCH_TEXT" <<< "$RESPONSE_BODY"; then
echo "ALERT: The text '$SEARCH_TEXT' was found on the page!"
notify "Website Alert" "Text found on $URL"
if [ "$BREAK_ON_FOUND" == "1" ]; then
break
fi
# Check 2: Custom failure condition
elif ! grep -q "$CUSTOM_CHECK_PATTERN" <<< "$RESPONSE_BODY"; then
echo "ALERT: Entry on the page seems to be empty or missing required element ('$CUSTOM_CHECK_PATTERN')!"
notify "Secondary Alert" "Entry missing element '$CUSTOM_CHECK_PATTERN' on $URL" "true"
else
echo "Text not found. Waiting for $RANDOM_INTERVAL seconds..."
fi
sleep "$RANDOM_INTERVAL"
done
echo "Monitoring stopped."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment