Construir uma integração com o Reddit que lida com mensagens privadas requer entender a abordagem única do Reddit para mensagens. O sistema de reddit api messages usa uma combinação de endpoints de caixa de entrada, identificadores fullname e escopos OAuth que diferem das APIs de mensagens típicas. Este guia orienta você através de tudo que você precisa para implementar uma integração completa de DM do Reddit.
O sistema de mensagens do Reddit lida tanto com mensagens privadas entre usuários quanto com respostas de comentários em uma caixa de entrada unificada. Sua aplicação precisa distinguir entre esses tipos, gerenciar threads de conversas e lidar com os limites de taxa do Reddit apropriadamente.
Introdução à Reddit API
O Reddit fornece uma API abrangente para acessar dados de usuários, postar conteúdo e gerenciar mensagens privadas. A API usa OAuth 2.0 para autenticação e requer escopos específicos para diferentes operações.
Para funcionalidade de mensagens, você trabalhará com estes endpoints principais:
| Endpoint | Propósito | Método |
|---|---|---|
/message/inbox | Todos os itens da caixa de entrada (mensagens + respostas de comentários) | GET |
/message/messages | Apenas mensagens privadas | GET |
/message/sent | Mensagens enviadas | GET |
/api/compose | Enviar nova mensagem | POST |
/api/comment | Responder mensagem | POST |
/api/read_message | Marcar como lida | POST |
A reddit inbox api retorna mensagens em um formato de listagem com suporte a paginação. Cada mensagem inclui metadados sobre o remetente, destinatário, assunto e thread da conversa.

Autenticação OAuth 2.0 para Reddit
Antes de acessar a reddit private messages api, você precisa autenticar usuários com os escopos OAuth corretos. O escopo privatemessages concede acesso para ler e enviar mensagens diretas.
interface RedditAuthConfig {
clientId: string;
clientSecret: string;
redirectUri: string;
scopes: string[];
}
const config: RedditAuthConfig = {
clientId: process.env.REDDIT_CLIENT_ID || '',
clientSecret: process.env.REDDIT_CLIENT_SECRET || '',
redirectUri: process.env.REDDIT_REDIRECT_URI || '',
scopes: ['identity', 'privatemessages', 'read', 'history']
};
function getAuthUrl(state: string): string {
const params = new URLSearchParams({
client_id: config.clientId,
response_type: 'code',
state: state,
redirect_uri: config.redirectUri,
duration: 'permanent',
scope: config.scopes.join(' '),
});
return `https://www.reddit.com/api/v1/authorize?${params.toString()}`;
}
async function exchangeCodeForToken(code: string): Promise<TokenResponse> {
const credentials = Buffer.from(
`${config.clientId}:${config.clientSecret}`
).toString('base64');
const response = await fetch('https://www.reddit.com/api/v1/access_token', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'YourApp/1.0.0',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: config.redirectUri,
}).toString(),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${response.status} ${error}`);
}
return response.json();
}
interface TokenResponse {
access_token: string;
refresh_token: string;
expires_in: number;
scope: string;
token_type: string;
}
Nota: O Reddit requer um cabeçalho User-Agent descritivo para todas as requisições da API. Usar um User-Agent genérico ou ausente resultará em limitação de taxa ou requisições bloqueadas.
Entendendo Fullnames (t1_, t3_, t4_)
O Reddit usa um sistema de "fullname" para identificar todos os objetos na API. Entender esses prefixos é crítico ao trabalhar com a reddit dm api:
| Prefixo | Tipo de Objeto | Exemplo |
|---|---|---|
t1_ | Comentário | t1_abc123 |
t2_ | Conta | t2_xyz789 |
t3_ | Link/Post | t3_def456 |
t4_ | Mensagem | t4_ghi012 |
t5_ | Subreddit | t5_jkl345 |
Ao responder mensagens ou marcá-las como lidas, você deve usar o fullname completo incluindo o prefixo:
function normalizeFullname(id: string, type: 't1' | 't3' | 't4'): string {
if (id.startsWith(`${type}_`)) {
return id;
}
return `${type}_${id}`;
}
// Exemplos de uso
const messageFullname = normalizeFullname('abc123', 't4'); // Retorna: t4_abc123
const commentFullname = normalizeFullname('t1_xyz789', 't1'); // Retorna: t1_xyz789
Buscando Mensagens da Caixa de Entrada
O endpoint de caixa de entrada retorna todos os itens incluindo mensagens privadas e respostas de comentários. Para buscar apenas mensagens privadas, use o endpoint /message/messages em vez disso.
interface RedditMessage {
id: string;
name: string; // Fullname (t4_xxx)
author: string;
dest: string;
subject: string;
body: string;
created_utc: number;
new: boolean;
was_comment: boolean;
first_message_name: string | null;
}
interface InboxResponse {
messages: RedditMessage[];
after: string | null;
}
async function fetchInboxMessages(
accessToken: string,
options: { limit?: number; after?: string } = {}
): Promise<InboxResponse> {
const limit = Math.min(options.limit || 25, 100);
let url = `https://oauth.reddit.com/message/messages?limit=${limit}&raw_json=1`;
if (options.after) {
url += `&after=${options.after}`;
}
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': 'YourApp/1.0.0',
},
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to fetch inbox: ${response.status} ${error}`);
}
const data = await response.json();
const messages: RedditMessage[] = (data.data?.children || [])
.filter((item: any) => !item.data.was_comment) // Filtrar respostas de comentários
.map((item: any) => ({
id: item.data.id,
name: item.data.name,
author: item.data.author,
dest: item.data.dest,
subject: item.data.subject,
body: item.data.body,
created_utc: item.data.created_utc,
new: item.data.new,
was_comment: item.data.was_comment,
first_message_name: item.data.first_message_name,
}));
return {
messages,
after: data.data?.after || null,
};
}
O campo first_message_name é importante para threading. Mensagens na mesma conversa compartilham esse valor, permitindo que você as agrupe.
Enviando Mensagens Privadas com a Reddit API
Para enviar uma nova mensagem privada, use o endpoint /api/compose. Este endpoint requer o nome de usuário do destinatário, uma linha de assunto e o corpo da mensagem.
interface SendMessageParams {
to: string;
subject: string;
text: string;
}
interface SendMessageResult {
success: boolean;
errors?: string[];
}
async function sendPrivateMessage(
accessToken: string,
params: SendMessageParams
): Promise<SendMessageResult> {
const { to, subject, text } = params;
// Validar entradas
if (!to || !subject || !text) {
return {
success: false,
errors: ['Destinatário, assunto e texto da mensagem são obrigatórios'],
};
}
if (subject.length > 100) {
return {
success: false,
errors: ['O assunto deve ter 100 caracteres ou menos'],
};
}
const response = await fetch('https://oauth.reddit.com/api/compose', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': 'YourApp/1.0.0',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
api_type: 'json',
to,
subject,
text,
}).toString(),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to send message: ${response.status} ${error}`);
}
const data = await response.json();
const errors = data.json?.errors || [];
if (errors.length > 0) {
return {
success: false,
errors: errors.map((e: any[]) => e.join(': ')),
};
}
return { success: true };
}
Nota: O Reddit limita contas novas de enviar mensagens privadas. Os usuários podem precisar verificar seu email e ter alguma idade de conta antes que o recurso de mensagens fique disponível.
Respondendo Mensagens
Responder a uma mensagem existente usa o endpoint /api/comment (o mesmo endpoint usado para respostas de comentários). Você precisa do fullname da mensagem que está respondendo.
interface ReplyResult {
success: boolean;
replyId?: string;
errors?: string[];
}
async function replyToMessage(
accessToken: string,
messageFullname: string,
text: string
): Promise<ReplyResult> {
// Garantir formato correto do fullname
const fullname = messageFullname.startsWith('t4_')
? messageFullname
: `t4_${messageFullname}`;
const response = await fetch('https://oauth.reddit.com/api/comment', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': 'YourApp/1.0.0',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
api_type: 'json',
thing_id: fullname,
text,
}).toString(),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to reply: ${response.status} ${error}`);
}
const data = await response.json();
const errors = data.json?.errors || [];
if (errors.length > 0) {
return {
success: false,
errors: errors.map((e: any[]) => e.join(': ')),
};
}
const replyData = data.json?.data?.things?.[0]?.data;
return {
success: true,
replyId: replyData?.id,
};
}
Threading e Agrupamento de Mensagens
O Reddit agrupa mensagens em conversas usando o campo first_message_name. Construir uma visualização de conversa requer buscar mensagens tanto da caixa de entrada quanto da pasta de enviados, depois agrupá-las por thread.
interface Conversation {
id: string;
participantUsername: string;
subject: string;
messages: ThreadMessage[];
lastUpdated: Date;
hasUnread: boolean;
}
interface ThreadMessage {
id: string;
fullname: string;
text: string;
sentAt: Date;
isFromMe: boolean;
senderUsername: string;
}
async function getConversations(
accessToken: string,
myUsername: string
): Promise<Conversation[]> {
// Buscar mensagens da caixa de entrada e enviadas
const [inboxResponse, sentResponse] = await Promise.all([
fetch('https://oauth.reddit.com/message/messages?limit=100&raw_json=1', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': 'YourApp/1.0.0',
},
}),
fetch('https://oauth.reddit.com/message/sent?limit=100&raw_json=1', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'User-Agent': 'YourApp/1.0.0',
},
}),
]);
const inboxData = await inboxResponse.json();
const sentData = await sentResponse.json();
const allMessages: any[] = [
...(inboxData.data?.children || []),
...(sentData.data?.children || []),
];
// Agrupar por conversa
const conversationsMap = new Map<string, Conversation>();
const seenMessageIds = new Set<string>();
for (const item of allMessages) {
const msg = item.data;
// Pular duplicatas e respostas de comentários
if (seenMessageIds.has(msg.name) || msg.was_comment) {
continue;
}
seenMessageIds.add(msg.name);
const conversationId = msg.first_message_name || msg.name;
const isFromMe = msg.author?.toLowerCase() === myUsername.toLowerCase();
const otherParticipant = isFromMe ? msg.dest : msg.author;
const threadMessage: ThreadMessage = {
id: msg.id,
fullname: msg.name,
text: msg.body,
sentAt: new Date(msg.created_utc * 1000),
isFromMe,
senderUsername: msg.author,
};
if (!conversationsMap.has(conversationId)) {
conversationsMap.set(conversationId, {
id: conversationId,
participantUsername: otherParticipant,
subject: msg.subject,
messages: [threadMessage],
lastUpdated: threadMessage.sentAt,
hasUnread: msg.new && !isFromMe,
});
} else {
const conversation = conversationsMap.get(conversationId)!;
conversation.messages.push(threadMessage);
if (threadMessage.sentAt > conversation.lastUpdated) {
conversation.lastUpdated = threadMessage.sentAt;
}
if (msg.new && !isFromMe) {
conversation.hasUnread = true;
}
}
}
// Ordenar mensagens dentro de cada conversa e ordenar conversas por última atualização
const conversations = Array.from(conversationsMap.values());
for (const conv of conversations) {
conv.messages.sort((a, b) => a.sentAt.getTime() - b.sentAt.getTime());
}
conversations.sort((a, b) => b.lastUpdated.getTime() - a.lastUpdated.getTime());
return conversations;
}
Marcando Mensagens como Lidas
O endpoint /api/read_message aceita uma lista de fullnames separados por vírgula, permitindo que você marque múltiplas mensagens como lidas em uma única requisição.
async function markMessagesAsRead(
acc