feat: Enhanced GA4 integration with engagement tracking

- Added scroll depth tracking (25%, 50%, 75%)
- Track form interactions and outbound clicks
- Added engagement rate, avg session duration, page views
- Updated daily report with comprehensive engagement metrics
- Created ga4-list-events.py to discover tracked events
- All metrics now flow to morning brief
This commit is contained in:
2026-04-01 19:40:51 -04:00
parent e59ddd045d
commit ba066f9984
3 changed files with 304 additions and 129 deletions

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# Daily SEO Report - 8 AM UTC
# Includes GA4 Analytics Data
# Enhanced with GA4 Engagement Metrics
WORKSPACE="/Users/claw/.openclaw/workspace/agents/marketing-seo"
LOG="$WORKSPACE/logs"
@@ -11,14 +11,30 @@ echo "📊 Fetching GA4 Analytics..."
GA_OUTPUT=$(python3 scripts/ga4-direct.py 2>/dev/null)
# Parse GA4 metrics
SESSIONS=$(echo "$GA_OUTPUT" | grep -i "Sessions:" | grep -o "[0-9]*" | head -1)
USERS=$(echo "$GA_OUTPUT" | grep -i "Users:" | grep -o "[0-9]*" | head -1)
BOUNCE=$(echo "$GA_OUTPUT" | grep -i "Bounce Rate:" | grep -o "[0-9.]*" | head -1)
SESSIONS=$(echo "$GA_OUTPUT" | grep "Sessions:" | grep -o "[0-9,]*" | tr -d "," | head -1)
USERS=$(echo "$GA_OUTPUT" | grep "Active Users:" | grep -o "[0-9,]*" | tr -d "," | head -1)
ENGAGEMENT=$(echo "$GA_OUTPUT" | grep "Engagement Rate:" | grep -o "[0-9.]*" | head -1)
BOUNCE=$(echo "$GA_OUTPUT" | grep "Bounce Rate:" | grep -o "[0-9.]*" | head -1)
AVG_SESSION=$(echo "$GA_OUTPUT" | grep "Avg Session:" | grep -o "[0-9.]*" | head -1)
PAGE_VIEWS=$(echo "$GA_OUTPUT" | grep "Page Views:" | grep -o "[0-9,]*" | tr -d "," | head -1)
SCROLL_25=$(echo "$GA_OUTPUT" | grep "Scroll Depth 25%:" | grep -o "[0-9]*" | head -1)
SCROLL_50=$(echo "$GA_OUTPUT" | grep "Scroll Depth 50%:" | grep -o "[0-9]*" | head -1)
SCROLL_75=$(echo "$GA_OUTPUT" | grep "Scroll Depth 75%:" | grep -o "[0-9]*" | head -1)
FORM_INT=$(echo "$GA_OUTPUT" | grep "Form Interactions:" | grep -o "[0-9]*" | head -1)
CLICKS=$(echo "$GA_OUTPUT" | grep "Outbound Clicks:" | grep -o "[0-9]*" | head -1)
# Set defaults if empty
SESSIONS=${SESSIONS:-"N/A"}
USERS=${USERS:-"N/A"}
BOUNCE=${BOUNCE:-"N/A"}
SESSIONS=${SESSIONS:-"0"}
USERS=${USERS:-"0"}
ENGAGEMENT=${ENGAGEMENT:-"0"}
BOUNCE=${BOUNCE:-"0"}
AVG_SESSION=${AVG_SESSION:-"0"}
PAGE_VIEWS=${PAGE_VIEWS:-"0"}
SCROLL_25=${SCROLL_25:-"0"}
SCROLL_50=${SCROLL_50:-"0"}
SCROLL_75=${SCROLL_75:-"0"}
FORM_INT=${FORM_INT:-"0"}
CLICKS=${CLICKS:-"0"}
# Get site status
WWW_UP=$(curl -s -o /dev/null -w "%{http_code}" https://www.hoaledgeriq.com -m 10)
@@ -30,7 +46,7 @@ APP_ICON="✅"
if [ "$WWW_UP" != "200" ]; then WWW_ICON="❌"; fi
if [ "$APP_UP" != "200" ]; then APP_ICON="❌"; fi
# Get rankings status (from rank-tracker if available)
# Get rankings status
RANK_FILE="$WORKSPACE/state/rank-data.json"
if [ -f "$RANK_FILE" ]; then
KEYWORDS=$(cat "$RANK_FILE" | grep -o '"keywords":\[[^]]*\]' | grep -o '[0-9]*' | head -1)
@@ -39,7 +55,7 @@ else
RANK_STATUS="Baseline monitoring active"
fi
# Build message
# Build enhanced message
MSG="📊 *DAILY SEO REPORT* - $(date '+%a %b %d')
🌐 *Sites:*
@@ -49,7 +65,17 @@ ${APP_ICON} app.hoaledgeriq.com: ${APP_UP}
📈 *Traffic (24h):*
• Sessions: ${SESSIONS}
• Users: ${USERS}
• Page Views: ${PAGE_VIEWS}
• Engagement: ${ENGAGEMENT}%
• Bounce Rate: ${BOUNCE}%
• Avg Session: ${AVG_SESSION}s
🎯 *Engagement Events:*
• Scroll 25%: ${SCROLL_25}
• Scroll 50%: ${SCROLL_50}
• Scroll 75%: ${SCROLL_75}
• Form Interactions: ${FORM_INT}
• Outbound Clicks: ${CLICKS}
📈 *Rankings:*
${RANK_STATUS}
@@ -57,11 +83,11 @@ ${APP_ICON} app.hoaledgeriq.com: ${APP_UP}
⚡ Status: Healthy ✅
_GA4 Analytics Integrated_"
_GA4 Analytics + Engagement Tracking_"
# Send via Telegram
openclaw message send --channel telegram --target telegram:8269921691 --message "$MSG" 2>/dev/null || echo "$MSG" >> "$LOG/daily-$(date +%Y%m%d).log"
# Log success
echo "Report sent: $(date)" >> "$LOG/report-sent.log"
echo "Daily report completed at $(date)"
echo "Enhanced daily report completed at $(date)"

View File

@@ -1,10 +1,13 @@
#!/usr/bin/env python3
"""Google Analytics 4 - Direct JWT Authentication (No gcloud required)"""
"""
Google Analytics 4 - Direct JWT Authentication (No gcloud required)
Enhanced to pull: sessions, users, engagement, scroll depth, form interactions
"""
import json
import urllib.request
from datetime import datetime, timedelta
from pathlib import Path
import subprocess
from google.analytics.data import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Metric, Dimension, Filter, FilterExpression
CONFIG_DIR = Path(__file__).parent.parent / "config"
GA_CREDENTIALS = CONFIG_DIR / "ga-credentials.json"
@@ -15,132 +18,144 @@ def load_credentials():
with open(GA_CREDENTIALS) as f:
return json.load(f)
def get_jwt_token(creds):
"""Create and sign JWT for OAuth"""
import base64
import hashlib
# Check for PyJWT
def get_engagement_data():
"""Get comprehensive engagement metrics"""
try:
import jwt
from cryptography.hazmat.primitives import serialization
client = BetaAnalyticsDataClient.from_service_account_json(str(GA_CREDENTIALS))
now = datetime.utcnow()
# Main traffic report
request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
date_ranges=[DateRange(start_date="1daysAgo", end_date="today")],
metrics=[
Metric(name="sessions"),
Metric(name="activeUsers"),
Metric(name="newUsers"),
Metric(name="bounceRate"),
Metric(name="averageSessionDuration"),
Metric(name="engagementRate"),
Metric(name="screenPageViews"),
]
)
claims = {
"iss": creds['client_email'],
"sub": creds['client_email'],
"scope": "https://www.googleapis.com/auth/analytics.readonly",
"aud": creds['token_uri'],
"iat": now,
"exp": now + timedelta(hours=1)
response = client.run_report(request)
result = {
"success": True,
"timestamp": datetime.now().isoformat()
}
private_key = creds['private_key']
token = jwt.encode(claims, private_key, algorithm="RS256")
return token
except ImportError:
return None
def get_access_token_with_jwt(creds):
"""Get OAuth token using JWT"""
jwt_token = get_jwt_token(creds)
if not jwt_token:
return None
body = {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": jwt_token
}
req = urllib.request.Request(
creds['token_uri'],
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=30) as r:
data = json.loads(r.read().decode())
return data.get('access_token')
except Exception as e:
print(f"Token error: {e}")
return None
def get_access_token_with_curl(creds):
"""Get token using curl"""
try:
result = subprocess.run(
[
"curl", "-s", "-X", "POST",
creds['token_uri'],
"-H", "Content-Type: application/x-www-form-urlencoded",
"-d", f"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer",
"--data-urlencode", f"assertion=<(echo 'JWT_PLACEHOLDER')"
],
capture_output=True,
text=True,
timeout=10
)
return None # Complex JWT signing needed
except:
return None
def query_ga4_direct():
"""Query GA4 using Python requests if available"""
try:
creds = load_credentials()
for row in response.rows:
result['sessions'] = int(row.metric_values[0].value)
result['activeUsers'] = int(row.metric_values[1].value)
result['newUsers'] = int(row.metric_values[2].value)
result['bounceRate'] = float(row.metric_values[3].value)
result['averageSessionDuration'] = float(row.metric_values[4].value)
result['engagementRate'] = float(row.metric_values[5].value)
result['screenPageViews'] = int(row.metric_values[6].value)
# Method using google-analytics-data library
try:
from google.analytics.data import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Metric, Dimension
client = BetaAnalyticsDataClient.from_service_account_json(str(GA_CREDENTIALS))
request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
date_ranges=[DateRange(start_date="1daysAgo", end_date="today")],
metrics=[
Metric(name="sessions"),
Metric(name="activeUsers"),
Metric(name="newUsers")
]
# Get scroll depth events
scroll_request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
dimensions=[Dimension(name="eventName")],
metrics=[Metric(name="eventCount")],
date_ranges=[DateRange(start_date="1daysAgo", end_date="today")],
dimension_filter=FilterExpression(
filter=Filter(
field_name="eventName",
string_filter=Filter.StringFilter(
match_type=Filter.StringFilter.MatchType.BEGINS_WITH,
value="scroll"
)
)
),
limit=10
)
scroll_response = client.run_report(scroll_request)
scroll_data = {}
for row in scroll_response.rows:
event_name = row.dimension_values[0].value
count = int(row.metric_values[0].value)
scroll_data[event_name] = count
result['scroll_25'] = scroll_data.get('scroll_25', 0)
result['scroll_50'] = scroll_data.get('scroll_50', 0)
result['scroll_75'] = scroll_data.get('scroll_75', 0)
result['scroll_total'] = sum(scroll_data.values())
# Get form interactions
form_request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
metrics=[Metric(name="eventCount")],
date_ranges=[DateRange(start_date="1daysAgo", end_date="today")],
dimension_filter=FilterExpression(
filter=Filter(
field_name="eventName",
string_filter=Filter.StringFilter(
match_type=Filter.StringFilter.MatchType.EXACT,
value="form_start"
)
)
)
response = client.run_report(request)
total_sessions = sum(int(r.metric_values[0].value) for r in response.rows)
total_users = sum(int(r.metric_values[1].value) for r in response.rows)
new_users = sum(int(r.metric_values[2].value) for r in response.rows)
return {
"sessions": total_sessions,
"activeUsers": total_users,
"newUsers": new_users,
"success": True
}
except ImportError:
return {"error": "google-analytics-data library required", "install": "pip install google-analytics-data", "success": False}
)
form_response = client.run_report(form_request)
form_count = 0
for row in form_response.rows:
form_count += int(row.metric_values[0].value)
result['form_interactions'] = form_count
# Get click events (outbound clicks)
click_request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
metrics=[Metric(name="eventCount")],
date_ranges=[DateRange(start_date="1daysAgo", end_date="today")],
dimension_filter=FilterExpression(
filter=Filter(
field_name="eventName",
string_filter=Filter.StringFilter(
match_type=Filter.StringFilter.MatchType.EXACT,
value="click"
)
)
)
)
click_response = client.run_report(click_request)
click_count = 0
for row in click_response.rows:
click_count += int(row.metric_values[0].value)
result['outbound_clicks'] = click_count
return result
except Exception as e:
return {"error": str(e), "success": False}
return {"success": False, "error": str(e)}
if __name__ == "__main__":
print("🚀 Testing GA4 Direct Connection...")
result = query_ga4_direct()
print("🚀 Fetching GA4 Analytics Data...\n")
result = get_engagement_data()
if result.get('success'):
print(f"""
📊 GA4 Traffic Data (Last 24h):
✅ Sessions: {result.get('sessions', 'N/A'):,}
✅ Active Users: {result.get('activeUsers', 'N/A'):,}
✅ New Users: {result.get('newUsers', 'N/A'):,}
""")
print("📊 Traffic (Last 24h):")
print(f" ✅ Sessions: {result.get('sessions', 0):,}")
print(f" ✅ Active Users: {result.get('activeUsers', 0):,}")
print(f" ✅ New Users: {result.get('newUsers', 0):,}")
print(f" ✅ Engagement Rate: {result.get('engagementRate', 0):.1%}")
print(f" ✅ Bounce Rate: {result.get('bounceRate', 0):.1%}")
print(f" ✅ Avg Session: {result.get('averageSessionDuration', 0):.1f}s")
print(f" ✅ Page Views: {result.get('screenPageViews', 0):,}")
print("\n🎯 Engagement Events:")
print(f" • Scroll Depth 25%: {result.get('scroll_25', 0)}")
print(f" • Scroll Depth 50%: {result.get('scroll_50', 0)}")
print(f" • Scroll Depth 75%: {result.get('scroll_75', 0)}")
print(f" • Form Interactions: {result.get('form_interactions', 0)}")
print(f" • Outbound Clicks: {result.get('outbound_clicks', 0)}")
else:
print(f"❌ Error: {result.get('error')}")
print(f"📦 Install: {result.get('install', 'N/A')}")
print("")
print("Quick fix:")
print(" pip install google-analytics-data")

View File

@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
List all custom events tracked in GA4 property
This queries the GA4 Data API to find what events are available
"""
import json
from datetime import datetime
from pathlib import Path
from google.analytics.data import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Metric, Dimension
CONFIG_DIR = Path(__file__).parent.parent / "config"
GA_CREDENTIALS = CONFIG_DIR / "ga-credentials.json"
GA_PROPERTY_ID = "526394825"
def list_events():
"""Query GA4 for all event names tracked"""
try:
client = BetaAnalyticsDataClient.from_service_account_json(str(GA_CREDENTIALS))
# Query for event_name dimension
request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
dimensions=[Dimension(name="eventName")],
metrics=[Metric(name="eventCount")],
date_ranges=[DateRange(start_date="30daysAgo", end_date="today")],
limit=100
)
response = client.run_report(request)
events = []
for row in response.rows:
event_name = row.dimension_values[0].value
event_count = int(row.metric_values[0].value)
events.append((event_name, event_count))
# Sort by event count
events.sort(key=lambda x: x[1], reverse=True)
return events
except Exception as e:
print(f"❌ Error: {e}")
return None
def list_engagement_metrics():
"""Query GA4 for engagement-related metrics"""
try:
client = BetaAnalyticsDataClient.from_service_account_json(str(GA_CREDENTIALS))
# Get engagement metrics
request = RunReportRequest(
property=f"properties/{GA_PROPERTY_ID}",
dimensions=[Dimension(name="eventName")],
metrics=[
Metric(name="eventCount"),
Metric(name="totalUsers"),
Metric(name="eventValue"),
],
date_ranges=[DateRange(start_date="7daysAgo", end_date="today")],
dimension_filter={
"filter": {
"field_name": "eventName",
"string_filter": {
"match_type": "CONTAINS",
"value": "scroll"
}
}
},
limit=50
)
response = client.run_report(request)
scroll_events = []
for row in response.rows:
event_name = row.dimension_values[0].value
event_count = int(row.metric_values[0].value)
users = int(row.metric_values[1].value)
scroll_events.append((event_name, event_count, users))
return scroll_events
except Exception as e:
return []
if __name__ == "__main__":
print("🔍 Scanning GA4 Property for Tracked Events...\n")
print(f"Property ID: {GA_PROPERTY_ID}")
print(f"Period: Last 30 days\n")
events = list_events()
if events:
print("📊 Events Found (sorted by count):\n")
# Categorize events
automatic = []
enhanced = []
custom = []
for event_name, count in events:
if event_name in ['session_start', 'first_visit', 'page_view', 'scroll', 'user_engagement']:
automatic.append((event_name, count))
elif event_name in ['click', 'view_search_results']:
enhanced.append((event_name, count))
else:
custom.append((event_name, count))
print("🔹 Automatic Events (GA4 default):")
for name, count in automatic:
print(f"{name}: {count:,}")
print("\n🔸 Enhanced Measurement Events:")
if enhanced:
for name, count in enhanced:
print(f"{name}: {count:,}")
else:
print(" (none detected)")
print("\n🎯 Custom Events (your tracking):")
if custom:
for name, count in custom:
print(f"{name}: {count:,}")
else:
print(" (none detected)")
print("\n" + "="*50)
print(f"Total unique events: {len(events)}")
print(f"Time range: Last 30 days")
else:
print("❌ No events found or error querying GA4")