Gerenciar comentários em múltiplas publicações do Instagram se torna avassalador à medida que sua audiência cresce. A moderação de comentários do Instagram através da Graph API permite que você construa sistemas automatizados que buscam, filtram, respondem e moderam comentários programaticamente. Este guia orienta você na implementação de uma solução completa de gerenciamento de comentários usando TypeScript.
Seja você construindo um painel de mídias sociais, uma ferramenta de suporte ao cliente ou um sistema de engajamento automatizado, entender a API de comentários do Instagram é essencial. Cobriremos tudo, desde autenticação básica até fluxos de moderação avançados com análise de sentimento.
Requisitos de Acesso à API
Antes de poder automatizar comentários do Instagram, você precisa ter o acesso adequado à API configurado. A Instagram Graph API requer uma conta de Desenvolvedor do Facebook e um aplicativo aprovado com permissões específicas.
Permissões Necessárias
Seu aplicativo precisa destes escopos OAuth para trabalhar com comentários:
const INSTAGRAM_SCOPES = [
'instagram_business_basic', // Acesso básico à conta
'instagram_business_content_publish', // Publicar conteúdo
'instagram_business_manage_comments', // Ler e gerenciar comentários
'instagram_business_manage_messages', // Necessário para respostas privadas
];
Configuração do Ambiente
Crie um arquivo .env com suas credenciais:
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
Implementação da Troca de Token
Os tokens do Instagram começam como de curta duração (1 hora) e devem ser trocados por tokens de longa duração (60 dias):
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: Tokens de longa duração devem ser atualizados antes de expirarem. Configure um cron job para atualizar tokens pelo menos 7 dias antes da expiração.
Buscando Comentários em Publicações
A base de qualquer sistema de moderação de comentários do Instagram é recuperar comentários de forma eficiente. A API suporta paginação para lidar com publicações com milhares de comentários.
Recuperação Básica de Comentários
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 lookup para dados completos do comentário
const commentDataById = new Map<string, any>();
(data.data || []).forEach((comment: any) => {
commentDataById.set(comment.id, comment);
});
// Identificar IDs de respostas para filtrar do nível superior
const allReplyIds = new Set<string>();
(data.data || []).forEach((comment: any) => {
(comment.replies?.data || []).forEach((reply: any) => {
allReplyIds.add(reply.id);
});
});
// Filtrar apenas comentários de nível 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,
},
};
}
Buscando Todos os Comentários com Paginação
Para publicações com muitos comentários, implemente paginação para recuperar tudo:
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;
// Rate limiting: aguardar entre requisições
await new Promise(resolve => setTimeout(resolve, 100));
}
return allComments.slice(0, maxComments);
}
Respondendo a Comentários Programaticamente
A capacidade de responder a comentários do Instagram via API programaticamente permite engajamento automatizado, bots de suporte ao cliente e gerenciamento de comunidade em escala.
Implementação de Resposta 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',
};
}
}
Resposta Privada (Mensagem Direta)
O Instagram permite enviar uma DM privada para autores de comentários. Isso é útil para cenários de suporte ao 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();
// Tratar casos de erro específicos
if (errorText.includes('551') || errorText.toLowerCase().includes('already')) {
return {
messageId: '',
success: false,
error: 'Uma resposta privada já foi enviada para este comentário. O Instagram permite apenas uma resposta privada por comentário.',
};
}
if (errorText.includes('10903')) {
return {
messageId: '',
success: false,
error: 'Comentário tem mais de 7 dias. Respostas privadas devem ser enviadas dentro de 7 dias.',
};
}
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: O Instagram permite apenas UMA resposta privada por comentário, e ela deve ser enviada dentro de 7 dias após o comentário ser postado. Planeje sua automação de acordo.
Ocultando e Excluindo Comentários
Uma moderação de comentários do Instagram eficaz requer a capacidade de ocultar comentários inapropriados ou excluir suas próprias respostas.
Ocultar e Desocultar Comentários
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',
};
}
}
Excluir Comentários
Você só pode excluir comentários que você fez (suas próprias respostas):
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',
};
}
}
Filtragem de Comentários e Regras de Moderação
Construir um sistema de moderação automatizado requer definir regras para qual conteúdo sinalizar, ocultar ou responder.
Motor de Regras de Moderação
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[]