Skip to main content

Cross-Platform Stats Bot Module

A bot module that tracks chat statistics across Twitch, YouTube, and Kick simultaneously. Demonstrates multi-platform configuration, cross-platform message handling via keywords, and a unified leaderboard.

Project structure

cross-platform-stats/
├── lumio.config.json
├── server/
│ ├── schema.ts
│ └── functions.ts
├── package.json
└── tsconfig.json

lumio.config.json

{
"$schema": "https://lumio.vision/schemas/lumio.config.schema.json",
"extension_id": "ext_placeholder",
"name": "Cross-Platform Chat Stats",
"category": "bot_module",
"version": "1.0.0",
"platforms": ["twitch", "youtube", "kick"],
"server": true,
"permissions": ["chat:send", "chat:read", "events:read"],
"triggers": {
"commands": [
{
"name": "stats",
"description": "Show your chat stats across all platforms",
"cooldown_global": 5,
"cooldown_user": 15,
"min_role": "everyone"
},
{
"name": "leaderboard",
"description": "Show the top chatters across all platforms",
"cooldown_global": 15,
"cooldown_user": 60,
"min_role": "everyone"
},
{
"name": "platformstats",
"description": "Show message count breakdown by platform",
"cooldown_global": 10,
"cooldown_user": 30,
"min_role": "everyone"
}
],
"events": [
"twitch:chat",
"youtube:chat",
"kick:chat"
],
"timers": [
{
"name": "statsSummary",
"interval": 900,
"handler": "statsSummary"
}
]
},
"config_schema": [
{
"key": "leaderboardSize",
"type": "number",
"label": "Number of users shown in leaderboard",
"default_value": 10,
"required": true
},
{
"key": "trackAllMessages",
"type": "boolean",
"label": "Track all messages (not just commands)",
"default_value": true,
"required": true
}
]
}

server/schema.ts

import { defineSchema, defineTable, column } from "@zaflun/lumio-sdk/server";

export default defineSchema({
chat_stats: defineTable({
user_id: column.string(),
user_name: column.string(),
platform: column.string(),
message_count: column.number(),
first_seen: column.number(),
last_seen: column.number(),
}),
});

server/functions.ts

import { command, event, timer } from "@zaflun/lumio-sdk/server";

// Event handler — track Twitch chat messages
export const trackTwitchChat = event("twitch:chat", async (ctx, evt) => {
const trackAll = (ctx.config.trackAllMessages as boolean) ?? true;
if (!trackAll) return null;

await trackMessage(ctx, evt.user_id as string, evt.user_name as string, "twitch");
return null;
});

// Event handler — track YouTube chat messages
export const trackYoutubeChat = event("youtube:chat", async (ctx, evt) => {
const trackAll = (ctx.config.trackAllMessages as boolean) ?? true;
if (!trackAll) return null;

await trackMessage(ctx, evt.user_id as string, evt.user_name as string, "youtube");
return null;
});

// Event handler — track Kick chat messages
export const trackKickChat = event("kick:chat", async (ctx, evt) => {
const trackAll = (ctx.config.trackAllMessages as boolean) ?? true;
if (!trackAll) return null;

await trackMessage(ctx, evt.user_id as string, evt.user_name as string, "kick");
return null;
});

// Helper: track a message from any platform
async function trackMessage(ctx: any, userId: string, userName: string, platform: string) {
// Increment per-platform counter in cache
const cacheKey = `msgs:${platform}:${userId}`;
await ctx.cache.increment(cacheKey, 1);

// Increment total counter
await ctx.cache.increment(`total:${platform}`, 1);
await ctx.cache.increment("total:all", 1);

// Persist to DB in background (batch-friendly — runs after handler returns)
ctx.defer(async () => {
const existing = await ctx.db.get("chat_stats", `${userId}:${platform}`);
if (existing) {
await ctx.db.patch("chat_stats", `${userId}:${platform}`, {
message_count: ((existing.message_count as number) ?? 0) + 1,
last_seen: Date.now(),
user_name: userName,
});
} else {
await ctx.db.insert("chat_stats", {
user_id: userId,
user_name: userName,
platform,
message_count: 1,
first_seen: Date.now(),
last_seen: Date.now(),
});
}
});
}

// !stats — Show the caller's chat stats across all platforms
export const stats = command("stats", async (ctx, args) => {
const platforms = ["twitch", "youtube", "kick"];
const counts: string[] = [];

for (const platform of platforms) {
const cached = (await ctx.cache.get(`msgs:${platform}:${ctx.user.id}`)) as number | null;
if (cached && cached > 0) {
counts.push(`${platform}: ${cached}`);
}
}

if (counts.length === 0) {
// Fall back to DB
const allStats = await ctx.db.list("chat_stats");
const userStats = allStats.filter((s) => s.user_id === ctx.user.id);
if (userStats.length === 0) {
return { reply: `${ctx.user.displayName}, no chat stats recorded yet!` };
}
for (const s of userStats) {
counts.push(`${s.platform}: ${s.message_count}`);
}
}

return { reply: `${ctx.user.displayName}'s chat stats: ${counts.join(" | ")}` };
});

// !leaderboard — Show top chatters across all platforms
export const leaderboard = command("leaderboard", async (ctx, args) => {
const size = (ctx.config.leaderboardSize as number) ?? 10;
const allStats = await ctx.db.list("chat_stats");

if (allStats.length === 0) {
return { reply: "No chat stats recorded yet!" };
}

// Aggregate by user across platforms
const totals: Record<string, { name: string; total: number }> = {};
for (const s of allStats) {
const uid = s.user_id as string;
if (!totals[uid]) {
totals[uid] = { name: s.user_name as string, total: 0 };
}
totals[uid].total += (s.message_count as number) ?? 0;
}

const sorted = Object.values(totals)
.sort((a, b) => b.total - a.total)
.slice(0, size);

const list = sorted
.map((t, i) => `${i + 1}. ${t.name}: ${t.total}`)
.join(" | ");

return { reply: `Chat leaderboard: ${list}` };
});

// !platformstats — Show message count breakdown by platform
export const platformstats = command("platformstats", async (ctx, args) => {
const platforms = ["twitch", "youtube", "kick"];
const counts: string[] = [];

for (const platform of platforms) {
const total = ((await ctx.cache.get(`total:${platform}`)) as number) ?? 0;
if (total > 0) {
counts.push(`${platform}: ${total}`);
}
}

const totalAll = ((await ctx.cache.get("total:all")) as number) ?? 0;

if (totalAll === 0) {
return { reply: "No messages tracked yet!" };
}

return { reply: `Platform stats (${totalAll} total): ${counts.join(" | ")}` };
});

// Timer — periodic stats summary every 15 minutes
export const statsSummary = timer("statsSummary", async (ctx) => {
const totalAll = ((await ctx.cache.get("total:all")) as number) ?? 0;

if (totalAll === 0) {
return null; // No messages tracked yet, skip this cycle
}

const platforms = ["twitch", "youtube", "kick"];
const counts: string[] = [];

for (const platform of platforms) {
const total = ((await ctx.cache.get(`total:${platform}`)) as number) ?? 0;
if (total > 0) {
counts.push(`${platform}: ${total}`);
}
}

return {
reply: `Chat stats update: ${totalAll} messages across ${counts.length} platform(s) (${counts.join(", ")})`,
};
});

Key concepts demonstrated

  • Multi-platform supportplatforms: ["twitch", "youtube", "kick"] receives messages from all three platforms
  • Platform-specific chat events — separate handlers for twitch:chat, youtube:chat, and kick:chat
  • Cross-platform aggregation — unified leaderboard combines message counts from all platforms
  • Per-platform cache countersctx.cache.increment() tracks totals per platform and per user-platform pair
  • Timer summary — periodic chat stats broadcast to all connected platforms
  • Shared helper functiontrackMessage() centralizes tracking logic for all platforms
  • Cache-first reads!stats and !platformstats read from cache for instant responses
  • Background DB persistencectx.defer() writes to the database without blocking the event handler