← back to Norma
agents/twitch-agent/skills/post.js
123 lines
/**
* Twitch Post Skill
*
* Sends a chat message to a Twitch channel.
* Can accept a pre-written message or generate one via AI.
* Messages are concise for Twitch chat format (max 500 chars).
*
* Rate limit: 20/30sec (shared with reply)
*/
const { RateLimiter } = require('../../shared/rate-limiter');
const { logAction } = require('../../shared/audit-logger');
const { generateContent } = require('../../shared/ai');
const { sendMessage, initChat } = require('../lib/twitch');
const AGENT = 'twitch-agent';
const PLATFORM = 'twitch';
const rateLimiter = new RateLimiter(AGENT, PLATFORM);
/**
* @param {Object} body - Request body
* @param {string} body.channel - Twitch channel name (required)
* @param {string} [body.message] - Message to send (auto-generated if not provided)
* @param {string} [body.topic='civic advocacy'] - Topic for AI generation
* @param {string} [body.tone='casual'] - Tone: 'casual', 'informative', 'empathetic'
* @returns {Promise<Object>}
*/
module.exports = async function post(body) {
const channel = body.channel;
if (!channel) {
throw new Error('channel is required');
}
// Check rate limit
const limit = await rateLimiter.checkLimit('post');
if (!limit.allowed) {
await logAction({
agent: AGENT,
actionType: 'post',
platform: PLATFORM,
channel,
status: 'rate_limited',
errorMessage: `Rate limited. Retry after ${Math.ceil(limit.retryAfterMs / 1000)}s`,
});
return {
rate_limited: true,
retry_after_seconds: Math.ceil(limit.retryAfterMs / 1000),
current: limit.current,
max: limit.max,
};
}
// Initialize chat client
await initChat();
let message = body.message;
let aiGenerated = false;
// Generate message via AI if not provided
if (!message) {
const topic = body.topic || 'civic advocacy';
const tone = body.tone || 'casual';
const prompt = `You are a community member in a Twitch chat. Write a single chat message about ${topic}.
TONE: ${tone}
MAX LENGTH: 200 characters (Twitch chat is fast-paced, keep it short)
RULES:
- Sound like a real person chatting, not a bot
- No hashtags (this is Twitch, not Twitter)
- No links (Twitch chat filters them)
- Be engaging and invite discussion
- Can reference current events about advocacy or civic engagement
- One sentence or two short ones max
Respond with ONLY the chat message text, nothing else.`;
try {
message = await generateContent(prompt, { maxTokens: 100, temperature: 0.9 });
message = message.trim().replace(/^["']|["']$/g, ''); // Strip quotes if AI wraps in them
aiGenerated = true;
} catch (err) {
console.error(`[${AGENT}] AI generation failed:`, err.message);
// Fallback message
message = 'Anyone else passionate about civic advocacy? Would love to hear what causes matter to you.';
aiGenerated = true;
}
}
// Send the message
const result = await sendMessage(channel, message);
// Record the action
await rateLimiter.recordAction('post');
await logAction({
agent: AGENT,
actionType: 'post',
platform: PLATFORM,
channel,
content: message,
targetUrl: `https://twitch.tv/${channel.replace('#', '')}`,
responseData: {
sent: result.sent,
simulated: result.simulated,
aiGenerated,
messageLength: message.length,
},
status: 'success',
});
return {
channel: result.channel,
message: result.message,
sent: result.sent,
simulated: result.simulated,
ai_generated: aiGenerated,
posted_at: new Date().toISOString(),
};
};