HuwiDevelopers

Ejemplos

Ejemplos listos para copiar en curl, TypeScript y Python. Novedades, mensajes, tarjetas con botones, comandos, webhooks, publicar, grupos y texto en vivo.

Todos los ejemplos de esta página se corrieron contra Huwi con un bot real. Para los de TypeScript y Python necesitas huwi.ts o huwi.py, que están en Primeros pasos. Antes de correrlos:

export HUWI_KEY="hwk_tu_clave"      # la clave de tu bot
export HUWI_URL="https://huwi.app"  # opcional: es el valor por defecto
  • TypeScript: Node 22.6 o más con node --experimental-strip-types archivo.ts, o con tsx.
  • Python: Python 3 con pip install requests.

Quién soy (bot.me)

curl -s "https://huwi.app/api/trpc/bot.me" \
  -H "x-api-key: $HUWI_KEY"

Pedir novedades y confirmar (bot.updates)

bot.updates espera hasta 25 segundos. Para confirmar, vuelve a pedir con offset igual al id de la última novedad más uno: lo anterior se da por recibido.

# Pedido largo: espera hasta 25 s a que haya novedades.
curl -s -G "https://huwi.app/api/trpc/bot.updates" \
  -H "x-api-key: $HUWI_KEY" \
  --data-urlencode 'input={"json":{"timeout":25000}}'

# Confirmar: vuelve a pedir con offset = id de la última novedad + 1.
curl -s -G "https://huwi.app/api/trpc/bot.updates" \
  -H "x-api-key: $HUWI_KEY" \
  --data-urlencode 'input={"json":{"offset":"107652223732637697","timeout":25000}}'

El offset es el id más uno

Si pasas el id de la última novedad tal cual, Huwi te la entrega otra vez. Los ids son números de 18 dígitos: en JavaScript sácales el más uno con BigInt (no con Number).

Mandar un mensaje de texto

curl -s "https://huwi.app/api/trpc/message.send" \
  -H "x-api-key: $HUWI_KEY" \
  -H "content-type: application/json" \
  -d '{"json":{"chatId":"107652219945181184","clientId":"saludo-1","text":"Hola desde curl"}}'

El clientId hace el envío idempotente: si tu programa reintenta con el mismo clientId, Huwi devuelve el mismo mensaje en lugar de mandar otro. Usa un id que salga de la novedad (eco-${update.id}) cuando contestes algo.

Una tarjeta con botones y el toque

Manda la tarjeta con components. El toque llega como novedad action; contesta con un mensaje y, si quieres, bot.answerAction.

curl -s "https://huwi.app/api/trpc/message.send" \
  -H "x-api-key: $HUWI_KEY" -H "content-type: application/json" \
  -d '{"json":{"chatId":"107652219945181184","clientId":"pedido-1042","components":{"v":1,"body":[
    {"type":"card","title":"Pedido #1042","subtitle":"En camino"},
    {"type":"actions","actions":[{"id":"track","label":"Rastrear","kind":"callback"}]}
  ]}}}'

Los componentes están en la galería.

Comandos y /start

Guarda los comandos una vez y atiende /start (con su carga útil) y /help.

curl -s "https://huwi.app/api/trpc/bot.setCommands" \
  -H "x-api-key: $HUWI_KEY" -H "content-type: application/json" \
  -d '{"json":{"commands":[{"command":"help","description":"Qué puedo hacer"},{"command":"pedido","description":"Ver el estado de mi pedido"}]}}'

Recibir por webhook

Poner el webhook y un receptor que verifica Huwi-Signature. Mira Webhooks para el detalle del algoritmo.

# Poner el webhook. Devuelve {"secret":"whsec_..."}: se ve una sola vez, guárdalo.
curl -s "https://huwi.app/api/trpc/bot.setWebhook" \
  -H "x-api-key: $HUWI_KEY" -H "content-type: application/json" \
  -d '{"json":{"url":"https://mi-bot.example.com/huwi"}}'

# Ver si está puesto y desde cuándo falla.
curl -s "https://huwi.app/api/trpc/bot.webhookInfo" -H "x-api-key: $HUWI_KEY"

# Quitarlo y volver a bot.updates.
curl -s -X POST "https://huwi.app/api/trpc/bot.deleteWebhook" -H "x-api-key: $HUWI_KEY"

Publicar

post.create publica en la cuenta del bot (necesita el permiso post). Una foto se sube primero: upload.create te da dónde, la subes con PUT y usas la url que devolvió. Hay un tope de 50 publicaciones por día.

# Publicación de solo texto.
curl -s "https://huwi.app/api/trpc/post.create" \
  -H "x-api-key: $HUWI_KEY" -H "content-type: application/json" \
  -d '{"json":{"media":{"type":"text","background":"none"},"caption":"Hola desde un bot #huwibots"}}'

Un video se sube igual, con contentType: "video/mp4", y media lleva url, posterUrl, width, height y durationSec; prepáralo como lo hace el celular (H.264, audio AAC e índice al principio).

En un grupo

import { mutate, query } from "./huwi.ts";

// En un grupo, las novedades traen el `chatId` del grupo. Con "solo menciones, comandos y respuestas",
// el bot recibe únicamente lo que le habla a él; con "todo", todos los mensajes.
const me = await query("bot.me");

async function handle(update: any) {
  if (update.type !== "message") return;
  const { text, command } = update.payload as { text: string; command?: string };
  const mentioned = text.toLowerCase().includes(`@${me.username}`);
  if (command === "help" || mentioned) {
    // En un grupo el límite es de 20 mensajes por minuto, sin turnos.
    await mutate("message.send", { chatId: update.chatId, clientId: `g-${update.id}`, text: "Aquí estoy. Escribe /help para ver qué hago." });
  }
}

let offset: string | undefined;
while (true) {
  const { updates } = await query("bot.updates", { offset, timeout: 25000 });
  for (const update of updates) {
    await handle(update);
    offset = String(BigInt(update.id) + 1n);
  }
}

Texto que se va escribiendo

Para una respuesta que llega de a poco (por ejemplo, la de un modelo), manda un mensaje y edítalo a medida que llega el texto.

import { mutate } from "./huwi.ts";

// Texto que se va escribiendo (por ejemplo, la respuesta de un modelo): se manda un mensaje y se edita.
async function* model(): AsyncGenerator<string> {
  for (const word of "Claro, te cuento cómo va tu pedido: sale hoy y llega en la tarde.".split(" ")) {
    await new Promise((resolve) => setTimeout(resolve, 150));
    yield `${word} `;
  }
}

const chatId = process.env.CHAT_ID!;
const message = await mutate("message.send", { chatId, clientId: crypto.randomUUID(), text: "…" });

let text = "";
let last = 0;
for await (const piece of model()) {
  text += piece;
  // Edita como mucho cada ~700 ms: cada edición es un pedido más y llega a la persona en vivo.
  if (Date.now() - last > 700) {
    await mutate("message.edit", { messageId: message.id, text });
    last = Date.now();
  }
}
await mutate("message.edit", { messageId: message.id, text: text.trim() }); // la versión final

bot.startStream, appendStream y endStream

Existen en la API para escribir un mensaje en vivo, pero hoy, con la base de datos real, bot.startStream responde BAD_REQUEST con use-message. Usa message.send y message.edit, como arriba.

Manejar errores

Cuando algo falla, la API responde con un código HTTP y un cuerpo así:

{ "error": { "json": { "message": "botRate", "code": -32029, "data": { "code": "TOO_MANY_REQUESTS", "httpStatus": 429 } } } }

Lee data.code y message (las tablas están en Límites y errores). Ante un TOO_MANY_REQUESTS, espera un poco y reintenta con el mismo clientId. Los ejemplos de huwi.ts y huwi.py lanzan un HuwiError con code y status:

import { HuwiError, mutate } from "./huwi.ts";

try {
  await mutate("message.send", { chatId, clientId: "x1", text: "Hola" });
} catch (error) {
  if (error instanceof HuwiError && error.code === "TOO_MANY_REQUESTS") {
    await new Promise((resolve) => setTimeout(resolve, 3000)); // espera y vuelve a intentar
  } else {
    throw error;
  }
}

En esta página