Scenario
Your community asks the same questions repeatedly across#help, #general, and #support. This job mines question messages (identified by ? marks and question patterns), clusters them by topic via Mave, then generates polished FAQ entries with your brand voice — ready for docs, help centers, or pinned messages.
Flow: Discord GET /channels/{id}/messages → Filter questions → Mavera POST /mave/chat (cluster) → POST /generations (FAQ entries)
Code
import os, requests, time, re
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"}
HELP_CHANNELS = ["CHANNEL_ID_1", "CHANNEL_ID_2"]
QUESTION_PATTERNS = re.compile(r"\?|how do|how can|is there|can i|does anyone|why does|where is|what is", re.I)
# 1. Collect questions from channels
all_questions = []
for channel_id in HELP_CHANNELS:
after = "0"
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:
time.sleep(r.json().get("retry_after", 1))
continue
if not r.ok:
break
batch = r.json()
if not batch:
break
for m in batch:
content = m.get("content", "")
if (not m.get("author",{}).get("bot",False)
and QUESTION_PATTERNS.search(content)
and 15 < len(content) < 500):
all_questions.append(content)
after = batch[0]["id"]
if len(all_questions) >= 200:
break
time.sleep(0.5)
print(f"Collected {len(all_questions)} questions")
# 2. Cluster via Mave
clustering = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"FAQ analyst. Cluster these {len(all_questions)} community questions into 10-15 FAQ topics.\n\n"
+ "\n".join(f"- {q[:200]}" for q in all_questions[:100])
+ "\n\nFor each cluster:\n- Topic name (clear, concise)\n- Frequency (how many questions)\n- Representative question (best phrased example)\n- Key details needed in the answer\n\nRank by frequency."
}).json()
clusters_text = clustering.get("content", "")
print(f"\nClusters:\n{clusters_text[:1500]}")
# 3. Generate FAQ entries
faq = requests.post(f"{MV_BASE}/generations", headers=MV_H, json={
"prompt": f"Generate a complete FAQ page from these clustered community questions.\n\n"
f"CLUSTERS:\n{clusters_text[:4000]}\n\n"
"For each FAQ entry:\n"
"- Question (phrased clearly, search-friendly)\n"
"- Answer (2-4 sentences, helpful, specific)\n"
"- Related: link suggestions to other FAQ entries\n\n"
"Tone: friendly, direct, knowledgeable. Use 'you' not 'the user'.\n"
"Format as markdown with ## headers per question.",
}).json()
print(f"\n{'='*60}\nGENERATED FAQ ({len(all_questions)} questions → clustered)\n{'='*60}")
print(faq.get("output", faq.get("content", ""))[:2500])
const DC_TOKEN = process.env.DISCORD_BOT_TOKEN;
const DC_BASE = "https://discord.com/api/v10";
const DC_H = { Authorization: `Bot ${DC_TOKEN}` };
const MV = process.env.MAVERA_API_KEY;
const MV_BASE = "https://app.mavera.io/api/v1";
const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const HELP_CHANNELS = ["CHANNEL_ID_1", "CHANNEL_ID_2"];
const QUESTION_RE = /\?|how do|how can|is there|can i|does anyone|why does|where is|what is/i;
// 1. Collect questions
const allQuestions = [];
for (const channelId of HELP_CHANNELS) {
let after = "0";
while (allQuestions.length < 200) {
const r = await fetch(`${DC_BASE}/channels/${channelId}/messages?limit=100&after=${after}`, { headers: DC_H });
if (r.status === 429) { await new Promise(res => setTimeout(res, (await r.json()).retry_after * 1000 || 1000)); continue; }
const batch = await r.json();
if (!batch.length) break;
for (const m of batch) {
const c = m.content || "";
if (!m.author?.bot && QUESTION_RE.test(c) && c.length > 15 && c.length < 500)
allQuestions.push(c);
}
after = batch[0].id;
await new Promise(r => setTimeout(r, 500));
}
}
console.log(`Collected ${allQuestions.length} questions`);
// 2. Cluster
const clustering = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
body: JSON.stringify({ message: `Cluster ${allQuestions.length} questions into 10-15 FAQ topics.\n\n${allQuestions.slice(0,100).map(q=>`- ${q.slice(0,200)}`).join("\n")}\n\nPer cluster: topic, frequency, best question, answer details. Rank by frequency.` }),
}).then(r => r.json());
const clustersText = clustering.content || "";
console.log(`Clusters:\n${clustersText.slice(0, 1500)}`);
// 3. FAQ
const faq = await fetch(`${MV_BASE}/generations`, { method: "POST", headers: MV_H,
body: JSON.stringify({ prompt: `FAQ page from clusters.\n\n${clustersText.slice(0,4000)}\n\nPer entry: clear question, answer (2-4 sentences), related links.\n\nTone: friendly, direct. Use 'you'. Markdown ## headers.` }),
}).then(r => r.json());
console.log(`\n${"=".repeat(60)}\nGENERATED FAQ`);
console.log((faq.output || faq.content || "").slice(0, 2500));
Example Output
Collected 187 questions
Clusters:
1. Pricing & Plans (32 questions) — "What's the difference between free and pro?"
2. API Rate Limits (24 questions) — "How do I increase my API rate limit?"
3. Mobile App Issues (21 questions) — "Why does the app crash on Android?"
4. SSO / Team Setup (18 questions) — "How do I set up SSO for my org?"
5. Integrations (15 questions) — "Can I connect this to Zapier?"
GENERATED FAQ
============================================================
## What's the difference between the Free and Pro plans?
Free gives you up to 5 team members, 1,000 API calls/day, and access
to core features. Pro unlocks unlimited team members, 50,000 API
calls/day, SSO, custom dashboards, and priority support. You can
compare plans side-by-side on our [pricing page](/pricing).
**Related:** How do I upgrade? | What happens when I hit the API limit?
## How do I increase my API rate limit?
Your rate limit depends on your plan — Free is 100 req/min, Pro is
1,000 req/min. If you need more, contact sales for an Enterprise plan
with custom limits. In the meantime, implement client-side caching
and exponential backoff to maximize your current allocation.
Error Handling
Question detection
Question detection
The regex pattern catches most questions but may miss imperative-style questions (“Tell me how to…”). Add patterns for your community’s question style.
Noise filtering
Noise filtering
Community channels contain memes, off-topic chat, and reactions. The length filter (15-500 chars) removes most noise. For cleaner results, only process messages from specific roles.
FAQ staleness
FAQ staleness
Regenerate monthly. Pin the latest FAQ in your Discord channels and update help center docs via webhook.