- Created chatwoot-agent-bot/ with Node.js webhook server - Bot detects intent (greeting, billing, technical, features, account) - Auto-responds from FAQ knowledge base or escalates to human - FAQ-KB.md: Living knowledge base that grows with customer questions - CHATWOOT-SETUP.md: Complete deployment and configuration guide - Supports Telegram notifications on escalation - Bot runs on port 3001, ready for Chatwoot webhook integration
199 lines
7.2 KiB
Bash
Executable File
199 lines
7.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Sales Lead CRM Integration Script - v3 (Fixed deduplication)
|
|
# Polls website leads endpoint and pushes to Twenty CRM
|
|
|
|
set -e
|
|
|
|
WORKSPACE="/Users/claw/.openclaw/workspace/agents/sales-lead"
|
|
STATE_FILE="$WORKSPACE/state.json"
|
|
LOG_FILE="$WORKSPACE/integration.log"
|
|
NOTIFY_FILE="$WORKSPACE/notifications.log"
|
|
ADMIN_KEY="K9mP2vL8x4qR7nZ"
|
|
TWENTY_BASE="https://salesforce.hoaledgeriq.com/rest"
|
|
TWENTY_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5M2FmNGFmNS0zZWQ0LTQ1ZDMtOWE5Zi01MDMzZjc3YTY3MjMiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiOTNhZjRhZjUtM2VkNC00NWQzLTlhOWYtNTAzM2Y3N2E2NzIzIiwiaWF0IjoxNzczMzI4NDQzLCJleHAiOjE4MDQ3ODE2NDIsImp0aSI6IjIwZjEyYzkwLTRkMDctNGJmNi1iMzk3LTZjNmU3MzlmMThjOCJ9.zeM5NvwCSGEcz99m2LYtgb0sVD6WUXcCF7SwonFg930"
|
|
|
|
echo "[$(date)] Running CRM integration check" >> "$LOG_FILE"
|
|
mkdir -p "$WORKSPACE"
|
|
|
|
# Initialize state if not exists
|
|
if [[ ! -f "$STATE_FILE" ]]; then
|
|
echo '{"processed_leads": [], "processed_calc_ids": [], "crm_pushed": {"calc_ids": [], "lead_ids": []}, "last_check": null, "status": "active"}' > "$STATE_FILE"
|
|
fi
|
|
|
|
# Read current state
|
|
STATE=$(cat "$STATE_FILE")
|
|
PROCESSED_LEADS=$(echo "$STATE" | jq -r '.processed_leads // []')
|
|
|
|
echo "=== Checking endpoints at $(date) ===" >> "$LOG_FILE"
|
|
echo "Already processed leads: $(echo "$PROCESSED_LEADS" | jq -r '. | join(", ")')" >> "$LOG_FILE"
|
|
|
|
# Fetch leads from API
|
|
RESPONSE=$(curl -s -H "x-admin-key: $ADMIN_KEY" "https://www.hoaledgeriq.com/api/leads" 2>&1 || echo "CONNECTION_FAILED")
|
|
|
|
if [[ "$RESPONSE" == "CONNECTION_FAILED" ]]; then
|
|
echo "⚠️ hoaledgeriq.com/api/leads not accessible" >> "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
|
|
echo "⚠️ Invalid response from hoaledgeriq.com" >> "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ hoaledgeriq.com/api/leads responding" >> "$LOG_FILE"
|
|
|
|
# Save leads to temp file for processing
|
|
LEADS_TEMP=$(mktemp)
|
|
echo "$RESPONSE" | jq -r '.leads[]? | @base64' 2>/dev/null > "$LEADS_TEMP"
|
|
|
|
# Process each lead
|
|
NEW_PROCESSED="$PROCESSED_LEADS"
|
|
FOUND_COUNT=0
|
|
|
|
while IFS= read -r encoded; do
|
|
[[ -z "$encoded" ]] && continue
|
|
|
|
item=$(echo "$encoded" | base64 -d)
|
|
id=$(echo "$item" | jq -r '.id // empty')
|
|
email=$(echo "$item" | jq -r '.email // empty')
|
|
|
|
[[ -z "$id" || -z "$email" ]] && continue
|
|
|
|
# Check if already processed
|
|
if echo "$NEW_PROCESSED" | jq -e --arg id "$id" 'index($id)' >/dev/null 2>&1; then
|
|
echo "Skipping lead $id (already processed)" >> "$LOG_FILE"
|
|
continue
|
|
fi
|
|
|
|
echo "🎉 NEW LEAD: id=$id, email=$email" | tee -a "$LOG_FILE" "$NOTIFY_FILE"
|
|
|
|
# Send Telegram notification via OpenClaw
|
|
TG_MSG="🏠 NEW LEAD ALERT
|
|
|
|
Name: $display_name
|
|
Email: $email
|
|
Organization: $org_name
|
|
State: $state_loc
|
|
Role: $role
|
|
Units: $unit_count
|
|
Beta: $beta_text
|
|
|
|
Check CRM: salesforce.hoaledgeriq.com"
|
|
|
|
openclaw message send --text "$TG_MSG" 2>/dev/null || echo "Notification queued"
|
|
FOUND_COUNT=$((FOUND_COUNT + 1))
|
|
|
|
# Extract all fields
|
|
first_name=$(echo "$item" | jq -r '.first_name // ""')
|
|
last_name=$(echo "$item" | jq -r '.last_name // ""')
|
|
org_name=$(echo "$item" | jq -r '.org_name // "N/A"')
|
|
state_loc=$(echo "$item" | jq -r '.state // "N/A"')
|
|
role=$(echo "$item" | jq -r '.role // "N/A"')
|
|
unit_count=$(echo "$item" | jq -r '.unit_count // "N/A"')
|
|
beta_interest=$(echo "$item" | jq -r '.beta_interest // 0')
|
|
source=$(echo "$item" | jq -r '.source // "unknown"')
|
|
created_at=$(echo "$item" | jq -r '.created_at // "N/A"')
|
|
|
|
display_name="${first_name} ${last_name}"
|
|
[[ -z "$first_name" && -z "$last_name" ]] && display_name="Unknown Lead"
|
|
|
|
# Create Person
|
|
person_json="{\"name\":{\"firstName\":\"$first_name\",\"lastName\":\"$last_name\"},\"emails\":{\"primaryEmail\":\"$email\",\"additionalEmails\":[]}}"
|
|
|
|
person_res=$(curl -s -X POST "$TWENTY_BASE/people" \
|
|
-H "Authorization: Bearer $TWENTY_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$person_json" 2>/dev/null)
|
|
|
|
person_id=$(echo "$person_res" | jq -r '.data.createPerson.id // empty')
|
|
|
|
if [[ -n "$person_id" ]]; then
|
|
echo "✓ Created Person: $person_id ($display_name)" >> "$LOG_FILE"
|
|
else
|
|
# Person exists - find by email
|
|
all_people=$(curl -s "$TWENTY_BASE/people" -H "Authorization: Bearer $TWENTY_TOKEN")
|
|
person_id=$(echo "$all_people" | jq -r --arg em "$email" '.data.people[] | select(.emails.primaryEmail == $em) | .id' | head -1)
|
|
[[ -n "$person_id" ]] && echo "✓ Found Person: $person_id ($display_name)" >> "$LOG_FILE"
|
|
fi
|
|
|
|
# Create Note
|
|
if [[ -n "$person_id" ]]; then
|
|
beta_text="No"
|
|
[[ "$beta_interest" == "1" ]] && beta_text="Yes"
|
|
|
|
# Build note body
|
|
note_text="## 🌐 Website Lead Submission
|
|
|
|
**Name:** $display_name
|
|
**Email:** $email
|
|
**Organization:** $org_name
|
|
**State:** $state_loc
|
|
**Role/Position:** $role
|
|
**Unit Count:** $unit_count
|
|
**Beta Interest:** $beta_text
|
|
**Source:** $source
|
|
**Submitted:** $created_at
|
|
|
|
---
|
|
*Submitted via website lead form. Review for opportunity qualification.*"
|
|
|
|
# Escape for JSON
|
|
escaped_body=$(echo "$note_text" | jq -Rs .)
|
|
|
|
note_json="{\"title\":\"🌐 Lead: $display_name | $org_name\",\"bodyV2\":{\"markdown\":"$escaped_body"}}"
|
|
|
|
note_res=$(curl -s -X POST "$TWENTY_BASE/notes" \
|
|
-H "Authorization: Bearer $TWENTY_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$note_json" 2>/dev/null)
|
|
|
|
note_id=$(echo "$note_res" | jq -r '.data.createOneNote.id // empty')
|
|
|
|
if [[ -n "$note_id" ]]; then
|
|
echo "✓ Created Note: $note_id ($org_name, $role)" >> "$LOG_FILE"
|
|
else
|
|
echo "⚠️ Note creation response: $note_res" >> "$LOG_FILE"
|
|
fi
|
|
fi
|
|
|
|
# Add to processed list (update in memory and immediately save)
|
|
NEW_PROCESSED=$(echo "$NEW_PROCESSED" | jq --arg id "$id" '. + [$id]')
|
|
echo "$STATE" | jq --argjson processed "$NEW_PROCESSED" '.processed_leads = $processed' > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
|
|
done < "$LEADS_TEMP"
|
|
|
|
# Clean up temp file
|
|
rm -f "$LEADS_TEMP"
|
|
|
|
# Update last check timestamp
|
|
STATE=$(cat "$STATE_FILE")
|
|
echo "$STATE" | jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '.last_check = $ts' > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
|
|
|
if [[ $FOUND_COUNT -eq 0 ]]; then
|
|
echo "No new leads found" >> "$LOG_FILE"
|
|
else
|
|
echo "Processed $FOUND_COUNT new leads" >> "$LOG_FILE"
|
|
fi
|
|
|
|
echo "=== Completed at $(date) ===" >> "$LOG_FILE"
|
|
echo "" >> "$LOG_FILE"
|
|
|
|
# Telegram notification function
|
|
send_telegram_notification() {
|
|
local name="$1"
|
|
local email="$2"
|
|
local org="$3"
|
|
local state="$4"
|
|
local role="$5"
|
|
local units="$6"
|
|
local beta="$7"
|
|
|
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"chat_id\": \"${TELEGRAM_CHAT_ID}\",
|
|
\"text\": \"🏠 NEW LEAD ALERT\\n\\nName: $name\\nEmail: $email\\nOrganization: $org\\nState: $state\\nRole: $role\\nUnits: $units\\nBeta: $beta\\n\\nCheck CRM for details\",
|
|
\"parse_mode\": \"Markdown\"
|
|
}" 2>/dev/null || echo "Telegram notification sent"
|
|
}
|