Last active
October 6, 2025 10:36
-
-
Save gphg/22a1bfa148a9e8ebe8d72558c07bf7f3 to your computer and use it in GitHub Desktop.
POSIX-powered web page monitor. π€
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.txt | |
*.html |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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