mika-agent-assist/supabase/functions/managed-bot-webhook/index.ts
gpt-engineer-app[bot] a8ed0f79e8 Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
2026-04-26 20:24:43 +00:00

180 lines
5.6 KiB
TypeScript

// managed-bot-webhook
// Recebe updates do @mika_managerbot. Quando o update tem o campo "managed_bot",
// busca o token do novo bot via getManagedBotToken, salva no Vault, atualiza
// o agent_instance correspondente e dispara provisionamento automático.
//
// Setup do webhook: GET ?setup=true configura o webhook do manager bot.
//
// IMPORTANTE: Esta função usa endpoints experimentais do BotFather Bot
// Management Mode que NÃO são parte da Bot API pública oficial.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
function ack(): Response {
// Sempre 200 — Telegram não deve retentar.
return new Response("ok", { status: 200 });
}
function json(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
Deno.serve(async (req) => {
const url = new URL(req.url);
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
const managerToken = Deno.env.get("TELEGRAM_MANAGER_BOT_TOKEN");
// Setup: configura webhook
if (url.searchParams.get("setup") === "true") {
if (!managerToken) {
return json({ error: "TELEGRAM_MANAGER_BOT_TOKEN não configurado" }, 400);
}
const webhookUrl = `${supabaseUrl}/functions/v1/managed-bot-webhook`;
const res = await fetch(
`https://api.telegram.org/bot${managerToken}/setWebhook`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: webhookUrl,
allowed_updates: ["message", "managed_bot"],
}),
},
);
const data = await res.json().catch(() => ({}));
return json({ webhook_set: webhookUrl, telegram_response: data });
}
if (req.method !== "POST") {
return ack();
}
if (!managerToken) {
console.error("TELEGRAM_MANAGER_BOT_TOKEN ausente");
return ack();
}
let update: Record<string, unknown>;
try {
update = await req.json();
} catch {
return ack();
}
// Só processamos eventos de managed_bot
// deno-lint-ignore no-explicit-any
const managed = (update as any).managed_bot;
if (!managed || !managed.bot) {
return ack();
}
try {
const admin = createClient(supabaseUrl, serviceKey);
const botId = managed.bot.id;
const botUsername: string = managed.bot.username;
// deno-lint-ignore no-explicit-any
const tgUser = (managed as any).user ?? {};
// 1) Busca token do bot recém-criado
const tokenRes = await fetch(
`https://api.telegram.org/bot${managerToken}/getManagedBotToken`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bot_id: botId }),
},
);
const tokenJson = await tokenRes.json().catch(() => ({}));
// deno-lint-ignore no-explicit-any
const newBotToken: string | undefined = (tokenJson as any)?.result?.token;
if (!newBotToken) {
console.error("getManagedBotToken falhou", tokenJson);
return ack();
}
// 2) Localiza agent_instance pendente correspondente
const filter = tgUser?.id
? `telegram_user_chat_id.eq.${tgUser.id},managed_bot_suggested_username.eq.${botUsername}`
: `managed_bot_suggested_username.eq.${botUsername}`;
const { data: agentInstance, error: lookupErr } = await admin
.from("agent_instances")
.select("id, user_id")
.eq("managed_bot_pending", true)
.or(filter)
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
if (lookupErr || !agentInstance) {
console.error(
"Nenhum agent_instance pendente encontrado para managed bot",
{ botUsername, tgUserId: tgUser?.id, lookupErr },
);
return ack();
}
// 3) Salva token no Vault (reutiliza vault_create_secret)
const secretName = `telegram_bot_token_${agentInstance.id}_${Math.floor(
Date.now() / 1000,
)}`;
const { data: vaultData, error: vaultErr } = await admin
.rpc("vault_create_secret", {
secret_value: newBotToken,
secret_name: secretName,
secret_description: `Managed bot token for agent ${agentInstance.id}`,
})
.single();
if (vaultErr || !vaultData) {
console.error("vault_create_secret falhou", vaultErr);
return ack();
}
// deno-lint-ignore no-explicit-any
const secretId = (vaultData as any).secret_id ?? vaultData;
// 4) Atualiza agent_instance
const { error: updErr } = await admin
.from("agent_instances")
.update({
telegram_bot_token_vault_id: secretId,
telegram_bot_username: botUsername,
telegram_user_chat_id: tgUser?.id ? Number(tgUser.id) : null,
telegram_connected_at: new Date().toISOString(),
telegram_onboarding_completed: true,
telegram_token_invalid: false,
managed_bot_pending: false,
onboarding_completed: true,
updated_at: new Date().toISOString(),
})
.eq("id", agentInstance.id);
if (updErr) {
console.error("agent update falhou", updErr);
return ack();
}
// 5) Dispara provisionamento (fire-and-forget)
fetch(`${supabaseUrl}/functions/v1/provision-agent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${serviceKey}`,
},
body: JSON.stringify({
agent_instance_id: agentInstance.id,
user_id: agentInstance.user_id,
}),
}).catch((err) => console.error("Auto-provision error:", err));
return ack();
} catch (err) {
console.error("managed-bot-webhook fatal", err);
return ack();
}
});