#!/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"