feat: Awakened Concierge — personalized welcome bot (Task #130)
- New service: src/services/awakenedConcierge.js - Fetches Discord username via Discord API - Calls Dify Awakened Concierge app (Gemma 4) for personalized message - Posts to #introductions (1403981218252324884) with typing indicator - Marks welcomed_at in subscriptions table - Non-fatal: welcome failure never breaks checkout flow - stripe.js: calls welcomeNewMember() after syncRole() on checkout complete - .env: CONCIERGE_API_KEY added to Command Center Fire + Frost + Foundation 💙🔥❄️
This commit is contained in:
@@ -12,6 +12,7 @@ const cors = require('cors');
|
||||
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||||
const db = require('../database');
|
||||
const { syncRole, removeAllRoles, downgradeToAwakened } = require('../services/discordRoleSync');
|
||||
const { welcomeNewMember } = require('../services/awakenedConcierge');
|
||||
|
||||
// CORS configuration for checkout endpoint
|
||||
const corsOptions = {
|
||||
@@ -280,6 +281,11 @@ router.post('/webhook', express.raw({ type: 'application/json' }), async (req, r
|
||||
if (discordId && tierLevel) {
|
||||
const roleResult = await syncRole(discordId, tierLevel);
|
||||
console.log(`🎭 Role sync for ${discordId}: ${roleResult.message}`);
|
||||
|
||||
// Welcome new member via Awakened Concierge (non-blocking)
|
||||
welcomeNewMember(discordId, client).catch(err =>
|
||||
console.error('[Concierge] Background welcome error:', err.message)
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
144
services/arbiter-3.0/src/services/awakenedConcierge.js
Normal file
144
services/arbiter-3.0/src/services/awakenedConcierge.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Awakened Concierge Service
|
||||
* Generates personalized welcome messages for new subscribers via Dify/Gemma 4
|
||||
* Posts to #introductions channel on Discord
|
||||
*
|
||||
* Task #130: Awakened Concierge — Personalized Welcome Bot
|
||||
* Date: April 12, 2026
|
||||
*/
|
||||
|
||||
const DIFY_BASE_URL = process.env.DIFY_API_URL || 'https://codex.firefrostgaming.com';
|
||||
const CONCIERGE_API_KEY = process.env.CONCIERGE_API_KEY;
|
||||
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN;
|
||||
const INTRODUCTIONS_CHANNEL_ID = '1403981218252324884';
|
||||
|
||||
/**
|
||||
* Get Discord username for a Discord ID
|
||||
*/
|
||||
async function getDiscordUsername(discordId) {
|
||||
try {
|
||||
const response = await fetch(`https://discord.com/api/v10/users/${discordId}`, {
|
||||
headers: { Authorization: `Bot ${DISCORD_BOT_TOKEN}` }
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const user = await response.json();
|
||||
return user.global_name || user.username || null;
|
||||
} catch (err) {
|
||||
console.error(`[Concierge] Failed to fetch Discord user ${discordId}:`, err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate welcome message via Dify Awakened Concierge app
|
||||
*/
|
||||
async function generateWelcomeMessage(username) {
|
||||
try {
|
||||
const response = await fetch(`${DIFY_BASE_URL}/v1/chat-messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${CONCIERGE_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
inputs: {},
|
||||
query: `New member username: ${username}`,
|
||||
response_mode: 'blocking',
|
||||
user: `concierge-${Date.now()}`
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`[Concierge] Dify API error: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.answer || null;
|
||||
} catch (err) {
|
||||
console.error('[Concierge] Failed to generate welcome message:', err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post welcome message to #introductions with typing indicator
|
||||
*/
|
||||
async function postWelcomeToDiscord(message) {
|
||||
try {
|
||||
// Trigger typing indicator for natural feel
|
||||
await fetch(`https://discord.com/api/v10/channels/${INTRODUCTIONS_CHANNEL_ID}/typing`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bot ${DISCORD_BOT_TOKEN}` }
|
||||
});
|
||||
|
||||
// Small delay — feels like someone is actually typing
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// Post the message
|
||||
const response = await fetch(`https://discord.com/api/v10/channels/${INTRODUCTIONS_CHANNEL_ID}/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bot ${DISCORD_BOT_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ content: message })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`[Concierge] Discord post error: ${response.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('[Concierge] Failed to post to Discord:', err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point — welcome a new subscriber
|
||||
* Called from stripe.js after successful checkout
|
||||
*/
|
||||
async function welcomeNewMember(discordId, client) {
|
||||
if (!CONCIERGE_API_KEY) {
|
||||
console.log('[Concierge] CONCIERGE_API_KEY not set, skipping welcome');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get their Discord username
|
||||
const username = await getDiscordUsername(discordId);
|
||||
if (!username) {
|
||||
console.log(`[Concierge] Could not fetch username for ${discordId}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[Concierge] Generating welcome for ${username} (${discordId})`);
|
||||
|
||||
// Generate personalized message via Dify
|
||||
const message = await generateWelcomeMessage(username);
|
||||
if (!message) {
|
||||
console.log(`[Concierge] No message generated for ${username}, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Post to #introductions
|
||||
const posted = await postWelcomeToDiscord(message);
|
||||
if (posted) {
|
||||
console.log(`[Concierge] ✅ Welcome posted for ${username}`);
|
||||
|
||||
// Mark as welcomed in DB
|
||||
await client.query(
|
||||
'UPDATE subscriptions SET welcomed_at = CURRENT_TIMESTAMP WHERE discord_id = $1 AND welcomed_at IS NULL',
|
||||
[discordId]
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// Non-fatal — welcome failure should never break checkout
|
||||
console.error('[Concierge] Unexpected error:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { welcomeNewMember };
|
||||
Reference in New Issue
Block a user