Kick Follow Bot Module
A Kick-only bot module that announces new followers and tracks follow streaks using ctx.cache for fast counter access and ctx.defer() for background database persistence.
Project structure
kick-follow-bot/
├── 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": "Kick Follow Bot",
"category": "bot_module",
"version": "1.0.0",
"platforms": ["kick"],
"server": true,
"permissions": ["chat:send", "events:read"],
"triggers": {
"commands": [
{
"name": "followers",
"description": "Show the follow streak count for this stream",
"cooldown_global": 10,
"cooldown_user": 30,
"min_role": "everyone"
},
{
"name": "recentfollows",
"description": "Show the most recent followers",
"cooldown_global": 10,
"cooldown_user": 30,
"min_role": "everyone"
}
],
"events": ["kick:follower"]
},
"config_schema": [
{
"key": "announceFollows",
"type": "boolean",
"label": "Announce new followers in chat",
"default_value": true,
"required": true
},
{
"key": "streakMilestone",
"type": "number",
"label": "Follow streak milestone for special message",
"default_value": 10,
"required": true
},
{
"key": "welcomeEmote",
"type": "string",
"label": "Emote to include in welcome message",
"default_value": "",
"required": false
}
]
}
server/schema.ts
import { defineSchema, defineTable, column } from "@zaflun/lumio-sdk/server";
export default defineSchema({
followers: defineTable({
user_id: column.string(),
user_name: column.string(),
followed_at: column.number(),
stream_session: column.string(),
}),
});
server/functions.ts
import { command, event } from "@zaflun/lumio-sdk/server";
// Event handler — new Kick follower
export const followHandler = event("kick:follower", async (ctx, evt) => {
const userName = evt.user_name as string;
// Increment the follow streak counter
const streakCount = await ctx.cache.increment("follow_streak", 1);
// Store follower record in background
ctx.defer(async () => {
await ctx.db.insert("followers", {
user_id: evt.user_id,
user_name: userName,
followed_at: Date.now(),
stream_session: await getSessionId(ctx),
});
});
const announceFollows = (ctx.config.announceFollows as boolean) ?? true;
if (!announceFollows) {
return null;
}
const emote = (ctx.config.welcomeEmote as string) ?? "";
const emotePart = emote ? ` ${emote}` : "";
const milestone = (ctx.config.streakMilestone as number) ?? 10;
// Check for milestone
if (streakCount > 0 && streakCount % milestone === 0) {
return {
reply: `${streakCount} follow streak! Welcome ${userName}!${emotePart} We've hit ${streakCount} new followers this stream!`,
};
}
return {
reply: `Welcome ${userName}!${emotePart} (Follow #${streakCount} this stream)`,
};
});
// !followers — Show the current follow streak count
export const followers = command("followers", async (ctx, args) => {
const streakCount = ((await ctx.cache.get("follow_streak")) as number) ?? 0;
if (streakCount === 0) {
return { reply: "No new followers this stream yet!" };
}
return { reply: `${streakCount} new follower(s) this stream!` };
});
// !recentfollows — Show the most recent followers
export const recentfollows = command("recentfollows", async (ctx, args) => {
const allFollowers = await ctx.db.list("followers");
if (allFollowers.length === 0) {
return { reply: "No followers recorded yet!" };
}
// Sort by timestamp descending, take last 5
const recent = allFollowers
.sort((a, b) => (b.followed_at as number) - (a.followed_at as number))
.slice(0, 5);
const names = recent.map((f) => f.user_name).join(", ");
return { reply: `Recent followers: ${names}` };
});
// Helper: get or create a session ID for this stream
async function getSessionId(ctx: any): Promise<string> {
let sessionId = (await ctx.cache.get("session_id")) as string | null;
if (!sessionId) {
sessionId = `session_${Date.now()}`;
// 12-hour TTL — covers a long stream
await ctx.cache.set("session_id", sessionId, 43200);
}
return sessionId;
}
Key concepts demonstrated
- Kick-specific events —
kick:followerfires when a new viewer follows the Kick channel - Single-platform module —
platforms: ["kick"]restricts this to Kick only - Follow streak tracking —
ctx.cache.increment()counts new followers during the stream session - Milestone announcements — special messages at configurable streak intervals (every 10, 25, 50, etc.)
- Session-scoped data — cache keys with 12-hour TTL naturally reset between streams
- Background persistence —
ctx.defer()stores follower records in the database while keeping the event handler fast - Config-driven behavior — announce toggle, milestone interval, and custom emote are all configurable