{
  "schemaVersion": "1.0",
  "item": {
    "slug": "social-listening-monitor",
    "name": "Social Listening & Brand Reputation Monitor",
    "source": "tencent",
    "type": "skill",
    "category": "通讯协作",
    "sourceUrl": "https://clawhub.ai/g4dr/social-listening-monitor",
    "canonicalUrl": "https://clawhub.ai/g4dr/social-listening-monitor",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/social-listening-monitor",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=social-listening-monitor",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/social-listening-monitor"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/social-listening-monitor",
    "agentPageUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent",
    "manifestUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Overview",
        "body": "This skill builds a real-time brand reputation monitoring system that:\n\nApify scrapes Twitter/X, Reddit, forums, and news sites for every mention of your brand\nClaude (OpenClaw) analyzes sentiment, detects crises, and classifies each mention\nAlerts fire instantly to Slack, Telegram, or email when reputation risk is detected\n\nThe result: you know what people are saying about your brand the moment they say it —\nand you can respond before it becomes a crisis.\n\n🔗 Apify: https://www.apify.com/?fpr=dx06p"
      },
      {
        "title": "What This Skill Does",
        "body": "Monitor Twitter/X, Reddit, forums, and news for brand mentions in real-time\nPerform sentiment analysis on every mention (positive / negative / neutral)\nDetect crisis signals — sudden spikes in negative mentions\nTrack competitor mentions for comparative reputation benchmarking\nScore reputation health over time with a rolling dashboard score\nAlert immediately on Slack/Telegram when a crisis threshold is crossed\nGenerate weekly reputation reports with trends and actionable insights\nDistinguish genuine complaints from spam or bot activity"
      },
      {
        "title": "Architecture Overview",
        "body": "┌──────────────────────────────────────────────────────────────────┐\n│           SOCIAL LISTENING & REPUTATION MONITOR                  │\n│                                                                  │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │  LAYER 1 — MENTION SCRAPING (Apify)                      │   │\n│  │  Twitter/X │ Reddit │ Hacker News │ Google News           │   │\n│  │  Trustpilot │ G2 │ App Store │ Niche Forums               │   │\n│  └───────────────────────────┬──────────────────────────────┘   │\n│                              │                                   │\n│  ┌───────────────────────────▼──────────────────────────────┐   │\n│  │  LAYER 2 — REPUTATION ANALYSIS ENGINE (Claude)           │   │\n│  │                                                          │   │\n│  │  • Sentiment Classifier   → pos / neg / neutral + score  │   │\n│  │  • Crisis Detector        → spike in neg mentions        │   │\n│  │  • Topic Categorizer      → product | support | pr | etc │   │\n│  │  • Influence Scorer       → who is talking (reach)       │   │\n│  │  • Response Generator     → suggested reply drafts       │   │\n│  └───────────────────────────┬──────────────────────────────┘   │\n│                              │                                   │\n│  ┌───────────────────────────▼──────────────────────────────┐   │\n│  │  LAYER 3 — ALERTS & REPORTING                            │   │\n│  │  Slack │ Telegram │ Email │ Dashboard │ Weekly Report     │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└──────────────────────────────────────────────────────────────────┘"
      },
      {
        "title": "Apify",
        "body": "Sign up at https://www.apify.com/?fpr=dx06p\nGo to Settings → Integrations\nCopy your token:\nexport APIFY_TOKEN=apify_api_xxxxxxxxxxxxxxxx"
      },
      {
        "title": "Claude / OpenClaw",
        "body": "export CLAUDE_API_KEY=sk-ant-xxxxxxxxxxxxxxxx"
      },
      {
        "title": "Slack Webhook (optional)",
        "body": "Go to api.slack.com/apps → Create App → Incoming Webhooks\nCopy the webhook URL:\nexport SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx"
      },
      {
        "title": "Telegram Bot (optional)",
        "body": "export TELEGRAM_BOT_TOKEN=123456789:AABBccDDeeFFggHH\nexport TELEGRAM_CHAT_ID=-1001234567890"
      },
      {
        "title": "Step 2 — Install Dependencies",
        "body": "npm install apify-client axios node-cron dotenv"
      },
      {
        "title": "Configuration — Define Your Brand",
        "body": "// config.js\nexport const BRAND_CONFIG = {\n  brandName: \"YourBrand\",\n  keywords: [\n    \"YourBrand\",\n    \"YourBrand.com\",\n    \"@YourBrandHandle\",\n    \"#YourBrand\",\n    \"your brand common misspelling\"\n  ],\n  competitors: [\"CompetitorA\", \"CompetitorB\"],\n  crisisThreshold: {\n    negativeSpike: 5,       // alert if 5+ negative mentions in one scan\n    sentimentDrop: 20,      // alert if sentiment score drops 20 points\n    viralThreshold: 1000    // alert if a negative post hits 1000+ engagements\n  },\n  language: \"en\",\n  timezone: \"America/New_York\"\n};"
      },
      {
        "title": "Scrape Twitter/X Mentions",
        "body": "import ApifyClient from 'apify-client';\nimport { BRAND_CONFIG } from './config.js';\n\nconst apify = new ApifyClient({ token: process.env.APIFY_TOKEN });\n\nasync function scrapeTwitterMentions() {\n  console.log(\"🐦 Scraping Twitter/X mentions...\");\n\n  const run = await apify.actor(\"apify/twitter-scraper\").call({\n    searchTerms: BRAND_CONFIG.keywords,\n    maxTweets: 100,\n    addUserInfo: true,\n    startUrls: [],\n    languageFilter: BRAND_CONFIG.language\n  });\n\n  const { items } = await run.dataset().getData();\n\n  return items.map(t => ({\n    source:      \"twitter\",\n    id:          t.id,\n    text:        t.fullText || t.text,\n    author:      t.author?.userName,\n    authorName:  t.author?.name,\n    followers:   t.author?.followers || 0,\n    verified:    t.author?.isVerified || false,\n    likes:       t.likeCount || 0,\n    retweets:    t.retweetCount || 0,\n    replies:     t.replyCount || 0,\n    engagements: (t.likeCount || 0) + (t.retweetCount || 0) * 2 + (t.replyCount || 0),\n    url:         t.url,\n    createdAt:   t.createdAt,\n    scrapedAt:   new Date().toISOString()\n  }));\n}"
      },
      {
        "title": "Scrape Reddit Mentions",
        "body": "async function scrapeRedditMentions() {\n  console.log(\"👽 Scraping Reddit mentions...\");\n\n  const searchQueries = BRAND_CONFIG.keywords.map(k =>\n    apify.actor(\"apify/reddit-search-scraper\").call({\n      queries: [k],\n      maxItems: 30,\n      sort: \"new\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items)\n  );\n\n  const results = await Promise.all(searchQueries);\n\n  return results.flat().map(p => ({\n    source:      \"reddit\",\n    id:          p.id,\n    text:        p.title + \" \" + (p.selftext || \"\"),\n    title:       p.title,\n    author:      p.author,\n    subreddit:   p.subreddit,\n    score:       p.score,\n    comments:    p.numComments,\n    upvoteRatio: p.upvoteRatio,\n    engagements: p.score + p.numComments * 2,\n    url:         p.url,\n    createdAt:   new Date(p.created * 1000).toISOString(),\n    scrapedAt:   new Date().toISOString()\n  }));\n}"
      },
      {
        "title": "Scrape News & Review Platforms",
        "body": "async function scrapeNewsAndReviews() {\n  console.log(\"📰 Scraping news and reviews...\");\n\n  const brandQuery = BRAND_CONFIG.brandName;\n\n  const [news, trustpilot, hackerNews] = await Promise.all([\n\n    // Google News\n    apify.actor(\"apify/google-search-scraper\").call({\n      queries: [`\"${brandQuery}\" news`],\n      maxPagesPerQuery: 2,\n      resultsPerPage: 20,\n      dateRange: \"pastWeek\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"google_news\",\n        text:      r.title + \" \" + r.snippet,\n        title:     r.title,\n        url:       r.url,\n        createdAt: r.date || new Date().toISOString(),\n        scrapedAt: new Date().toISOString()\n      }))),\n\n    // Trustpilot reviews\n    apify.actor(\"apify/trustpilot-scraper\").call({\n      startUrls: [{ url: `https://www.trustpilot.com/review/${brandQuery.toLowerCase()}.com` }],\n      maxReviews: 50,\n      filterScore: [1, 2, 3]   // focus on negative/neutral\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"trustpilot\",\n        text:      r.reviewBody,\n        title:     r.reviewTitle,\n        rating:    r.ratingValue,\n        author:    r.author,\n        url:       r.url,\n        createdAt: r.datePublished,\n        scrapedAt: new Date().toISOString()\n      }))).catch(() => []),  // graceful fail if brand not on Trustpilot\n\n    // Hacker News\n    apify.actor(\"apify/hacker-news-scraper\").call({\n      searchQuery: brandQuery,\n      maxItems: 20,\n      type: \"story\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"hacker_news\",\n        text:      r.title + \" \" + (r.text || \"\"),\n        title:     r.title,\n        author:    r.by,\n        score:     r.score,\n        comments:  r.descendants,\n        url:       r.url || `https://news.ycombinator.com/item?id=${r.id}`,\n        createdAt: new Date(r.time * 1000).toISOString(),\n        scrapedAt: new Date().toISOString()\n      }))).catch(() => [])\n\n  ]);\n\n  return [...news, ...trustpilot, ...hackerNews];\n}"
      },
      {
        "title": "Aggregate All Mentions",
        "body": "async function scrapeAllMentions() {\n  const [twitter, reddit, newsReviews] = await Promise.all([\n    scrapeTwitterMentions(),\n    scrapeRedditMentions(),\n    scrapeNewsAndReviews()\n  ]);\n\n  const all = [...twitter, ...reddit, ...newsReviews];\n\n  // Deduplicate by URL\n  const seen = new Set();\n  return all.filter(m => {\n    if (seen.has(m.url)) return false;\n    seen.add(m.url);\n    return true;\n  });\n}"
      },
      {
        "title": "Sentiment Classifier",
        "body": "import axios from 'axios';\n\nconst claude = axios.create({\n  baseURL: 'https://api.anthropic.com/v1',\n  headers: {\n    'x-api-key': process.env.CLAUDE_API_KEY,\n    'anthropic-version': '2023-06-01',\n    'Content-Type': 'application/json'\n  }\n});\n\nasync function analyzeSentiment(mentions) {\n  const prompt = `\nYou are a brand reputation analyst. Analyze each mention and classify it.\n\nBRAND: ${BRAND_CONFIG.brandName}\n\nMENTIONS TO ANALYZE:\n${JSON.stringify(mentions.slice(0, 30), null, 2)}\n\nRespond ONLY in this JSON format:\n{\n  \"analyzedMentions\": [\n    {\n      \"id\": \"mention id or url\",\n      \"sentiment\": \"positive | negative | neutral | mixed\",\n      \"sentimentScore\": 7,\n      \"confidenceLevel\": \"high | medium | low\",\n      \"emotionalTone\": \"angry | frustrated | disappointed | happy | excited | neutral | sarcastic\",\n      \"category\": \"product_feedback | customer_support | pr_crisis | competitor_comparison | spam | praise | question | bug_report\",\n      \"urgency\": \"critical | high | medium | low\",\n      \"isInfluencer\": true,\n      \"requiresResponse\": true,\n      \"suggestedResponseTone\": \"apologetic | informative | appreciative | ignore\",\n      \"keyTopics\": [\"topic1\", \"topic2\"],\n      \"isCrisisSignal\": false,\n      \"summary\": \"one-line summary of what was said\"\n    }\n  ],\n  \"batchSentiment\": {\n    \"positive\": 0,\n    \"negative\": 0,\n    \"neutral\": 0,\n    \"mixed\": 0,\n    \"overallScore\": 65,\n    \"trend\": \"improving | declining | stable\"\n  },\n  \"crisisSignals\": [\n    {\n      \"signal\": \"description of the risk\",\n      \"severity\": \"critical | high | medium\",\n      \"source\": \"platform\",\n      \"url\": \"url of the post\",\n      \"recommendedAction\": \"what to do right now\"\n    }\n  ],\n  \"topComplaintsThisRound\": [\"complaint 1\", \"complaint 2\"],\n  \"topPraisesThisRound\": [\"praise 1\", \"praise 2\"]\n}\n`;\n\n  const { data } = await claude.post('/messages', {\n    model: \"claude-opus-4-5\",\n    max_tokens: 4000,\n    messages: [{ role: \"user\", content: prompt }]\n  });\n\n  return JSON.parse(data.content[0].text.replace(/```json|```/g, '').trim());\n}"
      },
      {
        "title": "Crisis Detector",
        "body": "// Rolling sentiment history (use Redis/DB in production)\nconst sentimentHistory = [];\n\nfunction detectCrisis(analysis) {\n  const crisisAlerts = [];\n  const batch = analysis.batchSentiment;\n  const signals = analysis.crisisSignals || [];\n\n  // Track history\n  sentimentHistory.push({\n    score: batch.overallScore,\n    negative: batch.negative,\n    timestamp: new Date().toISOString()\n  });\n\n  const prev = sentimentHistory[sentimentHistory.length - 2];\n\n  // CRISIS TRIGGER 1 — Spike in negative mentions\n  if (batch.negative >= BRAND_CONFIG.crisisThreshold.negativeSpike) {\n    crisisAlerts.push({\n      type: \"negative_spike\",\n      severity: \"critical\",\n      message: `🚨 ${batch.negative} negative mentions detected in this scan`,\n      threshold: BRAND_CONFIG.crisisThreshold.negativeSpike,\n      current: batch.negative\n    });\n  }\n\n  // CRISIS TRIGGER 2 — Sentiment score drop\n  if (prev && (prev.score - batch.overallScore) >= BRAND_CONFIG.crisisThreshold.sentimentDrop) {\n    crisisAlerts.push({\n      type: \"sentiment_drop\",\n      severity: \"high\",\n      message: `📉 Sentiment dropped from ${prev.score} to ${batch.overallScore} (-${prev.score - batch.overallScore} pts)`,\n      previousScore: prev.score,\n      currentScore: batch.overallScore\n    });\n  }\n\n  // CRISIS TRIGGER 3 — High-engagement negative post\n  const viralNegative = analysis.analyzedMentions?.filter(m =>\n    m.sentiment === \"negative\" &&\n    m.urgency === \"critical\"\n  ) || [];\n\n  if (viralNegative.length > 0) {\n    crisisAlerts.push({\n      type: \"viral_negative\",\n      severity: \"high\",\n      message: `🔥 ${viralNegative.length} high-urgency negative mention(s) detected`,\n      mentions: viralNegative.map(m => m.id)\n    });\n  }\n\n  // Add explicit crisis signals from Claude\n  signals.forEach(signal => {\n    if (signal.severity === \"critical\" || signal.severity === \"high\") {\n      crisisAlerts.push({ ...signal, type: \"claude_signal\" });\n    }\n  });\n\n  return crisisAlerts;\n}"
      },
      {
        "title": "Response Suggestion Generator",
        "body": "async function generateResponseSuggestions(urgentMentions) {\n  if (urgentMentions.length === 0) return [];\n\n  const prompt = `\nYou are a brand communications expert. Write response suggestions for these urgent mentions.\nBe empathetic, on-brand, and action-oriented. Never defensive.\n\nBRAND: ${BRAND_CONFIG.brandName}\n\nURGENT MENTIONS REQUIRING RESPONSE:\n${JSON.stringify(urgentMentions.slice(0, 5), null, 2)}\n\nRespond ONLY in this JSON format:\n{\n  \"suggestions\": [\n    {\n      \"mentionId\": \"id or url\",\n      \"platform\": \"twitter | reddit | etc\",\n      \"originalText\": \"what they said (summarized)\",\n      \"sentiment\": \"negative | mixed\",\n      \"responseOptions\": [\n        {\n          \"tone\": \"apologetic\",\n          \"response\": \"full suggested response text\",\n          \"bestFor\": \"when the issue is your fault\"\n        },\n        {\n          \"tone\": \"informative\",\n          \"response\": \"full suggested response text\",\n          \"bestFor\": \"when it is a misunderstanding\"\n        }\n      ],\n      \"doNotDo\": \"what to avoid saying in this specific case\",\n      \"priority\": \"respond within 1h | 4h | 24h\"\n    }\n  ]\n}\n`;\n\n  const { data } = await claude.post('/messages', {\n    model: \"claude-opus-4-5\",\n    max_tokens: 2500,\n    messages: [{ role: \"user\", content: prompt }]\n  });\n\n  return JSON.parse(data.content[0].text.replace(/```json|```/g, '').trim());\n}"
      },
      {
        "title": "Slack Alert Publisher",
        "body": "async function sendSlackAlert(crisisAlerts, analysis, responses) {\n  const isCrisis = crisisAlerts.some(a => a.severity === \"critical\");\n  const color = isCrisis ? \"#FF0000\" : \"#FFA500\";\n  const icon = isCrisis ? \"🚨\" : \"⚠️\";\n\n  const payload = {\n    attachments: [{\n      color,\n      blocks: [\n        {\n          type: \"header\",\n          text: { type: \"plain_text\", text: `${icon} Brand Alert: ${BRAND_CONFIG.brandName}` }\n        },\n        {\n          type: \"section\",\n          fields: [\n            { type: \"mrkdwn\", text: `*Sentiment Score:*\\n${analysis.batchSentiment.overallScore}/100` },\n            { type: \"mrkdwn\", text: `*Trend:*\\n${analysis.batchSentiment.trend}` },\n            { type: \"mrkdwn\", text: `*Negative Mentions:*\\n${analysis.batchSentiment.negative}` },\n            { type: \"mrkdwn\", text: `*Requires Response:*\\n${responses?.suggestions?.length || 0} mentions` }\n          ]\n        },\n        ...crisisAlerts.map(alert => ({\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `*${alert.severity?.toUpperCase()}:* ${alert.message}\\n${alert.recommendedAction || \"\"}`\n          }\n        })),\n        {\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `*Top Complaints:*\\n${analysis.topComplaintsThisRound?.map(c => `• ${c}`).join('\\n') || \"None\"}`\n          }\n        }\n      ]\n    }]\n  };\n\n  await axios.post(process.env.SLACK_WEBHOOK_URL, payload);\n}"
      },
      {
        "title": "Telegram Crisis Alert",
        "body": "async function sendTelegramAlert(crisisAlerts, analysis) {\n  const severity = crisisAlerts[0]?.severity || \"medium\";\n  const icon = severity === \"critical\" ? \"🚨🚨🚨\" : \"⚠️\";\n\n  const message = `\n${icon} *BRAND ALERT: ${BRAND_CONFIG.brandName}*\n\n📊 *Reputation Score:* ${analysis.batchSentiment.overallScore}/100 (${analysis.batchSentiment.trend})\n😡 *Negative:* ${analysis.batchSentiment.negative} | 😊 *Positive:* ${analysis.batchSentiment.positive}\n\n*🔴 Crisis Signals:*\n${crisisAlerts.map(a => `• [${a.severity?.toUpperCase()}] ${a.message}`).join('\\n')}\n\n*📢 Top Complaints:*\n${analysis.topComplaintsThisRound?.slice(0, 3).map(c => `• ${c}`).join('\\n') || \"• None\"}\n\n*✅ Top Praises:*\n${analysis.topPraisesThisRound?.slice(0, 2).map(p => `• ${p}`).join('\\n') || \"• None\"}\n\n⏰ ${new Date().toLocaleString()}\n`.trim();\n\n  await axios.post(\n    `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`,\n    {\n      chat_id: process.env.TELEGRAM_CHAT_ID,\n      text: message,\n      parse_mode: \"Markdown\"\n    }\n  );\n}"
      },
      {
        "title": "Weekly Reputation Report",
        "body": "function generateWeeklyReport(weekData) {\n  const avgScore = Math.round(\n    weekData.reduce((sum, d) => sum + d.score, 0) / weekData.length\n  );\n  const totalMentions = weekData.reduce((sum, d) => sum + d.mentions, 0);\n  const totalNegative = weekData.reduce((sum, d) => sum + d.negative, 0);\n  const date = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });\n\n  return `# 📣 Weekly Reputation Report — ${BRAND_CONFIG.brandName}\n**Week ending:** ${date}\n\n---\n\n## 📊 At a Glance\n\n| Metric | Value |\n|---|---|\n| Reputation Score | ${avgScore}/100 |\n| Total Mentions | ${totalMentions} |\n| Negative Mentions | ${totalNegative} (${Math.round(totalNegative/totalMentions*100)}%) |\n| Crisis Events | ${weekData.filter(d => d.hadCrisis).length} |\n| Trend | ${avgScore >= 70 ? \"✅ Healthy\" : avgScore >= 50 ? \"⚠️ Watch\" : \"🚨 At Risk\"} |\n\n---\n\n## 📈 Day-by-Day Sentiment\n\n${weekData.map(d =>\n  `**${d.date}** — Score: ${d.score}/100 | Mentions: ${d.mentions} | Neg: ${d.negative}`\n).join('\\n')}\n\n---\n\n## 🔴 Top Complaints This Week\n${weekData.flatMap(d => d.complaints || []).slice(0, 8).map(c => `- ${c}`).join('\\n')}\n\n---\n\n## 🟢 Top Praises This Week\n${weekData.flatMap(d => d.praises || []).slice(0, 5).map(p => `- ${p}`).join('\\n')}\n\n---\n\n## 💡 Recommended Actions\n1. Address top recurring complaint systematically — not just one-by-one\n2. Amplify positive mentions by engaging with brand advocates\n3. Monitor competitor sentiment for positioning opportunities\n\n---\n*Generated by Social Listening Bot • Powered by Apify + Claude*\n`;\n}"
      },
      {
        "title": "Master Orchestrator — Full Pipeline",
        "body": "import cron from 'node-cron';\nimport { writeFileSync } from 'fs';\n\nasync function runSocialListening() {\n  console.log(`\\n👂 Social Listening scan — ${new Date().toISOString()}`);\n\n  try {\n    // STEP 1 — Scrape all platforms\n    console.log(\"[1/5] Scraping mentions...\");\n    const mentions = await scrapeAllMentions();\n    console.log(`  ✅ ${mentions.length} mentions collected`);\n\n    if (mentions.length === 0) {\n      console.log(\"  ℹ️  No new mentions found\");\n      return;\n    }\n\n    // STEP 2 — Analyze sentiment\n    console.log(\"[2/5] Analyzing sentiment with Claude...\");\n    const analysis = await analyzeSentiment(mentions);\n    const score = analysis.batchSentiment.overallScore;\n    console.log(`  ✅ Score: ${score}/100 | Neg: ${analysis.batchSentiment.negative} | Trend: ${analysis.batchSentiment.trend}`);\n\n    // STEP 3 — Detect crisis\n    console.log(\"[3/5] Checking for crisis signals...\");\n    const crisisAlerts = detectCrisis(analysis);\n    console.log(`  ✅ ${crisisAlerts.length} crisis signal(s) detected`);\n\n    // STEP 4 — Generate response suggestions for urgent mentions\n    const urgentMentions = analysis.analyzedMentions?.filter(m =>\n      m.requiresResponse && (m.urgency === \"critical\" || m.urgency === \"high\")\n    ) || [];\n    let responses = { suggestions: [] };\n\n    if (urgentMentions.length > 0) {\n      console.log(`[4/5] Generating ${urgentMentions.length} response suggestions...`);\n      responses = await generateResponseSuggestions(urgentMentions);\n      console.log(`  ✅ ${responses.suggestions?.length} response drafts ready`);\n    }\n\n    // STEP 5 — Send alerts if needed\n    if (crisisAlerts.length > 0) {\n      console.log(\"[5/5] Sending crisis alerts...\");\n      if (process.env.SLACK_WEBHOOK_URL) {\n        await sendSlackAlert(crisisAlerts, analysis, responses);\n      }\n      if (process.env.TELEGRAM_BOT_TOKEN) {\n        await sendTelegramAlert(crisisAlerts, analysis);\n      }\n      console.log(\"  ✅ Alerts sent\");\n    } else {\n      console.log(\"[5/5] No alerts needed — reputation looks healthy\");\n    }\n\n    // Save report\n    const report = {\n      scannedAt: new Date().toISOString(),\n      mentionsFound: mentions.length,\n      sentimentScore: score,\n      trend: analysis.batchSentiment.trend,\n      crisisAlerts,\n      topComplaints: analysis.topComplaintsThisRound,\n      topPraises: analysis.topPraisesThisRound,\n      responseSuggestions: responses.suggestions\n    };\n\n    writeFileSync(`./reputation-log-${Date.now()}.json`, JSON.stringify(report, null, 2));\n    return report;\n\n  } catch (err) {\n    console.error(\"Listening error:\", err.message);\n  }\n}\n\n// Scan every hour\ncron.schedule('0 * * * *', runSocialListening);\n\n// Run immediately on startup\nrunSocialListening();"
      },
      {
        "title": "Environment Variables",
        "body": "# .env\nAPIFY_TOKEN=apify_api_xxxxxxxxxxxxxxxx\nCLAUDE_API_KEY=sk-ant-xxxxxxxxxxxxxxxx\n\n# Alerts\nSLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx\nTELEGRAM_BOT_TOKEN=123456789:AABBccDDeeFFggHH\nTELEGRAM_CHAT_ID=-1001234567890\n\n# Optional\nALERT_EMAIL=team@yourbrand.com"
      },
      {
        "title": "Normalized Mention Schema",
        "body": "{\n  \"source\": \"twitter\",\n  \"text\": \"Just tried YourBrand and honestly it is broken...\",\n  \"author\": \"user123\",\n  \"followers\": 12400,\n  \"engagements\": 847,\n  \"sentiment\": \"negative\",\n  \"sentimentScore\": 2,\n  \"emotionalTone\": \"frustrated\",\n  \"category\": \"product_feedback\",\n  \"urgency\": \"high\",\n  \"requiresResponse\": true,\n  \"isCrisisSignal\": false,\n  \"keyTopics\": [\"bug\", \"login\", \"performance\"],\n  \"url\": \"https://twitter.com/user123/status/xxx\",\n  \"createdAt\": \"2025-02-25T09:00:00Z\"\n}"
      },
      {
        "title": "Best Practices",
        "body": "Scan every 30–60 minutes for real-time monitoring, every 4 hours for standard tracking\nAlways monitor competitor brand names in parallel for benchmarking opportunities\nSet crisisThreshold.negativeSpike based on your normal daily volume — not a fixed number\nFlag and ignore spam/bot mentions — Claude's confidenceLevel field helps filter these\nRoute critical alerts to on-call Slack/phone, high alerts to the team channel\nUse the response suggestions as drafts only — always have a human review before posting\nArchive all mention logs for quarterly trend analysis and PR reporting"
      },
      {
        "title": "Error Handling",
        "body": "try {\n  const mentions = await scrapeAllMentions();\n  return mentions;\n} catch (error) {\n  if (error.statusCode === 401) throw new Error(\"Invalid Apify token\");\n  if (error.statusCode === 429) throw new Error(\"Rate limit hit — space out scraping intervals\");\n  if (error.message.includes(\"TELEGRAM\")) throw new Error(\"Telegram config error — check token and chat ID\");\n  throw error;\n}"
      },
      {
        "title": "Requirements",
        "body": "Apify account → https://www.apify.com/?fpr=dx06p\nClaude / OpenClaw API key\nNode.js 18+ with apify-client, axios, node-cron\nSlack workspace and/or Telegram bot for alerts\nOptional: Redis for persistent sentiment history and trend tracking across restarts"
      }
    ],
    "body": "Social Listening & Brand Reputation Monitor Skill\nOverview\n\nThis skill builds a real-time brand reputation monitoring system that:\n\nApify scrapes Twitter/X, Reddit, forums, and news sites for every mention of your brand\nClaude (OpenClaw) analyzes sentiment, detects crises, and classifies each mention\nAlerts fire instantly to Slack, Telegram, or email when reputation risk is detected\n\nThe result: you know what people are saying about your brand the moment they say it — and you can respond before it becomes a crisis.\n\n🔗 Apify: https://www.apify.com/?fpr=dx06p\n\nWhat This Skill Does\nMonitor Twitter/X, Reddit, forums, and news for brand mentions in real-time\nPerform sentiment analysis on every mention (positive / negative / neutral)\nDetect crisis signals — sudden spikes in negative mentions\nTrack competitor mentions for comparative reputation benchmarking\nScore reputation health over time with a rolling dashboard score\nAlert immediately on Slack/Telegram when a crisis threshold is crossed\nGenerate weekly reputation reports with trends and actionable insights\nDistinguish genuine complaints from spam or bot activity\nArchitecture Overview\n┌──────────────────────────────────────────────────────────────────┐\n│           SOCIAL LISTENING & REPUTATION MONITOR                  │\n│                                                                  │\n│  ┌──────────────────────────────────────────────────────────┐   │\n│  │  LAYER 1 — MENTION SCRAPING (Apify)                      │   │\n│  │  Twitter/X │ Reddit │ Hacker News │ Google News           │   │\n│  │  Trustpilot │ G2 │ App Store │ Niche Forums               │   │\n│  └───────────────────────────┬──────────────────────────────┘   │\n│                              │                                   │\n│  ┌───────────────────────────▼──────────────────────────────┐   │\n│  │  LAYER 2 — REPUTATION ANALYSIS ENGINE (Claude)           │   │\n│  │                                                          │   │\n│  │  • Sentiment Classifier   → pos / neg / neutral + score  │   │\n│  │  • Crisis Detector        → spike in neg mentions        │   │\n│  │  • Topic Categorizer      → product | support | pr | etc │   │\n│  │  • Influence Scorer       → who is talking (reach)       │   │\n│  │  • Response Generator     → suggested reply drafts       │   │\n│  └───────────────────────────┬──────────────────────────────┘   │\n│                              │                                   │\n│  ┌───────────────────────────▼──────────────────────────────┐   │\n│  │  LAYER 3 — ALERTS & REPORTING                            │   │\n│  │  Slack │ Telegram │ Email │ Dashboard │ Weekly Report     │   │\n│  └──────────────────────────────────────────────────────────┘   │\n└──────────────────────────────────────────────────────────────────┘\n\nStep 1 — Get Your API Keys\nApify\nSign up at https://www.apify.com/?fpr=dx06p\nGo to Settings → Integrations\nCopy your token:\nexport APIFY_TOKEN=apify_api_xxxxxxxxxxxxxxxx\n\nClaude / OpenClaw\nexport CLAUDE_API_KEY=sk-ant-xxxxxxxxxxxxxxxx\n\nSlack Webhook (optional)\nGo to api.slack.com/apps → Create App → Incoming Webhooks\nCopy the webhook URL:\nexport SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx\n\nTelegram Bot (optional)\nexport TELEGRAM_BOT_TOKEN=123456789:AABBccDDeeFFggHH\nexport TELEGRAM_CHAT_ID=-1001234567890\n\nStep 2 — Install Dependencies\nnpm install apify-client axios node-cron dotenv\n\nConfiguration — Define Your Brand\n// config.js\nexport const BRAND_CONFIG = {\n  brandName: \"YourBrand\",\n  keywords: [\n    \"YourBrand\",\n    \"YourBrand.com\",\n    \"@YourBrandHandle\",\n    \"#YourBrand\",\n    \"your brand common misspelling\"\n  ],\n  competitors: [\"CompetitorA\", \"CompetitorB\"],\n  crisisThreshold: {\n    negativeSpike: 5,       // alert if 5+ negative mentions in one scan\n    sentimentDrop: 20,      // alert if sentiment score drops 20 points\n    viralThreshold: 1000    // alert if a negative post hits 1000+ engagements\n  },\n  language: \"en\",\n  timezone: \"America/New_York\"\n};\n\nLayer 1 — Multi-Platform Mention Scraper (Apify)\nScrape Twitter/X Mentions\nimport ApifyClient from 'apify-client';\nimport { BRAND_CONFIG } from './config.js';\n\nconst apify = new ApifyClient({ token: process.env.APIFY_TOKEN });\n\nasync function scrapeTwitterMentions() {\n  console.log(\"🐦 Scraping Twitter/X mentions...\");\n\n  const run = await apify.actor(\"apify/twitter-scraper\").call({\n    searchTerms: BRAND_CONFIG.keywords,\n    maxTweets: 100,\n    addUserInfo: true,\n    startUrls: [],\n    languageFilter: BRAND_CONFIG.language\n  });\n\n  const { items } = await run.dataset().getData();\n\n  return items.map(t => ({\n    source:      \"twitter\",\n    id:          t.id,\n    text:        t.fullText || t.text,\n    author:      t.author?.userName,\n    authorName:  t.author?.name,\n    followers:   t.author?.followers || 0,\n    verified:    t.author?.isVerified || false,\n    likes:       t.likeCount || 0,\n    retweets:    t.retweetCount || 0,\n    replies:     t.replyCount || 0,\n    engagements: (t.likeCount || 0) + (t.retweetCount || 0) * 2 + (t.replyCount || 0),\n    url:         t.url,\n    createdAt:   t.createdAt,\n    scrapedAt:   new Date().toISOString()\n  }));\n}\n\nScrape Reddit Mentions\nasync function scrapeRedditMentions() {\n  console.log(\"👽 Scraping Reddit mentions...\");\n\n  const searchQueries = BRAND_CONFIG.keywords.map(k =>\n    apify.actor(\"apify/reddit-search-scraper\").call({\n      queries: [k],\n      maxItems: 30,\n      sort: \"new\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items)\n  );\n\n  const results = await Promise.all(searchQueries);\n\n  return results.flat().map(p => ({\n    source:      \"reddit\",\n    id:          p.id,\n    text:        p.title + \" \" + (p.selftext || \"\"),\n    title:       p.title,\n    author:      p.author,\n    subreddit:   p.subreddit,\n    score:       p.score,\n    comments:    p.numComments,\n    upvoteRatio: p.upvoteRatio,\n    engagements: p.score + p.numComments * 2,\n    url:         p.url,\n    createdAt:   new Date(p.created * 1000).toISOString(),\n    scrapedAt:   new Date().toISOString()\n  }));\n}\n\nScrape News & Review Platforms\nasync function scrapeNewsAndReviews() {\n  console.log(\"📰 Scraping news and reviews...\");\n\n  const brandQuery = BRAND_CONFIG.brandName;\n\n  const [news, trustpilot, hackerNews] = await Promise.all([\n\n    // Google News\n    apify.actor(\"apify/google-search-scraper\").call({\n      queries: [`\"${brandQuery}\" news`],\n      maxPagesPerQuery: 2,\n      resultsPerPage: 20,\n      dateRange: \"pastWeek\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"google_news\",\n        text:      r.title + \" \" + r.snippet,\n        title:     r.title,\n        url:       r.url,\n        createdAt: r.date || new Date().toISOString(),\n        scrapedAt: new Date().toISOString()\n      }))),\n\n    // Trustpilot reviews\n    apify.actor(\"apify/trustpilot-scraper\").call({\n      startUrls: [{ url: `https://www.trustpilot.com/review/${brandQuery.toLowerCase()}.com` }],\n      maxReviews: 50,\n      filterScore: [1, 2, 3]   // focus on negative/neutral\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"trustpilot\",\n        text:      r.reviewBody,\n        title:     r.reviewTitle,\n        rating:    r.ratingValue,\n        author:    r.author,\n        url:       r.url,\n        createdAt: r.datePublished,\n        scrapedAt: new Date().toISOString()\n      }))).catch(() => []),  // graceful fail if brand not on Trustpilot\n\n    // Hacker News\n    apify.actor(\"apify/hacker-news-scraper\").call({\n      searchQuery: brandQuery,\n      maxItems: 20,\n      type: \"story\"\n    }).then(run => run.dataset().getData())\n      .then(d => d.items.map(r => ({\n        source:    \"hacker_news\",\n        text:      r.title + \" \" + (r.text || \"\"),\n        title:     r.title,\n        author:    r.by,\n        score:     r.score,\n        comments:  r.descendants,\n        url:       r.url || `https://news.ycombinator.com/item?id=${r.id}`,\n        createdAt: new Date(r.time * 1000).toISOString(),\n        scrapedAt: new Date().toISOString()\n      }))).catch(() => [])\n\n  ]);\n\n  return [...news, ...trustpilot, ...hackerNews];\n}\n\nAggregate All Mentions\nasync function scrapeAllMentions() {\n  const [twitter, reddit, newsReviews] = await Promise.all([\n    scrapeTwitterMentions(),\n    scrapeRedditMentions(),\n    scrapeNewsAndReviews()\n  ]);\n\n  const all = [...twitter, ...reddit, ...newsReviews];\n\n  // Deduplicate by URL\n  const seen = new Set();\n  return all.filter(m => {\n    if (seen.has(m.url)) return false;\n    seen.add(m.url);\n    return true;\n  });\n}\n\nLayer 2 — Reputation Analysis Engine (Claude)\nSentiment Classifier\nimport axios from 'axios';\n\nconst claude = axios.create({\n  baseURL: 'https://api.anthropic.com/v1',\n  headers: {\n    'x-api-key': process.env.CLAUDE_API_KEY,\n    'anthropic-version': '2023-06-01',\n    'Content-Type': 'application/json'\n  }\n});\n\nasync function analyzeSentiment(mentions) {\n  const prompt = `\nYou are a brand reputation analyst. Analyze each mention and classify it.\n\nBRAND: ${BRAND_CONFIG.brandName}\n\nMENTIONS TO ANALYZE:\n${JSON.stringify(mentions.slice(0, 30), null, 2)}\n\nRespond ONLY in this JSON format:\n{\n  \"analyzedMentions\": [\n    {\n      \"id\": \"mention id or url\",\n      \"sentiment\": \"positive | negative | neutral | mixed\",\n      \"sentimentScore\": 7,\n      \"confidenceLevel\": \"high | medium | low\",\n      \"emotionalTone\": \"angry | frustrated | disappointed | happy | excited | neutral | sarcastic\",\n      \"category\": \"product_feedback | customer_support | pr_crisis | competitor_comparison | spam | praise | question | bug_report\",\n      \"urgency\": \"critical | high | medium | low\",\n      \"isInfluencer\": true,\n      \"requiresResponse\": true,\n      \"suggestedResponseTone\": \"apologetic | informative | appreciative | ignore\",\n      \"keyTopics\": [\"topic1\", \"topic2\"],\n      \"isCrisisSignal\": false,\n      \"summary\": \"one-line summary of what was said\"\n    }\n  ],\n  \"batchSentiment\": {\n    \"positive\": 0,\n    \"negative\": 0,\n    \"neutral\": 0,\n    \"mixed\": 0,\n    \"overallScore\": 65,\n    \"trend\": \"improving | declining | stable\"\n  },\n  \"crisisSignals\": [\n    {\n      \"signal\": \"description of the risk\",\n      \"severity\": \"critical | high | medium\",\n      \"source\": \"platform\",\n      \"url\": \"url of the post\",\n      \"recommendedAction\": \"what to do right now\"\n    }\n  ],\n  \"topComplaintsThisRound\": [\"complaint 1\", \"complaint 2\"],\n  \"topPraisesThisRound\": [\"praise 1\", \"praise 2\"]\n}\n`;\n\n  const { data } = await claude.post('/messages', {\n    model: \"claude-opus-4-5\",\n    max_tokens: 4000,\n    messages: [{ role: \"user\", content: prompt }]\n  });\n\n  return JSON.parse(data.content[0].text.replace(/```json|```/g, '').trim());\n}\n\nCrisis Detector\n// Rolling sentiment history (use Redis/DB in production)\nconst sentimentHistory = [];\n\nfunction detectCrisis(analysis) {\n  const crisisAlerts = [];\n  const batch = analysis.batchSentiment;\n  const signals = analysis.crisisSignals || [];\n\n  // Track history\n  sentimentHistory.push({\n    score: batch.overallScore,\n    negative: batch.negative,\n    timestamp: new Date().toISOString()\n  });\n\n  const prev = sentimentHistory[sentimentHistory.length - 2];\n\n  // CRISIS TRIGGER 1 — Spike in negative mentions\n  if (batch.negative >= BRAND_CONFIG.crisisThreshold.negativeSpike) {\n    crisisAlerts.push({\n      type: \"negative_spike\",\n      severity: \"critical\",\n      message: `🚨 ${batch.negative} negative mentions detected in this scan`,\n      threshold: BRAND_CONFIG.crisisThreshold.negativeSpike,\n      current: batch.negative\n    });\n  }\n\n  // CRISIS TRIGGER 2 — Sentiment score drop\n  if (prev && (prev.score - batch.overallScore) >= BRAND_CONFIG.crisisThreshold.sentimentDrop) {\n    crisisAlerts.push({\n      type: \"sentiment_drop\",\n      severity: \"high\",\n      message: `📉 Sentiment dropped from ${prev.score} to ${batch.overallScore} (-${prev.score - batch.overallScore} pts)`,\n      previousScore: prev.score,\n      currentScore: batch.overallScore\n    });\n  }\n\n  // CRISIS TRIGGER 3 — High-engagement negative post\n  const viralNegative = analysis.analyzedMentions?.filter(m =>\n    m.sentiment === \"negative\" &&\n    m.urgency === \"critical\"\n  ) || [];\n\n  if (viralNegative.length > 0) {\n    crisisAlerts.push({\n      type: \"viral_negative\",\n      severity: \"high\",\n      message: `🔥 ${viralNegative.length} high-urgency negative mention(s) detected`,\n      mentions: viralNegative.map(m => m.id)\n    });\n  }\n\n  // Add explicit crisis signals from Claude\n  signals.forEach(signal => {\n    if (signal.severity === \"critical\" || signal.severity === \"high\") {\n      crisisAlerts.push({ ...signal, type: \"claude_signal\" });\n    }\n  });\n\n  return crisisAlerts;\n}\n\nResponse Suggestion Generator\nasync function generateResponseSuggestions(urgentMentions) {\n  if (urgentMentions.length === 0) return [];\n\n  const prompt = `\nYou are a brand communications expert. Write response suggestions for these urgent mentions.\nBe empathetic, on-brand, and action-oriented. Never defensive.\n\nBRAND: ${BRAND_CONFIG.brandName}\n\nURGENT MENTIONS REQUIRING RESPONSE:\n${JSON.stringify(urgentMentions.slice(0, 5), null, 2)}\n\nRespond ONLY in this JSON format:\n{\n  \"suggestions\": [\n    {\n      \"mentionId\": \"id or url\",\n      \"platform\": \"twitter | reddit | etc\",\n      \"originalText\": \"what they said (summarized)\",\n      \"sentiment\": \"negative | mixed\",\n      \"responseOptions\": [\n        {\n          \"tone\": \"apologetic\",\n          \"response\": \"full suggested response text\",\n          \"bestFor\": \"when the issue is your fault\"\n        },\n        {\n          \"tone\": \"informative\",\n          \"response\": \"full suggested response text\",\n          \"bestFor\": \"when it is a misunderstanding\"\n        }\n      ],\n      \"doNotDo\": \"what to avoid saying in this specific case\",\n      \"priority\": \"respond within 1h | 4h | 24h\"\n    }\n  ]\n}\n`;\n\n  const { data } = await claude.post('/messages', {\n    model: \"claude-opus-4-5\",\n    max_tokens: 2500,\n    messages: [{ role: \"user\", content: prompt }]\n  });\n\n  return JSON.parse(data.content[0].text.replace(/```json|```/g, '').trim());\n}\n\nLayer 3 — Alerts & Reporting\nSlack Alert Publisher\nasync function sendSlackAlert(crisisAlerts, analysis, responses) {\n  const isCrisis = crisisAlerts.some(a => a.severity === \"critical\");\n  const color = isCrisis ? \"#FF0000\" : \"#FFA500\";\n  const icon = isCrisis ? \"🚨\" : \"⚠️\";\n\n  const payload = {\n    attachments: [{\n      color,\n      blocks: [\n        {\n          type: \"header\",\n          text: { type: \"plain_text\", text: `${icon} Brand Alert: ${BRAND_CONFIG.brandName}` }\n        },\n        {\n          type: \"section\",\n          fields: [\n            { type: \"mrkdwn\", text: `*Sentiment Score:*\\n${analysis.batchSentiment.overallScore}/100` },\n            { type: \"mrkdwn\", text: `*Trend:*\\n${analysis.batchSentiment.trend}` },\n            { type: \"mrkdwn\", text: `*Negative Mentions:*\\n${analysis.batchSentiment.negative}` },\n            { type: \"mrkdwn\", text: `*Requires Response:*\\n${responses?.suggestions?.length || 0} mentions` }\n          ]\n        },\n        ...crisisAlerts.map(alert => ({\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `*${alert.severity?.toUpperCase()}:* ${alert.message}\\n${alert.recommendedAction || \"\"}`\n          }\n        })),\n        {\n          type: \"section\",\n          text: {\n            type: \"mrkdwn\",\n            text: `*Top Complaints:*\\n${analysis.topComplaintsThisRound?.map(c => `• ${c}`).join('\\n') || \"None\"}`\n          }\n        }\n      ]\n    }]\n  };\n\n  await axios.post(process.env.SLACK_WEBHOOK_URL, payload);\n}\n\nTelegram Crisis Alert\nasync function sendTelegramAlert(crisisAlerts, analysis) {\n  const severity = crisisAlerts[0]?.severity || \"medium\";\n  const icon = severity === \"critical\" ? \"🚨🚨🚨\" : \"⚠️\";\n\n  const message = `\n${icon} *BRAND ALERT: ${BRAND_CONFIG.brandName}*\n\n📊 *Reputation Score:* ${analysis.batchSentiment.overallScore}/100 (${analysis.batchSentiment.trend})\n😡 *Negative:* ${analysis.batchSentiment.negative} | 😊 *Positive:* ${analysis.batchSentiment.positive}\n\n*🔴 Crisis Signals:*\n${crisisAlerts.map(a => `• [${a.severity?.toUpperCase()}] ${a.message}`).join('\\n')}\n\n*📢 Top Complaints:*\n${analysis.topComplaintsThisRound?.slice(0, 3).map(c => `• ${c}`).join('\\n') || \"• None\"}\n\n*✅ Top Praises:*\n${analysis.topPraisesThisRound?.slice(0, 2).map(p => `• ${p}`).join('\\n') || \"• None\"}\n\n⏰ ${new Date().toLocaleString()}\n`.trim();\n\n  await axios.post(\n    `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`,\n    {\n      chat_id: process.env.TELEGRAM_CHAT_ID,\n      text: message,\n      parse_mode: \"Markdown\"\n    }\n  );\n}\n\nWeekly Reputation Report\nfunction generateWeeklyReport(weekData) {\n  const avgScore = Math.round(\n    weekData.reduce((sum, d) => sum + d.score, 0) / weekData.length\n  );\n  const totalMentions = weekData.reduce((sum, d) => sum + d.mentions, 0);\n  const totalNegative = weekData.reduce((sum, d) => sum + d.negative, 0);\n  const date = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });\n\n  return `# 📣 Weekly Reputation Report — ${BRAND_CONFIG.brandName}\n**Week ending:** ${date}\n\n---\n\n## 📊 At a Glance\n\n| Metric | Value |\n|---|---|\n| Reputation Score | ${avgScore}/100 |\n| Total Mentions | ${totalMentions} |\n| Negative Mentions | ${totalNegative} (${Math.round(totalNegative/totalMentions*100)}%) |\n| Crisis Events | ${weekData.filter(d => d.hadCrisis).length} |\n| Trend | ${avgScore >= 70 ? \"✅ Healthy\" : avgScore >= 50 ? \"⚠️ Watch\" : \"🚨 At Risk\"} |\n\n---\n\n## 📈 Day-by-Day Sentiment\n\n${weekData.map(d =>\n  `**${d.date}** — Score: ${d.score}/100 | Mentions: ${d.mentions} | Neg: ${d.negative}`\n).join('\\n')}\n\n---\n\n## 🔴 Top Complaints This Week\n${weekData.flatMap(d => d.complaints || []).slice(0, 8).map(c => `- ${c}`).join('\\n')}\n\n---\n\n## 🟢 Top Praises This Week\n${weekData.flatMap(d => d.praises || []).slice(0, 5).map(p => `- ${p}`).join('\\n')}\n\n---\n\n## 💡 Recommended Actions\n1. Address top recurring complaint systematically — not just one-by-one\n2. Amplify positive mentions by engaging with brand advocates\n3. Monitor competitor sentiment for positioning opportunities\n\n---\n*Generated by Social Listening Bot • Powered by Apify + Claude*\n`;\n}\n\nMaster Orchestrator — Full Pipeline\nimport cron from 'node-cron';\nimport { writeFileSync } from 'fs';\n\nasync function runSocialListening() {\n  console.log(`\\n👂 Social Listening scan — ${new Date().toISOString()}`);\n\n  try {\n    // STEP 1 — Scrape all platforms\n    console.log(\"[1/5] Scraping mentions...\");\n    const mentions = await scrapeAllMentions();\n    console.log(`  ✅ ${mentions.length} mentions collected`);\n\n    if (mentions.length === 0) {\n      console.log(\"  ℹ️  No new mentions found\");\n      return;\n    }\n\n    // STEP 2 — Analyze sentiment\n    console.log(\"[2/5] Analyzing sentiment with Claude...\");\n    const analysis = await analyzeSentiment(mentions);\n    const score = analysis.batchSentiment.overallScore;\n    console.log(`  ✅ Score: ${score}/100 | Neg: ${analysis.batchSentiment.negative} | Trend: ${analysis.batchSentiment.trend}`);\n\n    // STEP 3 — Detect crisis\n    console.log(\"[3/5] Checking for crisis signals...\");\n    const crisisAlerts = detectCrisis(analysis);\n    console.log(`  ✅ ${crisisAlerts.length} crisis signal(s) detected`);\n\n    // STEP 4 — Generate response suggestions for urgent mentions\n    const urgentMentions = analysis.analyzedMentions?.filter(m =>\n      m.requiresResponse && (m.urgency === \"critical\" || m.urgency === \"high\")\n    ) || [];\n    let responses = { suggestions: [] };\n\n    if (urgentMentions.length > 0) {\n      console.log(`[4/5] Generating ${urgentMentions.length} response suggestions...`);\n      responses = await generateResponseSuggestions(urgentMentions);\n      console.log(`  ✅ ${responses.suggestions?.length} response drafts ready`);\n    }\n\n    // STEP 5 — Send alerts if needed\n    if (crisisAlerts.length > 0) {\n      console.log(\"[5/5] Sending crisis alerts...\");\n      if (process.env.SLACK_WEBHOOK_URL) {\n        await sendSlackAlert(crisisAlerts, analysis, responses);\n      }\n      if (process.env.TELEGRAM_BOT_TOKEN) {\n        await sendTelegramAlert(crisisAlerts, analysis);\n      }\n      console.log(\"  ✅ Alerts sent\");\n    } else {\n      console.log(\"[5/5] No alerts needed — reputation looks healthy\");\n    }\n\n    // Save report\n    const report = {\n      scannedAt: new Date().toISOString(),\n      mentionsFound: mentions.length,\n      sentimentScore: score,\n      trend: analysis.batchSentiment.trend,\n      crisisAlerts,\n      topComplaints: analysis.topComplaintsThisRound,\n      topPraises: analysis.topPraisesThisRound,\n      responseSuggestions: responses.suggestions\n    };\n\n    writeFileSync(`./reputation-log-${Date.now()}.json`, JSON.stringify(report, null, 2));\n    return report;\n\n  } catch (err) {\n    console.error(\"Listening error:\", err.message);\n  }\n}\n\n// Scan every hour\ncron.schedule('0 * * * *', runSocialListening);\n\n// Run immediately on startup\nrunSocialListening();\n\nEnvironment Variables\n# .env\nAPIFY_TOKEN=apify_api_xxxxxxxxxxxxxxxx\nCLAUDE_API_KEY=sk-ant-xxxxxxxxxxxxxxxx\n\n# Alerts\nSLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/xxx/xxx\nTELEGRAM_BOT_TOKEN=123456789:AABBccDDeeFFggHH\nTELEGRAM_CHAT_ID=-1001234567890\n\n# Optional\nALERT_EMAIL=team@yourbrand.com\n\nNormalized Mention Schema\n{\n  \"source\": \"twitter\",\n  \"text\": \"Just tried YourBrand and honestly it is broken...\",\n  \"author\": \"user123\",\n  \"followers\": 12400,\n  \"engagements\": 847,\n  \"sentiment\": \"negative\",\n  \"sentimentScore\": 2,\n  \"emotionalTone\": \"frustrated\",\n  \"category\": \"product_feedback\",\n  \"urgency\": \"high\",\n  \"requiresResponse\": true,\n  \"isCrisisSignal\": false,\n  \"keyTopics\": [\"bug\", \"login\", \"performance\"],\n  \"url\": \"https://twitter.com/user123/status/xxx\",\n  \"createdAt\": \"2025-02-25T09:00:00Z\"\n}\n\nBest Practices\nScan every 30–60 minutes for real-time monitoring, every 4 hours for standard tracking\nAlways monitor competitor brand names in parallel for benchmarking opportunities\nSet crisisThreshold.negativeSpike based on your normal daily volume — not a fixed number\nFlag and ignore spam/bot mentions — Claude's confidenceLevel field helps filter these\nRoute critical alerts to on-call Slack/phone, high alerts to the team channel\nUse the response suggestions as drafts only — always have a human review before posting\nArchive all mention logs for quarterly trend analysis and PR reporting\nError Handling\ntry {\n  const mentions = await scrapeAllMentions();\n  return mentions;\n} catch (error) {\n  if (error.statusCode === 401) throw new Error(\"Invalid Apify token\");\n  if (error.statusCode === 429) throw new Error(\"Rate limit hit — space out scraping intervals\");\n  if (error.message.includes(\"TELEGRAM\")) throw new Error(\"Telegram config error — check token and chat ID\");\n  throw error;\n}\n\nRequirements\nApify account → https://www.apify.com/?fpr=dx06p\nClaude / OpenClaw API key\nNode.js 18+ with apify-client, axios, node-cron\nSlack workspace and/or Telegram bot for alerts\nOptional: Redis for persistent sentiment history and trend tracking across restarts"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/g4dr/social-listening-monitor",
    "publisherUrl": "https://clawhub.ai/g4dr/social-listening-monitor",
    "owner": "g4dr",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/social-listening-monitor",
    "downloadUrl": "https://openagent3.xyz/downloads/social-listening-monitor",
    "agentUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent",
    "manifestUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/social-listening-monitor/agent.md"
  }
}