Clip Logger Bot Module
A bot module that uses regex pattern matching to detect Twitch clip URLs in chat, stores them in the extension database, and provides a !clips command to display recent clips.
Project structure
clip-logger/
├── 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": "Clip Logger",
"category": "bot_module",
"version": "1.0.0",
"platforms": ["twitch"],
"server": true,
"permissions": ["chat:send", "chat:read"],
"triggers": {
"commands": [
{
"name": "clips",
"description": "Show the most recent clips shared in chat",
"cooldown_global": 10,
"cooldown_user": 30,
"min_role": "everyone"
},
{
"name": "clipcount",
"description": "Show total number of clips logged",
"cooldown_global": 5,
"cooldown_user": 15,
"min_role": "everyone"
}
],
"patterns": [
"https?://clips\\.twitch\\.tv/\\S+",
"https?://www\\.twitch\\.tv/\\S+/clip/\\S+"
]
},
"config_schema": [
{
"key": "maxClipsShown",
"type": "number",
"label": "Number of clips to show with !clips",
"default_value": 5,
"required": true
},
{
"key": "announceClips",
"type": "boolean",
"label": "Announce when a clip is detected",
"default_value": true,
"required": true
}
]
}
server/schema.ts
import { defineSchema, defineTable, column } from "@zaflun/lumio-sdk/server";
export default defineSchema({
clips: defineTable({
url: column.string(),
shared_by: column.string(),
shared_by_name: column.string(),
platform: column.string(),
timestamp: column.number(),
}),
});
server/functions.ts
import { command, pattern } from "@zaflun/lumio-sdk/server";
// Pattern handler — fires when a Twitch clip URL is detected
export const clipDetector = pattern(
"https?://clips\\.twitch\\.tv/\\S+",
async (ctx, message, matched) => {
// Check for duplicate (same URL in the last hour)
const recentClips = await ctx.db.list("clips");
const oneHourAgo = Date.now() - 3600000;
const isDuplicate = recentClips.some(
(clip) => clip.url === matched && (clip.timestamp as number) > oneHourAgo
);
if (isDuplicate) {
return null; // Silently skip duplicates
}
// Store the clip
ctx.defer(async () => {
await ctx.db.insert("clips", {
url: matched,
shared_by: ctx.user.id,
shared_by_name: ctx.user.displayName,
platform: ctx.user.platform,
timestamp: Date.now(),
});
});
// Increment clip counter in cache
await ctx.cache.increment("clip_total", 1);
const announceClips = (ctx.config.announceClips as boolean) ?? true;
if (announceClips) {
return { reply: `Clip logged from ${ctx.user.displayName}!` };
}
return null;
}
);
// Also catch the alternate Twitch clip URL format
export const clipDetectorAlt = pattern(
"https?://www\\.twitch\\.tv/\\S+/clip/\\S+",
async (ctx, message, matched) => {
ctx.defer(async () => {
await ctx.db.insert("clips", {
url: matched,
shared_by: ctx.user.id,
shared_by_name: ctx.user.displayName,
platform: ctx.user.platform,
timestamp: Date.now(),
});
});
await ctx.cache.increment("clip_total", 1);
const announceClips = (ctx.config.announceClips as boolean) ?? true;
if (announceClips) {
return { reply: `Clip logged from ${ctx.user.displayName}!` };
}
return null;
}
);
// !clips — Show recent clips
export const clips = command("clips", async (ctx, args) => {
const maxShown = (ctx.config.maxClipsShown as number) ?? 5;
const allClips = await ctx.db.list("clips");
if (allClips.length === 0) {
return { reply: "No clips have been shared yet!" };
}
// Sort by timestamp descending and take the most recent
const recent = allClips
.sort((a, b) => (b.timestamp as number) - (a.timestamp as number))
.slice(0, maxShown);
const clipList = recent
.map((clip, i) => `${i + 1}. ${clip.url} (by ${clip.shared_by_name})`)
.join(" | ");
return { reply: `Recent clips: ${clipList}` };
});
// !clipcount — Show total clips logged
export const clipcount = command("clipcount", async (ctx, args) => {
const total = (await ctx.cache.get("clip_total")) as number | null;
if (total === null) {
// Cache miss — count from DB
const allClips = await ctx.db.list("clips");
await ctx.cache.set("clip_total", allClips.length, 3600);
return { reply: `Total clips logged: ${allClips.length}` };
}
return { reply: `Total clips logged: ${total}` };
});
Key concepts demonstrated
- Regex pattern triggers — two patterns catch both Twitch clip URL formats
matchedparameter — the handler receives the exact URL that matched the pattern- Duplicate detection — checks recent clips to avoid logging the same URL twice
- Background persistence —
ctx.defer()stores clips in the database after responding - Cache counters —
ctx.cache.increment()for fast total clip counts - Config-driven announcements — the
announceClipsconfig lets users toggle clip detection messages