Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

Your Discord community’s mood shifts week to week — a buggy release tanks sentiment, a popular feature launch creates excitement. This job pulls messages from key channels (general, support, feedback), analyzes sentiment via Mave, and produces a weekly sentiment report with trend comparisons, topic breakdown, and recommended community actions. Flow: Discord GET /channels/{id}/messages (multiple channels) → Aggregate → Mavera POST /mave/chat → Weekly sentiment report

Code

import os, requests, time
from datetime import datetime, timedelta

DC_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
DC_BASE = "https://discord.com/api/v10"
DC_H = {"Authorization": f"Bot {DC_TOKEN}"}
MV = os.environ["MAVERA_API_KEY"]
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

CHANNELS = {
    "general": "CHANNEL_ID_1",
    "support": "CHANNEL_ID_2",
    "feedback": "CHANNEL_ID_3",
}
DAYS_BACK = 7
cutoff = datetime.utcnow() - timedelta(days=DAYS_BACK)
snowflake_cutoff = str(int((cutoff.timestamp() - 1420070400) * 1000) << 22)

# 1. Fetch messages from each channel
all_messages = {}
for name, channel_id in CHANNELS.items():
    msgs = []
    after = snowflake_cutoff
    while True:
        r = requests.get(f"{DC_BASE}/channels/{channel_id}/messages", headers=DC_H,
            params={"limit": 100, "after": after})
        if r.status_code == 429:
            retry = r.json().get("retry_after", 1)
            time.sleep(retry)
            continue
        r.raise_for_status()
        batch = r.json()
        if not batch:
            break
        msgs.extend(batch)
        after = batch[0]["id"]
        time.sleep(0.5)
    human = [m for m in msgs if not m.get("author",{}).get("bot",False)
             and len(m.get("content","")) > 10]
    all_messages[name] = human
    print(f"#{name}: {len(human)} messages")

# 2. Build per-channel corpus
corpus_parts = []
for name, msgs in all_messages.items():
    corpus_parts.append(f"\n## #{name} ({len(msgs)} messages)")
    for m in sorted(msgs, key=lambda x: x["id"])[-40:]:
        corpus_parts.append(
            f"- [{m.get('timestamp','')[:10]}] {m.get('content','')[:300]}"
        )

corpus = "\n".join(corpus_parts)

# 3. Mave sentiment analysis
report = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
    "message": f"Community sentiment analyst. Analyze {sum(len(v) for v in all_messages.values())} Discord messages from the past {DAYS_BACK} days.\n\n"
        f"{corpus[:8000]}\n\n"
        "Produce a WEEKLY COMMUNITY SENTIMENT REPORT:\n\n"
        "1. **Overall Sentiment** — Score (1-100) with label (thriving/healthy/neutral/concerning/critical)\n"
        "2. **Channel Breakdown** — Sentiment per channel with top topic\n"
        "3. **Hot Topics** — Top 5 discussion themes ranked by volume + sentiment\n"
        "4. **Praise Highlights** — What the community loves right now (with quotes)\n"
        "5. **Pain Points** — Top 3 frustrations (with quotes)\n"
        "6. **Emerging Trends** — New topics that weren't discussed last week\n"
        "7. **Recommended Actions** — 3 specific things the team should do this week\n\n"
        "Include representative quotes for each section."
}).json()

print(f"\n{'='*60}\nWEEKLY COMMUNITY SENTIMENT REPORT\n{cutoff.strftime('%b %d')}{datetime.utcnow().strftime('%b %d, %Y')}\n{'='*60}")
print(report.get("content", "")[:3000])

Example Output

#general: 312 messages
#support: 187 messages
#feedback: 94 messages

WEEKLY COMMUNITY SENTIMENT REPORT
Mar 10 – Mar 17, 2026
============================================================

1. OVERALL SENTIMENT: 72/100 (Healthy)
   Up from ~65 last week — new feature launch drove positive discussion.

2. CHANNEL BREAKDOWN:
   - #general: 78/100 — Excited about v2.5 launch
   - #support: 58/100 — Mobile app crashes dominating
   - #feedback: 74/100 — Feature requests constructive

3. HOT TOPICS:
   1. v2.5 Launch (142 mentions, positive)
   2. Mobile App Crashes (34 mentions, negative)
   3. API Rate Limits (22 mentions, mixed)
   4. Dark Mode Request (18 mentions, positive)
   5. Pricing Discussion (12 mentions, neutral)

4. PRAISE: "The new dashboard is actually insane. Switched from
   [Competitor] last month and don't regret it at all."

5. PAIN POINTS:
   - "App crashes every time I open notifications on Android 14"
   - "Why is the API limited to 100 req/min? That's not usable."

Error Handling

Without MESSAGE_CONTENT intent enabled, bot receives empty content for messages in guilds with 100+ members. Enable it in Developer Portal → Bot settings.
Discord returns 429 with retry_after in seconds. The code handles this automatically. Global limit is 50 req/sec but per-route limits are lower.
Discord uses snowflake IDs for pagination. The after parameter fetches messages newer than a given ID. Convert timestamps to snowflakes for date-based filtering.