Construir una integración de Reddit que maneje mensajes privados requiere entender el enfoque único de Reddit hacia la mensajería. El sistema de reddit api messages utiliza una combinación de endpoints de bandeja de entrada, identificadores fullname y scopes de OAuth que difieren de las APIs de mensajería típicas. Esta guía te lleva a través de todo lo que necesitas para implementar una integración completa de DM de Reddit.
El sistema de mensajería de Reddit maneja tanto mensajes privados entre usuarios como respuestas a comentarios en una bandeja de entrada unificada. Tu aplicación necesita distinguir entre estos tipos, gestionar hilos de conversación y manejar los límites de tasa de Reddit apropiadamente.
Introducción a Reddit API
Reddit proporciona una API completa para acceder a datos de usuarios, publicar contenido y gestionar mensajes privados. La API utiliza OAuth 2.0 para autenticación y requiere scopes específicos para diferentes operaciones.
Para la funcionalidad de mensajería, trabajarás con estos endpoints principales:
| Endpoint | Propósito | Método |
|---|---|---|
/message/inbox | Todos los elementos de la bandeja de entrada (mensajes + respuestas a comentarios) | GET |
/message/messages | Solo mensajes privados | GET |
/message/sent | Mensajes enviados | GET |
/api/compose | Enviar nuevo mensaje | POST |
/api/comment | Responder a mensaje | POST |
/api/read_message | Marcar como leído | POST |
La reddit inbox api devuelve mensajes en un formato de listado con soporte de paginación. Cada mensaje incluye metadatos sobre el remitente, destinatario, asunto e hilo de conversación.

Autenticación OAuth 2.0 para Reddit
Antes de acceder a la reddit private messages api, necesitas autenticar usuarios con los scopes de OAuth correctos. El scope privatemessages otorga acceso para leer y enviar mensajes directos.
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: Reddit requiere un encabezado User-Agent descriptivo para todas las solicitudes de API. Usar un User-Agent genérico o faltante resultará en limitación de tasa o solicitudes bloqueadas.
Entendiendo los Fullnames (t1_, t3_, t4_)
Reddit utiliza un sistema de "fullname" para identificar todos los objetos en la API. Entender estos prefijos es crítico cuando trabajas con la reddit dm api:
| Prefijo | Tipo de Objeto | Ejemplo |
|---|---|---|
t1_ | Comentario | t1_abc123 |
t2_ | Cuenta | t2_xyz789 |
t3_ | Enlace/Publicación | t3_def456 |
t4_ | Mensaje | t4_ghi012 |
t5_ | Subreddit | t5_jkl345 |
Al responder mensajes o marcarlos como leídos, debes usar el fullname completo incluyendo el prefijo:
function normalizeFullname(id: string, type: 't1' | 't3' | 't4'): string {
if (id.startsWith(`${type}_`)) {
return id;
}
return `${type}_${id}`;
}
// Ejemplos de uso
const messageFullname = normalizeFullname('abc123', 't4'); // Devuelve: t4_abc123
const commentFullname = normalizeFullname('t1_xyz789', 't1'); // Devuelve: t1_xyz789
Obteniendo Mensajes de la Bandeja de Entrada
El endpoint de bandeja de entrada devuelve todos los elementos incluyendo mensajes privados y respuestas a comentarios. Para obtener solo mensajes privados, usa el endpoint /message/messages en su lugar.
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 respuestas a comentarios
.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,
};
}
El campo first_message_name es importante para el threading. Los mensajes en la misma conversación comparten este valor, permitiéndote agruparlos juntos.
Enviando Mensajes Privados con Reddit API
Para enviar un nuevo mensaje privado, usa el endpoint /api/compose. Este endpoint requiere el nombre de usuario del destinatario, una línea de asunto y el cuerpo del mensaje.
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: ['Destinatario, asunto y texto del mensaje son requeridos'],
};
}
if (subject.length > 100) {
return {
success: false,
errors: ['El asunto debe tener 100 caracteres o 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: Reddit limita a las cuentas nuevas de enviar mensajes privados. Los usuarios pueden necesitar verificar su correo electrónico y tener cierta antigüedad de cuenta antes de que la función de mensajería esté disponible.
Respondiendo a Mensajes
Responder a un mensaje existente usa el endpoint /api/comment (el mismo endpoint usado para respuestas a comentarios). Necesitas el fullname del mensaje al que estás respondiendo.
interface ReplyResult {
success: boolean;
replyId?: string;
errors?: string[];
}
async function replyToMessage(
accessToken: string,
messageFullname: string,
text: string
): Promise<ReplyResult> {
// Asegurar formato de fullname correcto
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 y Agrupación de Mensajes
Reddit agrupa mensajes en conversaciones usando el campo first_message_name. Construir una vista de conversación requiere obtener mensajes tanto de las carpetas de bandeja de entrada como de enviados, luego agruparlos por hilo.
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[]> {
// Obtener tanto mensajes de bandeja de entrada como enviados
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 conversación
const conversationsMap = new Map<string, Conversation>();
const seenMessageIds = new Set<string>();
for (const item of allMessages) {
const msg = item.data;
// Omitir duplicados y respuestas a comentarios
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 mensajes dentro de cada conversación y ordenar conversaciones por última actualización
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 Mensajes como Leídos
El endpoint /api/read_message acepta una lista de fullnames separados por comas, permitiéndote marcar múltiples mensajes como leídos en una sola solicitud.
async function markMessagesAsRead(
acc