Gestionar comentarios en múltiples publicaciones de Instagram se vuelve abrumador a medida que tu audiencia crece. La moderación de comentarios de Instagram a través de la Graph API te permite construir sistemas automatizados que obtienen, filtran, responden y moderan comentarios programáticamente. Esta guía te lleva paso a paso a través de la implementación de una solución completa de gestión de comentarios usando TypeScript.
Ya sea que estés construyendo un panel de redes sociales, una herramienta de soporte al cliente o un sistema de engagement automatizado, entender la API de comentarios de Instagram es esencial. Cubriremos todo, desde la autenticación básica hasta flujos de trabajo de moderación avanzados con análisis de sentimiento.
Requisitos de Acceso a la API
Antes de poder automatizar comentarios de Instagram, necesitas tener configurado el acceso apropiado a la API. La Instagram Graph API requiere una cuenta de Facebook Developer y una app aprobada con permisos específicos.
Permisos Requeridos
Tu app necesita estos scopes de OAuth para trabajar con comentarios:
const INSTAGRAM_SCOPES = [
'instagram_business_basic', // Acceso básico a la cuenta
'instagram_business_content_publish', // Publicar contenido
'instagram_business_manage_comments', // Leer y gestionar comentarios
'instagram_business_manage_messages', // Requerido para respuestas privadas
];
Configuración del Entorno
Crea un archivo .env con tus credenciales:
INSTAGRAM_CLIENT_ID=your_client_id
INSTAGRAM_CLIENT_SECRET=your_client_secret
INSTAGRAM_ACCESS_TOKEN=your_long_lived_token
INSTAGRAM_USER_ID=your_business_account_id
Implementación del Intercambio de Token
Los tokens de Instagram comienzan como de corta duración (1 hora) y deben intercambiarse por tokens de larga duración (60 días):
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
}
async function exchangeForLongLivedToken(
shortLivedToken: string,
clientSecret: string
): Promise<TokenResponse> {
const baseUrl = 'https://graph.instagram.com';
const response = await fetch(
`${baseUrl}/access_token?` +
`grant_type=ig_exchange_token&` +
`client_secret=${clientSecret}&` +
`access_token=${shortLivedToken}`,
{ method: 'GET' }
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Token exchange failed: ${response.status} - ${errorText}`);
}
return response.json();
}
async function refreshLongLivedToken(
currentToken: string
): Promise<TokenResponse> {
const baseUrl = 'https://graph.instagram.com';
const response = await fetch(
`${baseUrl}/refresh_access_token?` +
`grant_type=ig_refresh_token&` +
`access_token=${currentToken}`,
{ method: 'GET' }
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Token refresh failed: ${response.status} - ${errorText}`);
}
return response.json();
}
Nota: Los tokens de larga duración deben renovarse antes de que expiren. Configura un cron job para renovar tokens al menos 7 días antes de la expiración.
Obteniendo Comentarios de Publicaciones
La base de cualquier sistema de moderación de comentarios de Instagram es recuperar comentarios de manera eficiente. La API soporta paginación para manejar publicaciones con miles de comentarios.
Recuperación Básica de Comentarios
interface InstagramUser {
id: string;
username: string;
}
interface InstagramComment {
commentId: string;
comment: string;
created: string;
from: InstagramUser;
likeCount: number;
replyCount: number;
hidden: boolean;
platform: 'instagram';
replies: InstagramComment[];
}
interface CommentResponse {
comments: InstagramComment[];
pagination: {
hasMore: boolean;
cursor?: string;
};
}
async function getMediaComments(
accessToken: string,
mediaId: string,
options?: { limit?: number; cursor?: string }
): Promise<CommentResponse> {
const baseUrl = 'https://graph.instagram.com';
const limit = Math.min(options?.limit || 25, 50);
let url = `${baseUrl}/${mediaId}/comments?` +
`fields=id,text,timestamp,username,hidden,` +
`from{id,username},like_count,` +
`replies{id,text,timestamp,username,hidden,from{id,username},like_count}&` +
`limit=${limit}&access_token=${accessToken}`;
if (options?.cursor) {
url += `&after=${options.cursor}`;
}
const response = await fetch(url);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to fetch comments: ${response.status} - ${errorText}`);
}
const data = await response.json();
// Construir mapa de búsqueda para datos completos de comentarios
const commentDataById = new Map<string, any>();
(data.data || []).forEach((comment: any) => {
commentDataById.set(comment.id, comment);
});
// Identificar IDs de respuestas para filtrar del nivel superior
const allReplyIds = new Set<string>();
(data.data || []).forEach((comment: any) => {
(comment.replies?.data || []).forEach((reply: any) => {
allReplyIds.add(reply.id);
});
});
// Filtrar solo comentarios de nivel superior
const topLevelComments = (data.data || [])
.filter((comment: any) => !allReplyIds.has(comment.id));
const comments: InstagramComment[] = topLevelComments.map((comment: any) => ({
commentId: comment.id,
comment: comment.text,
created: comment.timestamp,
from: {
id: comment.from?.id,
username: comment.from?.username || comment.username,
},
likeCount: comment.like_count || 0,
replyCount: comment.replies?.data?.length || 0,
hidden: comment.hidden || false,
platform: 'instagram',
replies: (comment.replies?.data || []).map((reply: any) => {
const fullReplyData = commentDataById.get(reply.id) || reply;
return {
commentId: reply.id,
comment: fullReplyData.text || reply.text,
created: fullReplyData.timestamp || reply.timestamp,
from: {
id: fullReplyData.from?.id || reply.from?.id,
username: fullReplyData.from?.username || reply.from?.username,
},
likeCount: fullReplyData.like_count || reply.like_count || 0,
hidden: fullReplyData.hidden || false,
platform: 'instagram',
replies: [],
replyCount: 0,
};
}),
}));
return {
comments,
pagination: {
hasMore: !!data.paging?.next,
cursor: data.paging?.cursors?.after,
},
};
}
Obteniendo Todos los Comentarios con Paginación
Para publicaciones con muchos comentarios, implementa paginación para recuperar todo:
async function getAllComments(
accessToken: string,
mediaId: string,
maxComments: number = 1000
): Promise<InstagramComment[]> {
const allComments: InstagramComment[] = [];
let cursor: string | undefined;
while (allComments.length < maxComments) {
const response = await getMediaComments(accessToken, mediaId, {
limit: 50,
cursor,
});
allComments.push(...response.comments);
if (!response.pagination.hasMore || !response.pagination.cursor) {
break;
}
cursor = response.pagination.cursor;
// Limitación de tasa: esperar entre solicitudes
await new Promise(resolve => setTimeout(resolve, 100));
}
return allComments.slice(0, maxComments);
}
Respondiendo a Comentarios Programáticamente
La capacidad de responder a comentarios de Instagram vía API programáticamente permite engagement automatizado, bots de soporte al cliente y gestión de comunidad a escala.
Implementación de Respuesta Pública
interface ReplyResult {
replyId: string;
success: boolean;
error?: string;
}
async function replyToComment(
accessToken: string,
commentId: string,
message: string
): Promise<ReplyResult> {
const baseUrl = 'https://graph.instagram.com';
try {
const response = await fetch(
`${baseUrl}/${commentId}/replies`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message,
access_token: accessToken,
}),
}
);
if (!response.ok) {
const errorText = await response.text();
return {
replyId: '',
success: false,
error: `Reply failed: ${response.status} - ${errorText}`,
};
}
const data = await response.json();
return {
replyId: data.id,
success: true,
};
} catch (error) {
return {
replyId: '',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
Respuesta Privada (Mensaje Directo)
Instagram permite enviar un DM privado a los autores de comentarios. Esto es útil para escenarios de soporte al cliente:
interface PrivateReplyResult {
messageId: string;
success: boolean;
error?: string;
}
async function sendPrivateReply(
accessToken: string,
igUserId: string,
commentId: string,
message: string
): Promise<PrivateReplyResult> {
const baseUrl = 'https://graph.instagram.com';
try {
const response = await fetch(
`${baseUrl}/${igUserId}/messages`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({
recipient: {
comment_id: commentId,
},
message: {
text: message,
},
}),
}
);
if (!response.ok) {
const errorText = await response.text();
// Manejar casos de error específicos
if (errorText.includes('551') || errorText.toLowerCase().includes('already')) {
return {
messageId: '',
success: false,
error: 'Ya se ha enviado una respuesta privada a este comentario. Instagram solo permite una respuesta privada por comentario.',
};
}
if (errorText.includes('10903')) {
return {
messageId: '',
success: false,
error: 'El comentario tiene más de 7 días. Las respuestas privadas deben enviarse dentro de los 7 días.',
};
}
return {
messageId: '',
success: false,
error: `Private reply failed: ${response.status} - ${errorText}`,
};
}
const data = await response.json();
return {
messageId: data.message_id || data.id,
success: true,
};
} catch (error) {
return {
messageId: '',
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
Nota: Instagram solo permite UNA respuesta privada por comentario, y debe enviarse dentro de los 7 días posteriores a la publicación del comentario. Planifica tu automatización en consecuencia.
Ocultando y Eliminando Comentarios
Una moderación de comentarios de Instagram efectiva requiere la capacidad de ocultar comentarios inapropiados o eliminar tus propias respuestas.
Ocultar y Mostrar Comentarios
async function hideComment(
accessToken: string,
commentId: string
): Promise<{ success: boolean; error?: string }> {
const baseUrl = 'https://graph.instagram.com';
try {
const response = await fetch(
`${baseUrl}/${commentId}?hide=true&access_token=${accessToken}`,
{ method: 'POST' }
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `Hide failed: ${response.status} - ${errorText}`,
};
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
async function unhideComment(
accessToken: string,
commentId: string
): Promise<{ success: boolean; error?: string }> {
const baseUrl = 'https://graph.instagram.com';
try {
const response = await fetch(
`${baseUrl}/${commentId}?hide=false&access_token=${accessToken}`,
{ method: 'POST' }
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `Unhide failed: ${response.status} - ${errorText}`,
};
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
Eliminar Comentarios
Solo puedes eliminar comentarios que hayas hecho (tus propias respuestas):
async function deleteComment(
accessToken: string,
commentId: string
): Promise<{ success: boolean; error?: string }> {
const baseUrl = 'https://graph.instagram.com';
try {
const response = await fetch(
`${baseUrl}/${commentId}?access_token=${accessToken}`,
{ method: 'DELETE' }
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `Delete failed: ${response.status} - ${errorText}`,
};
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
Filtrado de Comentarios y Reglas de Moderación
Construir un sistema de moderación automatizado requiere definir reglas para qué contenido marcar, ocultar o responder.
Motor de Reglas de Moderación
interface ModerationRule {
id: string;
name: string;
type: 'keyword' | 'regex' | 'sentiment' | 'spam';
pattern?: string;
keywords?: string[];
action: 'hide' | 'flag' | 'delete' | 'reply';
replyTemplate?: string;
enabled: boolean;
}
interface ModerationResult {
commentId: string;
triggered: boolean;
matchedRules: string[];
action: 'hide' | 'flag' | 'delete' | 'reply' | 'none';
replyMessage?: string;
}
class CommentModerator {
private rules: ModerationRule[];
constructor(rules: ModerationRule[]) {
this.rules = rules.filter(r => r.enabled);
}
evaluate(comment: InstagramComment): ModerationResult {
const matchedRules: string[]