Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e4b74e1
Updated documentation for chat-members plugin, adding new sections ab…
rayz1065 Jan 22, 2025
639036a
Improve documentation of chat-members based on suggestions
rayz1065 Jan 24, 2025
7032e84
Add diagram to chat-members plugin, showing the statuses correspondin…
rayz1065 Jan 24, 2025
4d41b1a
Merge branch 'main' into chat-member-filters
LWJerri Jan 27, 2025
8abdf8e
Updated chat-members documentation, adding section about hydration
rayz1065 Feb 6, 2025
808938c
Apply suggestions from code review
rayz1065 Feb 14, 2025
c72c38b
Merge branch 'main' into chat-member-filters
LWJerri Feb 17, 2025
aa44ba2
Apply suggestions from code review
rayz1065 Feb 19, 2025
1dec670
Chat members plugin, include DEFAULT_UPDATE_TYPES where they were mis…
rayz1065 Feb 19, 2025
69159ff
Chat members, added example showcasing filtering without the plugin
rayz1065 Feb 19, 2025
4e9b995
Chat members, apply suggested rephrasing from code review
rayz1065 Feb 19, 2025
73149ad
Merge branch 'main' into chat-member-filters
rojvv Mar 31, 2025
5a9b127
Merge branch 'main' into chat-member-filters
MasedMSD Apr 12, 2025
370b223
sync to Ukrainian
niusia-ua Apr 12, 2025
57a1f81
Apply suggestions from code review
rayz1065 Apr 12, 2025
50c14bd
Chat members, clean up string composition
rayz1065 Apr 12, 2025
6a0d5ea
[uk]: correct apostrophes
niusia-ua Apr 13, 2025
d4d02e8
Apply suggestions from code review
LWJerri Apr 13, 2025
9741d77
Merge branch 'main' into chat-member-filters
LWJerri Apr 19, 2025
be00ce1
sync with indonesia
ppabcd Apr 21, 2025
b2d61d6
Merge branch 'main' into chat-member-filters
LWJerri May 5, 2025
cfd8b31
Add Spanish
habemuscode Jun 21, 2025
1af712d
Remove non-needed spaces
habemuscode Jun 21, 2025
c3c0050
Merge branch 'main' into chat-member-filters
LWJerri Jul 17, 2025
4a6be97
Merge branch 'main' into chat-member-filters
MasedMSD Aug 16, 2025
93f9788
Merge branch 'main' into chat-member-filters
LWJerri Aug 23, 2025
db0f788
sync with bahasa indonesia
ppabcd Sep 3, 2025
c9d5cd7
Merge branch 'main' into chat-member-filters
LWJerri Sep 3, 2025
4bd0dbe
Merge branch 'main' into chat-member-filters
LWJerri Sep 12, 2025
da78d08
Merge branch 'main' into chat-member-filters
LWJerri Oct 12, 2025
c67e2ba
sync zh #1181
Nov 3, 2025
b2a4c48
Merge branch 'main' into chat-member-filters
KnorpelSenf Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
347 changes: 334 additions & 13 deletions site/docs/es/plugins/chat-members.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,338 @@ next: false

# Plugin para miembros del chat (`chat-members`)

Almacena automáticamente información sobre los usuarios de un chat y recupérala fácilmente.
Rastrea miembros de grupos y canales, y enlístalos.
Telegram no ofrece un método en la API del Bot para recuperar los miembros de un chat, tienes que seguirles la pista tú mismo.
Este plugin facilita el trabajo con objetos `ChatMember`, ofreciendo una forma cómoda de escuchar los cambios en forma de filtros personalizados, y almacenando y actualizando los objetos.

## Introducción

En muchas situaciones, es necesario que un bot tenga información sobre todos los usuarios de un determinado chat.
Actualmente, sin embargo, la API de Telegram Bot no expone ningún método que nos permita recuperar esta información.
Trabajar con objetos `ChatMember` de la API de Telegram Bot a veces puede ser engorroso.
Hay varios estados diferentes que suelen ser intercambiables en la mayoría de las aplicaciones.
Además, el estado restringido es ambiguo porque puede representar tanto a miembros del grupo como a usuarios restringidos que no están en el grupo.

Este plugin viene al rescate: escucha automáticamente los eventos `chat_member` y almacena todos los objetos `ChatMember`.
Este complemento simplifica el tratamiento de los miembros del chat al ofrecer filtros de escritura fuerte para las actualizaciones de los miembros del chat.

## Uso

### Filtros de miembros del chat

Puedes escuchar dos tipos de actualizaciones relacionadas con los miembros del chat usando un bot de Telegram: `chat_member` y `my_chat_member`.
Ambos especifican el estado antiguo y nuevo del usuario.

- Las actualizaciones de `my_chat_member` son siempre recibidas por tu bot para informarte sobre el estado del bot siendo actualizado en cualquier chat, así como cuando los usuarios bloquean al bot.
- Las actualizaciones de `chat_member` sólo se reciben si las incluyes explícitamente en la lista de actualizaciones permitidas, notifican sobre cualquier cambio de estado de los usuarios en los chats en los que el bot es **admin**.

En lugar de filtrar manualmente los estados antiguos y los nuevos, los filtros de miembros de chat lo hacen automáticamente por ti, permitiéndote actuar sobre cualquier tipo de transición que te interese.
Dentro del manejador, los tipos de `old_chat_member` y `new_chat_member` se reducen en consecuencia.

::: code-group

```ts [TypeScript]
import { API_CONSTANTS, Bot } from "grammy";
import { chatMemberFilter, myChatMemberFilter } from "@grammyjs/chat-members";

const bot = new Bot("");
const groups = bot.chatType(["group", "supergroup"]);

// SIN este plugin, para reaccionar cada vez que un usuario se une a un grupo, tienes que
// filtrar manualmente por estado, resultando en código propenso a errores y difícil de leer.
groups.on("chat_member").filter(
(ctx) => {
const { old_chat_member: oldMember, new_chat_member: newMember } =
ctx.chatMember;
return (
(["kicked", "left"].includes(oldMember.status) ||
(oldMember.status === "restricted" && !oldMember.is_member)) &&
(["administrator", "creator", "member"].includes(newMember.status) ||
(newMember.status === "restricted" && newMember.is_member))
);
},
(ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
},
);

// CON este plugin, el código se simplifica enormemente y tiene un menor riesgo de errores.
// El código de abajo escucha los mismos eventos pero es mucho más simple.
groups.filter(chatMemberFilter("out", "in"), async (ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como usuario normal.
groups.filter(myChatMemberFilter("out", "regular"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group!");
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como administrador.
groups.filter(myChatMemberFilter("out", "admin"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group as admin!");
});

// Escucha las actualizaciones en las que el bot es ascendido a admin.
groups.filter(myChatMemberFilter("regular", "admin"), async (ctx) => {
await ctx.reply("I was promoted to admin!");
});

// Escucha las actualizaciones en las que el bot es degradado a usuario normal.
groups.filter(myChatMemberFilter("admin", "regular"), async (ctx) => {
await ctx.reply("I am no longer admin");
});

bot.start({
/// Asegúrate de incluir el tipo de actualización "chat_member" para que los manejadores anteriores funcionen.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

```js [JavaScript]
import { API_CONSTANTS, Bot } from "grammy";
import { chatMemberFilter, myChatMemberFilter } from "@grammyjs/chat-members";

const bot = new Bot("");
const groups = bot.chatType(["group", "supergroup"]);

// SIN este plugin, para reaccionar cada vez que un usuario se une a un grupo, tienes que
// filtrar manualmente por estado, resultando en código propenso a errores y difícil de leer.
groups.on("chat_member").filter(
(ctx) => {
const { old_chat_member: oldMember, new_chat_member: newMember } =
ctx.chatMember;
return (
(["kicked", "left"].includes(oldMember.status) ||
(oldMember.status === "restricted" && !oldMember.is_member)) &&
(["administrator", "creator", "member"].includes(newMember.status) ||
(newMember.status === "restricted" && newMember.is_member))
);
},
(ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
},
);

// CON este plugin, el código se simplifica enormemente y tiene un menor riesgo de errores.
// El código de abajo escucha los mismos eventos pero es mucho más simple.
groups.filter(chatMemberFilter("out", "in"), async (ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como usuario normal.
groups.filter(myChatMemberFilter("out", "regular"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group!");
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como administrador.
groups.filter(myChatMemberFilter("out", "admin"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group as admin!");
});

// Escucha las actualizaciones en las que el bot es ascendido a admin.
groups.filter(myChatMemberFilter("regular", "admin"), async (ctx) => {
await ctx.reply("I was promoted to admin!");
});

// Escucha las actualizaciones en las que el bot es degradado a usuario normal.
groups.filter(myChatMemberFilter("admin", "regular"), async (ctx) => {
await ctx.reply("I am no longer admin");
});

bot.start({
// Asegúrate de incluir el tipo de actualización "chat_member" para que los manejadores anteriores funcionen.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

```ts [Deno]
import { API_CONSTANTS, Bot } from "https://deno.land/x/grammy/mod.ts";
import {
chatMemberFilter,
myChatMemberFilter,
} from "https://deno.land/x/grammy_chat_members/mod.ts";

const bot = new Bot("");
const groups = bot.chatType(["group", "supergroup"]);

// SIN este plugin, para reaccionar cada vez que un usuario se une a un grupo, tienes que
// filtrar manualmente por estado, resultando en código propenso a errores y difícil de leer.
groups.on("chat_member").filter(
(ctx) => {
const { old_chat_member: oldMember, new_chat_member: newMember } =
ctx.chatMember;
return (
(["kicked", "left"].includes(oldMember.status) ||
(oldMember.status === "restricted" && !oldMember.is_member)) &&
(["administrator", "creator", "member"].includes(newMember.status) ||
(newMember.status === "restricted" && newMember.is_member))
);
},
(ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
},
);

// CON este plugin, el código se simplifica enormemente y tiene un menor riesgo de errores.
// El código de abajo escucha los mismos eventos pero es mucho más simple.
groups.filter(chatMemberFilter("out", "in"), async (ctx) => {
const user = ctx.chatMember.new_chat_member.user;
await ctx.reply(`Welcome ${user.first_name} to the group!`);
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como usuario normal.
groups.filter(myChatMemberFilter("out", "regular"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group!");
});

// Escucha las actualizaciones en las que el bot se añade a un grupo como administrador.
groups.filter(myChatMemberFilter("out", "admin"), async (ctx) => {
await ctx.reply("Hello, thank you for adding me to the group as admin!");
});

// Escucha las actualizaciones en las que el bot es ascendido a admin.
groups.filter(myChatMemberFilter("regular", "admin"), async (ctx) => {
await ctx.reply("I was promoted to admin!");
});

// Escucha las actualizaciones en las que el bot es degradado a usuario normal.
groups.filter(myChatMemberFilter("admin", "regular"), async (ctx) => {
await ctx.reply("I am no longer admin");
});

bot.start({
// Asegúrate de incluir el tipo de actualización "chat_member" para que los manejadores anteriores funcionen.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

:::

Los filtros incluyen los estados habituales (propietario, administrador, miembro, restringido, abandonado, expulsado) y algunos adicionales por comodidad:

- `restricted_in`: miembro restringido del chat
- `restricted_out`: no es miembro del chat, tiene restricciones
- `in`: miembro del chat (administrador, creador, miembro, restricted_in)
- `out`: no es miembro del chat (abandono, expulsado, restricted_out)
- `free`: miembro no restringido del chat (administrador, creador, miembro)
- `admin`: un administrador del chat (administrador, creador)
- `regular`: miembro no administrador del chat (miembro, restricted_in)

Para resumir, he aquí un diagrama que muestra a qué corresponde cada consulta:

![Diagrama que muestra los estados correspondientes a cada consulta.](/images/chat-members-statuses.svg)

Puede crear sus agrupaciones personalizadas de tipos de miembros de chat pasando una matriz en lugar de una cadena:

```typescript
groups.filter(
chatMemberFilter(["restricted", "kicked"], ["free", "left"]),
async (ctx) => {
const from = ctx.from;
const { status: oldStatus, user } = ctx.chatMember.old_chat_member;
const lifted = oldStatus === "kicked" ? "ban" : "restrictions";
await ctx.reply(
`${from.first_name} levantó la ${lifted} de ${user.first_name}`,
);
},
);
```

#### Ejemplo de uso

La mejor manera de utilizar los filtros es elegir un conjunto de estados relevantes, por ejemplo `out`, `regular` y `admin`, y luego hacer una tabla de las transiciones entre ellos:

| ↱ | `out` | `regular` | `admin` |
| --------- | ---------------------- | ------------------------- | -------------------- |
| `out` | prohibición modificada | unirse | unión y promoción |
| `regular` | salida | restricciones modificadas | promoción |
| `admin` | salida | degradado | permisos modificados |

Asigne una escucha a todas las transiciones que sean relevantes para su caso de uso.

Combina estos filtros con `bot.chatType` para escuchar sólo las transiciones de un tipo específico de chat.
Añade un middleware para escuchar todas las actualizaciones como una forma de realizar operaciones comunes (como actualizar tu base de datos) antes de entregar el control a un manejador específico.

```typescript
const groups = bot.chatType(["group", "supergroup"]);

groups.on("chat_member", async (ctx, next) => {
// ejecutado en todas las actualizaciones de tipo chat_member
const {
old_chat_member: { status: oldStatus },
new_chat_member: { user, status },
from,
chat,
} = ctx.chatMember;
console.log(
`In group ${chat.id} user ${from.id} changed status of ${user.id}:`,
`${oldStatus} -> ${status}`,
);

// actualice aquí los datos de la base de datos

await next();
});

// controladores específicos

groups.filter(chatMemberFilter("out", "in"), async (ctx, next) => {
const { new_chat_member: { user } } = ctx.chatMember;
await ctx.reply(`Welcome ${user.first_name}!`);
});
```

### Utilidad de comprobación de estado

La función de utilidad `chatMemberIs` puede ser útil siempre que desee utilizar la lógica de filtrado dentro de un manejador.
Toma como entrada cualquiera de los estados regulares y personalizados (o una matriz de ellos), y reduce el tipo de la variable pasada.

```ts
bot.callbackQuery("foo", async (ctx) => {
const chatMember = await ctx.getChatMember(ctx.from.id);

if (!chatMemberIs(chatMember, "free")) {
chatMember.status; // "restricted" | "left" | "kicked"
await ctx.answerCallbackQuery({
show_alert: true,
text: "You don't have permission to do this!",
});
return;
}

chatMember.status; // "creator" | "administrator" | "member"
await ctx.answerCallbackQuery("bar");
});
```

### Hidratación de objetos miembros del chat

Puede mejorar aún más su experiencia de desarrollo utilizando la hidratación [API transformer](../advanced/transformers).
Este transformador se aplicará a las llamadas a `getChatMember` y `getChatAdministrators`, añadiendo un práctico método `is` a los objetos `ChatMember` devueltos.

```ts
type MyContext = HydrateChatMemberFlavor<Context>;
type MyApi = HydrateChatMemberApiFlavor<Api>;

const bot = new Bot<MyContext, MyApi>("");

bot.api.config.use(hydrateChatMember());

bot.command("ban", async (ctx) => {
const author = await ctx.getAuthor();

if (!author.is("admin")) {
author.status; // "member" | "restricted" | "left" | "kicked"
await ctx.reply("You don't have permission to do this");
return;
}

author.status; // "creator" | "administrator"
// ...
});
```

### Almacenamiento de miembros del chat

Puedes usar un [adaptador de almacenamiento](./session#adaptadores-de-almacenamiento-conocidos) válido de grammY o una instancia de cualquier clase que implemente la interfaz [`StorageAdapter`](/ref/core/storageadapter).
Expand All @@ -27,7 +347,7 @@ Esto significa que también necesitas especificar cualquier otro evento que quie
::: code-group

```ts [TypeScript]
import { Bot, type Context, MemorySessionStorage } from "grammy";
import { API_CONSTANTS, Bot, type Context, MemorySessionStorage } from "grammy";
import { type ChatMember } from "grammy/types";
import { chatMembers, type ChatMembersFlavor } from "@grammyjs/chat-members";

Expand All @@ -40,13 +360,13 @@ const bot = new Bot<MyContext>("<tu token de bot>");
bot.use(chatMembers(adapter));

bot.start({
// Asegúrese de especificar los tipos de actualización deseados
allowed_updates: ["chat_member", "message"],
// Asegúrese de especificar los tipos de actualización deseados.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

```js [JavaScript]
import { Bot, MemorySessionStorage } from "grammy";
import { API_CONSTANTS, Bot, MemorySessionStorage } from "grammy";
import { chatMembers } from "@grammyjs/chat-members";

const adapter = new MemorySessionStorage();
Expand All @@ -56,13 +376,14 @@ const bot = new Bot("");
bot.use(chatMembers(adapter));

bot.start({
// Asegúrese de especificar los tipos de actualización deseados
allowed_updates: ["chat_member", "message"],
// Asegúrese de especificar los tipos de actualización deseados.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

```ts [Deno]
import {
API_CONSTANTS,
Bot,
type Context,
MemorySessionStorage,
Expand All @@ -82,8 +403,8 @@ const bot = new Bot<MyContext>("<tu token de bot>");
bot.use(chatMembers(adapter));

bot.start({
// Asegúrese de especificar los tipos de actualización deseados
allowed_updates: ["chat_member", "message"],
// Asegúrese de especificar los tipos de actualización deseados.
allowed_updates: [...API_CONSTANTS.DEFAULT_UPDATE_TYPES, "chat_member"],
});
```

Expand Down
Loading
Loading