diff --git a/services/arbiter-3.0/src/routes/stripe.js b/services/arbiter-3.0/src/routes/stripe.js index e15eec0..45bcb1c 100644 --- a/services/arbiter-3.0/src/routes/stripe.js +++ b/services/arbiter-3.0/src/routes/stripe.js @@ -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; diff --git a/services/arbiter-3.0/src/services/awakenedConcierge.js b/services/arbiter-3.0/src/services/awakenedConcierge.js new file mode 100644 index 0000000..231f375 --- /dev/null +++ b/services/arbiter-3.0/src/services/awakenedConcierge.js @@ -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 };