Fix: fetchMessageHistory con evento SSE y logs de debug
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m9s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m9s
- Agregar logs detallados en /api/debug/history/fetch para diagnosticar - Emitir evento SSE 'history.synced' cuando llegan mensajes del historial - Frontend ahora escucha el evento SSE en vez de timeout fijo de 3 segundos - Agregar script y documentación para referencia de Baileys message history
This commit is contained in:
@@ -8,6 +8,7 @@ export interface RealtimeEvents {
|
|||||||
'message.status': (data: { instanceId: string; messageId: string; status: string }) => void
|
'message.status': (data: { instanceId: string; messageId: string; status: string }) => void
|
||||||
'instance.status': (data: { instanceId: string; status: string; phoneNumber?: string }) => void
|
'instance.status': (data: { instanceId: string; status: string; phoneNumber?: string }) => void
|
||||||
'instance.qr': (data: { instanceId: string; qr: string; qrDataUrl: string }) => void
|
'instance.qr': (data: { instanceId: string; qr: string; qrDataUrl: string }) => void
|
||||||
|
'history.synced': (data: { instanceId: string; chatsCount: number; contactsCount: number; messagesCount: number; syncType: any }) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRealtime = () => {
|
export const useRealtime = () => {
|
||||||
@@ -49,11 +50,12 @@ export const useRealtime = () => {
|
|||||||
// Listen for specific event types
|
// Listen for specific event types
|
||||||
const eventTypes = [
|
const eventTypes = [
|
||||||
'message.received',
|
'message.received',
|
||||||
'message.sent',
|
'message.sent',
|
||||||
'message.status',
|
'message.status',
|
||||||
'instance.status',
|
'instance.status',
|
||||||
'instance.qr',
|
'instance.qr',
|
||||||
'instance.pairing'
|
'instance.pairing',
|
||||||
|
'history.synced'
|
||||||
]
|
]
|
||||||
|
|
||||||
eventTypes.forEach(eventType => {
|
eventTypes.forEach(eventType => {
|
||||||
|
|||||||
@@ -395,10 +395,10 @@ const fetchWhatsAppHistory = async () => {
|
|||||||
if (!selectedInstance.value?.value || !selectedChat.value) return
|
if (!selectedInstance.value?.value || !selectedChat.value) return
|
||||||
|
|
||||||
loadingWhatsAppHistory.value = true
|
loadingWhatsAppHistory.value = true
|
||||||
try {
|
const instanceId = selectedInstance.value.value
|
||||||
const instanceId = selectedInstance.value.value
|
const chatId = selectedChat.value.id
|
||||||
const chatId = selectedChat.value.id
|
|
||||||
|
|
||||||
|
try {
|
||||||
// Get the oldest message from local DB
|
// Get the oldest message from local DB
|
||||||
const oldest = await $fetch<{
|
const oldest = await $fetch<{
|
||||||
hasMessages: boolean
|
hasMessages: boolean
|
||||||
@@ -406,6 +406,23 @@ const fetchWhatsAppHistory = async () => {
|
|||||||
timestamp: number | null
|
timestamp: number | null
|
||||||
}>(`/api/messages/${instanceId}/${chatId}/oldest`)
|
}>(`/api/messages/${instanceId}/${chatId}/oldest`)
|
||||||
|
|
||||||
|
// Create a promise that resolves when history.synced event arrives
|
||||||
|
const historyPromise = new Promise<{ messagesCount: number; timeout?: boolean }>((resolve) => {
|
||||||
|
// Set up listener for history.synced event
|
||||||
|
const cleanup = on('history.synced', (data) => {
|
||||||
|
if (data.instanceId === instanceId) {
|
||||||
|
cleanup() // Remove listener
|
||||||
|
resolve({ messagesCount: data.messagesCount })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Timeout de seguridad (15 segundos)
|
||||||
|
setTimeout(() => {
|
||||||
|
cleanup() // Remove listener
|
||||||
|
resolve({ messagesCount: 0, timeout: true })
|
||||||
|
}, 15000)
|
||||||
|
})
|
||||||
|
|
||||||
// Request history from WhatsApp
|
// Request history from WhatsApp
|
||||||
if (!oldest.hasMessages) {
|
if (!oldest.hasMessages) {
|
||||||
// No messages in DB, request without reference
|
// No messages in DB, request without reference
|
||||||
@@ -429,10 +446,18 @@ const fetchWhatsAppHistory = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for history sync to complete and reload
|
// Wait for history sync event or timeout
|
||||||
await new Promise(r => setTimeout(r, 3000))
|
const result = await historyPromise
|
||||||
await reloadMessages()
|
|
||||||
hasMoreMessages.value = true
|
if (result.timeout) {
|
||||||
|
console.log('[History] Timeout waiting for history sync - WhatsApp may not have more messages')
|
||||||
|
} else if (result.messagesCount > 0) {
|
||||||
|
console.log(`[History] Received ${result.messagesCount} messages from history sync`)
|
||||||
|
await reloadMessages()
|
||||||
|
hasMoreMessages.value = true
|
||||||
|
} else {
|
||||||
|
console.log('[History] No messages received from history sync')
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error fetching WhatsApp history:', e)
|
console.error('Error fetching WhatsApp history:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
1684
docs/baileys-message-history-reference.md
Normal file
1684
docs/baileys-message-history-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
253
scripts/scrape-baileys-message-history.ts
Normal file
253
scripts/scrape-baileys-message-history.ts
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
/**
|
||||||
|
* Script to scrape Baileys message history documentation from baileys.wiki
|
||||||
|
* Focused on fetching messages and chat history functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
const BASE_URL = 'https://baileys.wiki/docs/api'
|
||||||
|
|
||||||
|
interface DocSection {
|
||||||
|
name: string
|
||||||
|
path: string
|
||||||
|
type: 'interface' | 'type' | 'function' | 'variable' | 'class' | 'enum'
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sections specifically related to message history
|
||||||
|
const SECTIONS: DocSection[] = [
|
||||||
|
// Core message types
|
||||||
|
{ name: 'WAMessage', path: '/type-aliases/WAMessage', type: 'type', category: 'Message Types' },
|
||||||
|
{ name: 'WAMessageKey', path: '/type-aliases/WAMessageKey', type: 'type', category: 'Message Types' },
|
||||||
|
{ name: 'WAMessageCursor', path: '/type-aliases/WAMessageCursor', type: 'type', category: 'Message Types' },
|
||||||
|
{ name: 'WAMessageUpdate', path: '/type-aliases/WAMessageUpdate', type: 'type', category: 'Message Types' },
|
||||||
|
{ name: 'MinimalMessage', path: '/type-aliases/MinimalMessage', type: 'type', category: 'Message Types' },
|
||||||
|
{ name: 'RecentMessage', path: '/interfaces/RecentMessage', type: 'interface', category: 'Message Types' },
|
||||||
|
{ name: 'RecentMessageKey', path: '/interfaces/RecentMessageKey', type: 'interface', category: 'Message Types' },
|
||||||
|
{ name: 'LastMessageList', path: '/type-aliases/LastMessageList', type: 'type', category: 'Message Types' },
|
||||||
|
|
||||||
|
// Chat types (messages are in chats)
|
||||||
|
{ name: 'Chat', path: '/type-aliases/Chat', type: 'type', category: 'Chat Types' },
|
||||||
|
{ name: 'ChatModification', path: '/type-aliases/ChatModification', type: 'type', category: 'Chat Types' },
|
||||||
|
{ name: 'ChatMutation', path: '/type-aliases/ChatMutation', type: 'type', category: 'Chat Types' },
|
||||||
|
|
||||||
|
// History sync functions
|
||||||
|
{ name: 'downloadAndProcessHistorySyncNotification', path: '/functions/downloadAndProcessHistorySyncNotification', type: 'function', category: 'History Functions' },
|
||||||
|
{ name: 'processHistoryMessage', path: '/functions/processHistoryMessage', type: 'function', category: 'History Functions' },
|
||||||
|
{ name: 'getHistoryMsg', path: '/functions/getHistoryMsg', type: 'function', category: 'History Functions' },
|
||||||
|
{ name: 'downloadHistory', path: '/functions/downloadHistory', type: 'function', category: 'History Functions' },
|
||||||
|
|
||||||
|
// Socket configuration for history
|
||||||
|
{ name: 'WASocket', path: '/type-aliases/WASocket', type: 'type', category: 'Socket' },
|
||||||
|
{ name: 'SocketConfig', path: '/type-aliases/SocketConfig', type: 'type', category: 'Socket' },
|
||||||
|
{ name: 'UserFacingSocketConfig', path: '/type-aliases/UserFacingSocketConfig', type: 'type', category: 'Socket' },
|
||||||
|
|
||||||
|
// Events related to messages/history
|
||||||
|
{ name: 'BaileysEventMap', path: '/type-aliases/BaileysEventMap', type: 'type', category: 'Events' },
|
||||||
|
{ name: 'BaileysEventEmitter', path: '/interfaces/BaileysEventEmitter', type: 'interface', category: 'Events' },
|
||||||
|
{ name: 'MessageUpsertType', path: '/type-aliases/MessageUpsertType', type: 'type', category: 'Events' },
|
||||||
|
|
||||||
|
// Message content extraction
|
||||||
|
{ name: 'extractMessageContent', path: '/functions/extractMessageContent', type: 'function', category: 'Message Utils' },
|
||||||
|
{ name: 'getContentType', path: '/functions/getContentType', type: 'function', category: 'Message Utils' },
|
||||||
|
{ name: 'normalizeMessageContent', path: '/functions/normalizeMessageContent', type: 'function', category: 'Message Utils' },
|
||||||
|
|
||||||
|
// Variables related to history
|
||||||
|
{ name: 'PROCESSABLE_HISTORY_TYPES', path: '/variables/PROCESSABLE_HISTORY_TYPES', type: 'variable', category: 'Constants' },
|
||||||
|
]
|
||||||
|
|
||||||
|
async function fetchPage(url: string): Promise<string> {
|
||||||
|
const response = await fetch(url)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${url}: ${response.status}`)
|
||||||
|
}
|
||||||
|
return response.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractContent(html: string): string {
|
||||||
|
// Remove script tags and style tags
|
||||||
|
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||||
|
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
||||||
|
html = html.replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, '')
|
||||||
|
html = html.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
|
||||||
|
html = html.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
|
||||||
|
html = html.replace(/<aside[^>]*>[\s\S]*?<\/aside>/gi, '')
|
||||||
|
|
||||||
|
// Extract main/article content
|
||||||
|
let mainMatch = html.match(/<main[^>]*>([\s\S]*?)<\/main>/i)
|
||||||
|
if (!mainMatch) {
|
||||||
|
mainMatch = html.match(/<article[^>]*>([\s\S]*?)<\/article>/i)
|
||||||
|
}
|
||||||
|
const content = mainMatch ? mainMatch[1] : html
|
||||||
|
|
||||||
|
// Convert HTML to markdown-like text
|
||||||
|
let text = content
|
||||||
|
// Headers
|
||||||
|
.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, '\n# $1\n')
|
||||||
|
.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, '\n## $1\n')
|
||||||
|
.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, '\n### $1\n')
|
||||||
|
.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, '\n#### $1\n')
|
||||||
|
// Code blocks
|
||||||
|
.replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, '\n```typescript\n$1\n```\n')
|
||||||
|
.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, '`$1`')
|
||||||
|
// Lists
|
||||||
|
.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, '- $1\n')
|
||||||
|
.replace(/<ul[^>]*>/gi, '\n')
|
||||||
|
.replace(/<\/ul>/gi, '\n')
|
||||||
|
// Paragraphs
|
||||||
|
.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, '\n$1\n')
|
||||||
|
// Links - keep only text for cleaner output
|
||||||
|
.replace(/<a[^>]*>([\s\S]*?)<\/a>/gi, '$1')
|
||||||
|
// Bold/Italic
|
||||||
|
.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, '**$1**')
|
||||||
|
.replace(/<em[^>]*>([\s\S]*?)<\/em>/gi, '*$1*')
|
||||||
|
// Line breaks
|
||||||
|
.replace(/<br\s*\/?>/gi, '\n')
|
||||||
|
// Divs and spans
|
||||||
|
.replace(/<div[^>]*>/gi, '\n')
|
||||||
|
.replace(/<\/div>/gi, '\n')
|
||||||
|
.replace(/<span[^>]*>/gi, '')
|
||||||
|
.replace(/<\/span>/gi, '')
|
||||||
|
// Tables (simplified)
|
||||||
|
.replace(/<table[^>]*>/gi, '\n')
|
||||||
|
.replace(/<\/table>/gi, '\n')
|
||||||
|
.replace(/<tr[^>]*>/gi, '')
|
||||||
|
.replace(/<\/tr>/gi, '\n')
|
||||||
|
.replace(/<td[^>]*>([\s\S]*?)<\/td>/gi, '| $1 ')
|
||||||
|
.replace(/<th[^>]*>([\s\S]*?)<\/th>/gi, '| **$1** ')
|
||||||
|
// Remove remaining tags
|
||||||
|
.replace(/<[^>]+>/g, '')
|
||||||
|
// Decode HTML entities
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
// Clean up whitespace
|
||||||
|
.replace(/\n\s*\n\s*\n/g, '\n\n')
|
||||||
|
.replace(/^\s+|\s+$/g, '')
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeSection(section: DocSection): Promise<string> {
|
||||||
|
const url = `${BASE_URL}${section.path}`
|
||||||
|
console.log(`Fetching ${section.name}...`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const html = await fetchPage(url)
|
||||||
|
const content = extractContent(html)
|
||||||
|
|
||||||
|
return `
|
||||||
|
---
|
||||||
|
|
||||||
|
## ${section.type.charAt(0).toUpperCase() + section.type.slice(1)}: ${section.name}
|
||||||
|
|
||||||
|
**Source:** ${url}
|
||||||
|
|
||||||
|
${content}
|
||||||
|
`
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching ${section.name}:`, (error as Error).message)
|
||||||
|
return `
|
||||||
|
---
|
||||||
|
|
||||||
|
## ${section.type.charAt(0).toUpperCase() + section.type.slice(1)}: ${section.name}
|
||||||
|
|
||||||
|
**Source:** ${url}
|
||||||
|
|
||||||
|
*Error: Could not fetch documentation*
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Starting Baileys message history documentation scrape...\n')
|
||||||
|
|
||||||
|
// Group sections by category
|
||||||
|
const categories = [...new Set(SECTIONS.map(s => s.category))]
|
||||||
|
|
||||||
|
const markdown: string[] = [
|
||||||
|
`# Baileys Message History API Reference
|
||||||
|
|
||||||
|
> Documentation for fetching and managing message history in WhatsApp Nucleo
|
||||||
|
> Source: https://baileys.wiki
|
||||||
|
> Generated: ${new Date().toISOString()}
|
||||||
|
|
||||||
|
This document contains the Baileys API documentation specifically for:
|
||||||
|
- Fetching message history from chats
|
||||||
|
- History sync functionality
|
||||||
|
- Message events and types
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
${categories.map(cat => {
|
||||||
|
const items = SECTIONS.filter(s => s.category === cat)
|
||||||
|
return `### ${cat}
|
||||||
|
${items.map(s => `- [${s.name}](#${s.type}-${s.name.toLowerCase()})`).join('\n')}`
|
||||||
|
}).join('\n\n')}
|
||||||
|
|
||||||
|
## Quick Reference: How to Fetch Messages
|
||||||
|
|
||||||
|
### Using fetchMessageHistory (WASocket method)
|
||||||
|
|
||||||
|
\`\`\`typescript
|
||||||
|
// The WASocket has a fetchMessageHistory method:
|
||||||
|
sock.fetchMessageHistory(
|
||||||
|
count: number, // Number of messages to fetch
|
||||||
|
oldestMsgKey: WAMessageKey, // Key of the oldest message you have
|
||||||
|
oldestMsgTimestamp: number // Timestamp of the oldest message
|
||||||
|
)
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Listening to History Sync Events
|
||||||
|
|
||||||
|
\`\`\`typescript
|
||||||
|
sock.ev.on('messaging-history.set', ({ chats, contacts, messages, isLatest }) => {
|
||||||
|
// messages: WAMessage[] - reverse chronologically sorted
|
||||||
|
// chats: Chat[] - chat metadata
|
||||||
|
// isLatest: boolean - if this is the most recent sync
|
||||||
|
})
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
\`\`\`typescript
|
||||||
|
const sock = makeWASocket({
|
||||||
|
// ... other config
|
||||||
|
syncFullHistory: true, // Request full history from phone
|
||||||
|
shouldSyncHistoryMessage: (msg) => true, // Control which messages to sync
|
||||||
|
getMessage: async (key) => {
|
||||||
|
// Implement to fetch message from your store
|
||||||
|
// Required for message retries
|
||||||
|
}
|
||||||
|
})
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
`
|
||||||
|
]
|
||||||
|
|
||||||
|
// Process each category
|
||||||
|
for (const category of categories) {
|
||||||
|
markdown.push(`\n# ${category}\n`)
|
||||||
|
|
||||||
|
const sections = SECTIONS.filter(s => s.category === category)
|
||||||
|
for (const section of sections) {
|
||||||
|
const content = await scrapeSection(section)
|
||||||
|
markdown.push(content)
|
||||||
|
|
||||||
|
// Small delay to be nice to the server
|
||||||
|
await new Promise(r => setTimeout(r, 300))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
const outputPath = './docs/baileys-message-history-reference.md'
|
||||||
|
fs.writeFileSync(outputPath, markdown.join('\n'))
|
||||||
|
|
||||||
|
console.log(`\nDocumentation saved to ${outputPath}`)
|
||||||
|
console.log(`Total sections: ${SECTIONS.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
@@ -26,13 +26,31 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 400, message: 'Instance not connected' })
|
throw createError({ statusCode: 400, message: 'Instance not connected' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar que el método existe
|
||||||
|
if (typeof socket.fetchMessageHistory !== 'function') {
|
||||||
|
console.error('[History] fetchMessageHistory method not available on socket')
|
||||||
|
console.error('[History] Available methods:', Object.keys(socket).filter(k => typeof (socket as any)[k] === 'function').join(', '))
|
||||||
|
throw createError({ statusCode: 500, message: 'fetchMessageHistory not available on this Baileys version' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log de parámetros
|
||||||
|
console.log('[History] Requesting history with params:', {
|
||||||
|
instanceId,
|
||||||
|
count,
|
||||||
|
oldestMsgKey: oldestMsgKey ? JSON.stringify(oldestMsgKey) : 'undefined',
|
||||||
|
oldestMsgTimestamp: oldestMsgTimestamp || 'undefined'
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await socket.fetchMessageHistory(count, oldestMsgKey, oldestMsgTimestamp)
|
const result = await socket.fetchMessageHistory(count, oldestMsgKey, oldestMsgTimestamp)
|
||||||
|
console.log('[History] fetchMessageHistory returned:', result)
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
result,
|
||||||
message: `Requested ${count} messages from history. Check messaging-history.set event for results.`
|
message: `Requested ${count} messages from history. Check messaging-history.set event for results.`
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('[History] fetchMessageHistory error:', error)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
message: `Failed to fetch message history: ${(error as Error).message}`
|
message: `Failed to fetch message history: ${(error as Error).message}`
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ export default defineEventHandler(async (event) => {
|
|||||||
},
|
},
|
||||||
'presence.update': (data: any) => {
|
'presence.update': (data: any) => {
|
||||||
res.write(`event: presence.update\ndata: ${JSON.stringify(data)}\n\n`)
|
res.write(`event: presence.update\ndata: ${JSON.stringify(data)}\n\n`)
|
||||||
|
},
|
||||||
|
'history.synced': (data: any) => {
|
||||||
|
res.write(`event: history.synced\ndata: ${JSON.stringify(data)}\n\n`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -473,6 +473,15 @@ class BaileysManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[BaileysManager] History sync processed`)
|
console.log(`[BaileysManager] History sync processed`)
|
||||||
|
|
||||||
|
// Emitir evento SSE al frontend para notificar que llegó el historial
|
||||||
|
this.emit('history.synced', {
|
||||||
|
instanceId,
|
||||||
|
chatsCount: chats?.length || 0,
|
||||||
|
contactsCount: contacts?.length || 0,
|
||||||
|
messagesCount: messages?.length || 0,
|
||||||
|
syncType
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user