#!/bin/sh # history browser sync (encrypted with SQLCipher) # Syncs search history from browsers to unified database # Run via systemd timer every minute DB_PATH="$HOME/.local/share/history/history.db" KEY_FILE="$HOME/.local/share/history/.key" LAST_SYNC_FILE="$HOME/.local/share/history/.last_sync" # Load encryption key if [ ! -f "$KEY_FILE" ]; then echo "Encryption key not found. Run 'history-init' first." >&2 exit 1 fi KEY=$(cat "$KEY_FILE") # Get last sync timestamp or default to epoch if [ -f "$LAST_SYNC_FILE" ]; then LAST_SYNC=$(cat "$LAST_SYNC_FILE") else LAST_SYNC="1970-01-01 00:00:00" fi # Current timestamp NOW=$(date '+%Y-%m-%d %H:%M:%S') # Function to extract search query from URL extract_search_query() { local url="$1" local query="" # Google, DuckDuckGo, Bing, Brave Search query=$(echo "$url" | grep -oP '(?<=[?&]q=)[^&]*' | head -1) [ -n "$query" ] && echo "$query" | sed 's/+/ /g' | python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))" 2>/dev/null && return # YouTube query=$(echo "$url" | grep -oP '(?<=search_query=)[^&]*' | head -1) [ -n "$query" ] && echo "$query" | sed 's/+/ /g' | python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))" 2>/dev/null && return echo "" } # Check if URL is a search is_search_url() { local url="$1" echo "$url" | grep -qE '(google\.(com|[a-z]{2})/search|duckduckgo\.com/\?|bing\.com/search|search\.brave\.com/search|youtube\.com/(results|search))' } # Sync from Firefox/Librewolf sync_firefox() { local browser="$1" local profile_dir="$2" [ ! -d "$profile_dir" ] && return # Find the default profile local profile=$(find "$profile_dir" -maxdepth 1 -name "*.default*" -type d 2>/dev/null | head -1) [ -z "$profile" ] && return local places_db="$profile/places.sqlite" [ ! -f "$places_db" ] && return # Copy to temp to avoid lock issues local temp_db=$(mktemp) cp "$places_db" "$temp_db" 2>/dev/null || return # Extract URLs with timestamps sqlite3 "$temp_db" "SELECT datetime(visit_date/1000000, 'unixepoch', 'localtime'), url FROM moz_historyvisits JOIN moz_places ON moz_historyvisits.place_id = moz_places.id WHERE datetime(visit_date/1000000, 'unixepoch', 'localtime') > '$LAST_SYNC' ORDER BY visit_date;" 2>/dev/null | while IFS='|' read -r timestamp url; do if is_search_url "$url"; then query=$(extract_search_query "$url") if [ -n "$query" ]; then # Escape quotes query=$(echo "$query" | sed "s/'/''/g") url=$(echo "$url" | sed "s/'/''/g") hash=$(echo "${timestamp}${browser}${query}" | md5sum | cut -d' ' -f1) sqlcipher "$DB_PATH" "PRAGMA key = '$KEY'; INSERT OR IGNORE INTO search_history (timestamp, query, browser, url, hash) VALUES ('$timestamp', '$query', '$browser', '$url', '$hash');" 2>/dev/null fi fi done rm -f "$temp_db" } # Sync from Chromium-based browsers sync_chromium() { local browser="$1" local history_file="$2" [ ! -f "$history_file" ] && return # Copy to temp to avoid lock issues local temp_db=$(mktemp) cp "$history_file" "$temp_db" 2>/dev/null || return # Chromium stores time as microseconds since 1601-01-01 sqlite3 "$temp_db" "SELECT datetime((last_visit_time - 11644473600000000) / 1000000, 'unixepoch', 'localtime'), url FROM urls WHERE datetime((last_visit_time - 11644473600000000) / 1000000, 'unixepoch', 'localtime') > '$LAST_SYNC' ORDER BY last_visit_time;" 2>/dev/null | while IFS='|' read -r timestamp url; do if is_search_url "$url"; then query=$(extract_search_query "$url") if [ -n "$query" ]; then query=$(echo "$query" | sed "s/'/''/g") url=$(echo "$url" | sed "s/'/''/g") hash=$(echo "${timestamp}${browser}${query}" | md5sum | cut -d' ' -f1) sqlcipher "$DB_PATH" "PRAGMA key = '$KEY'; INSERT OR IGNORE INTO search_history (timestamp, query, browser, url, hash) VALUES ('$timestamp', '$query', '$browser', '$url', '$hash');" 2>/dev/null fi fi done rm -f "$temp_db" } # Sync all browsers sync_firefox "librewolf" "$HOME/.librewolf" sync_firefox "firefox" "$HOME/.mozilla/firefox" sync_chromium "brave" "$HOME/.config/BraveSoftware/Brave-Browser/Default/History" sync_chromium "chrome" "$HOME/.config/google-chrome/Default/History" sync_chromium "chromium" "$HOME/.config/chromium/Default/History" # Update last sync time echo "$NOW" > "$LAST_SYNC_FILE"