UNPKG

@ai-growth/n8n-nodes-wordpress

Version:

n8n node for WordPress integration with AI GROWTH - SEO WP plugin

700 lines (698 loc) 34.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContentService = void 0; const ErrorUtils_1 = require("../utils/ErrorUtils"); const SeoService_1 = require("./SeoService"); const FaqService_1 = require("./FaqService"); const CtaService_1 = require("./CtaService"); /** * Serviço para gerenciamento de conteúdo do WordPress */ class ContentService { /** * Construtor do serviço * @param client Cliente WordPress */ constructor(client) { this.parameterSupportCache = new Map(); this.client = client; this.seoService = new SeoService_1.SeoService(client); this.faqService = new FaqService_1.FaqService(client, this.seoService); this.ctaService = new CtaService_1.CtaService(client, this.seoService); } /** * Valida se um endpoint está disponível antes de fazer a requisição * @param endpoint Endpoint a ser validado * @param method Método HTTP (padrão: GET) * @returns True se o endpoint estiver disponível */ async validateEndpointAvailability(endpoint, method = 'GET') { try { const isValid = await this.client.validateEndpoint(endpoint, method); if (!isValid) { // Tentar descobrir rotas se a validação falhar const discovery = await this.client.discoverRoutes(false); if (!discovery.success) { // Se a descoberta de rotas falhar, assumir que o endpoint pode estar disponível // para manter compatibilidade com versões antigas do WordPress return true; } return false; } return true; } catch (error) { // Em caso de erro na validação, assumir que o endpoint está disponível // para evitar bloquear operações em casos onde a descoberta de rotas não funciona return true; } } /** * Testa se um endpoint suporta parâmetros específicos * @param endpoint Endpoint a ser testado * @param parameters Parâmetros a serem testados * @returns True se os parâmetros são suportados */ async testParameterSupport(endpoint, parameters) { const cacheKey = `${endpoint}_${parameters.join(',')}`; // Verificar cache primeiro if (this.parameterSupportCache.has(cacheKey)) { return this.parameterSupportCache.get(cacheKey); } try { // Fazer uma requisição de teste com parâmetros vazios para verificar se são aceitos const testParams = {}; parameters.forEach(param => { testParams[param] = ''; // Usar valores vazios para teste }); // Fazer requisição de teste com per_page=1 para minimizar dados transferidos testParams.per_page = '1'; testParams.page = '1'; await this.client.get(endpoint, testParams); // Se chegou aqui, os parâmetros são suportados this.parameterSupportCache.set(cacheKey, true); return true; } catch (error) { // Verificar se o erro é especificamente sobre parâmetros inválidos if (error instanceof ErrorUtils_1.WordPressError) { const errorMessage = error.message.toLowerCase(); const isParameterError = parameters.some(param => errorMessage.includes(param.toLowerCase()) && (errorMessage.includes('inválido') || errorMessage.includes('invalid'))); if (isParameterError) { // Parâmetros não são suportados this.parameterSupportCache.set(cacheKey, false); return false; } } // Para outros tipos de erro, assumir que os parâmetros são suportados // (o erro pode ser por outras razões, como autenticação) this.parameterSupportCache.set(cacheKey, true); return true; } } /** * Filtra posts no lado do cliente quando a filtragem no servidor falha * @param posts Posts a serem filtrados * @param categories IDs das categorias para filtrar * @param tags IDs das tags para filtrar * @returns Posts filtrados */ filterPostsClientSide(posts, categories, tags) { return posts.filter(post => { // Filtrar por categorias se especificado if (categories.length > 0) { const postCategories = post.categories || []; const hasMatchingCategory = categories.some(catId => postCategories.includes(catId)); if (!hasMatchingCategory) { return false; } } // Filtrar por tags se especificado if (tags.length > 0) { const postTags = post.tags || []; const hasMatchingTag = tags.some(tagId => postTags.includes(tagId)); if (!hasMatchingTag) { return false; } } return true; }); } /** * Cria uma mensagem de erro amigável quando um endpoint não está disponível * @param endpoint Endpoint que não foi encontrado * @param method Método HTTP * @returns Mensagem de erro */ createEndpointNotAvailableError(endpoint, method = 'GET') { const message = `WordPress REST API endpoint '${endpoint}' is not available or does not support the ${method} method. Possible causes: • The endpoint may not exist in your WordPress version • Required plugins may not be installed or activated • The WordPress REST API may be disabled • The API version (${this.client.getApiVersion()}) may not support this endpoint Suggestions: • Verify that the WordPress REST API is enabled • Check if required plugins are installed and active • Try using a different API version if available • Ensure your WordPress installation is up to date`; return new ErrorUtils_1.WordPressError(message, ErrorUtils_1.WordPressErrorType.ROUTE_NOT_FOUND, 404); } /** * Obtém posts do WordPress * @param options Opções da consulta * @returns Array de posts */ async getPosts(options = {}) { const { page = 1, perPage = 10, status = 'publish', search = '', categories = [], tags = [], includeSeoMetadata = false, includeFaqs = false, includeCta = false, } = options; try { // Validar se o endpoint 'posts' está disponível const isEndpointAvailable = await this.validateEndpointAvailability('posts', 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError('posts', 'GET'); } // Montar parâmetros básicos da consulta const params = { page: page.toString(), per_page: perPage.toString(), status, }; // Adicionar termo de busca se fornecido if (search) { params.search = search; } // Verificar se precisamos testar suporte a parâmetros de taxonomia const needsTaxonomyParams = categories.length > 0 || tags.length > 0; let useServerSideFiltering = true; if (needsTaxonomyParams) { // Testar suporte aos parâmetros categories e tags const taxonomyParams = []; if (categories.length > 0) taxonomyParams.push('categories'); if (tags.length > 0) taxonomyParams.push('tags'); useServerSideFiltering = await this.testParameterSupport('posts', taxonomyParams); } let posts; if (useServerSideFiltering && needsTaxonomyParams) { // Tentar filtragem no servidor primeiro try { // Adicionar filtro de categorias se fornecido if (categories.length > 0) { params.categories = categories.join(','); } // Adicionar filtro de tags se fornecido if (tags.length > 0) { params.tags = tags.join(','); } // Fazer a requisição com parâmetros de taxonomia posts = await this.client.get('posts', params); } catch (serverError) { // Verificar se o erro é especificamente sobre parâmetros inválidos if (serverError instanceof ErrorUtils_1.WordPressError) { const errorMessage = serverError.message.toLowerCase(); const isParameterError = ((errorMessage.includes('categories') || errorMessage.includes('tags')) && (errorMessage.includes('inválido') || errorMessage.includes('invalid') || errorMessage.includes('parâmetro'))); if (isParameterError) { // Marcar parâmetros como não suportados no cache const taxonomyParams = []; if (categories.length > 0) taxonomyParams.push('categories'); if (tags.length > 0) taxonomyParams.push('tags'); const cacheKey = `posts_${taxonomyParams.join(',')}`; this.parameterSupportCache.set(cacheKey, false); // Fazer fallback para filtragem no cliente useServerSideFiltering = false; } else { // Re-lançar outros tipos de erro throw serverError; } } else { throw serverError; } } } if (!useServerSideFiltering || !needsTaxonomyParams) { // Fazer requisição sem parâmetros de taxonomia const basicParams = { ...params }; delete basicParams.categories; delete basicParams.tags; if (!useServerSideFiltering && needsTaxonomyParams) { // Para filtragem no cliente, buscar mais posts para garantir resultados suficientes // Aumentar per_page temporariamente, mas limitar a um máximo razoável const originalPerPage = parseInt(basicParams.per_page); const maxFetchSize = Math.min(originalPerPage * 3, 100); // Máximo de 100 posts basicParams.per_page = maxFetchSize.toString(); basicParams.page = '1'; // Sempre buscar da primeira página para filtragem } posts = await this.client.get('posts', basicParams); // Aplicar filtragem no cliente se necessário if (!useServerSideFiltering && needsTaxonomyParams && posts && Array.isArray(posts)) { posts = this.filterPostsClientSide(posts, categories, tags); // Aplicar paginação manual após filtragem const startIndex = (page - 1) * perPage; const endIndex = startIndex + perPage; posts = posts.slice(startIndex, endIndex); } } // Se não há posts, retornar array vazio if (!posts || !Array.isArray(posts)) { return []; } // Processar os posts e enriquecer com metadados conforme solicitado const enrichedPosts = await this.enrichPosts(posts, { includeSeoMetadata, includeFaqs, includeCta, }); return enrichedPosts; } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress posts: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Obtém páginas do WordPress * @param options Opções da consulta * @returns Array de páginas */ async getPages(options = {}) { const { page = 1, perPage = 10, status = 'publish', search = '', categories = [], tags = [], includeSeoMetadata = false, includeFaqs = false, includeCta = false, } = options; try { // Validar se o endpoint 'pages' está disponível const isEndpointAvailable = await this.validateEndpointAvailability('pages', 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError('pages', 'GET'); } // Montar parâmetros básicos da consulta const params = { page: page.toString(), per_page: perPage.toString(), status, }; // Adicionar termo de busca se fornecido if (search) { params.search = search; } // Verificar se precisamos testar suporte a parâmetros de taxonomia // Nota: Páginas geralmente não suportam categories/tags, mas alguns temas/plugins podem habilitar const needsTaxonomyParams = categories.length > 0 || tags.length > 0; let useServerSideFiltering = true; if (needsTaxonomyParams) { // Testar suporte aos parâmetros categories e tags para páginas const taxonomyParams = []; if (categories.length > 0) taxonomyParams.push('categories'); if (tags.length > 0) taxonomyParams.push('tags'); useServerSideFiltering = await this.testParameterSupport('pages', taxonomyParams); } let pages; if (useServerSideFiltering && needsTaxonomyParams) { // Tentar filtragem no servidor primeiro try { // Adicionar filtro de categorias se fornecido if (categories.length > 0) { params.categories = categories.join(','); } // Adicionar filtro de tags se fornecido if (tags.length > 0) { params.tags = tags.join(','); } // Fazer a requisição com parâmetros de taxonomia pages = await this.client.get('pages', params); } catch (serverError) { // Verificar se o erro é especificamente sobre parâmetros inválidos if (serverError instanceof ErrorUtils_1.WordPressError) { const errorMessage = serverError.message.toLowerCase(); const isParameterError = ((errorMessage.includes('categories') || errorMessage.includes('tags')) && (errorMessage.includes('inválido') || errorMessage.includes('invalid') || errorMessage.includes('parâmetro'))); if (isParameterError) { // Marcar parâmetros como não suportados no cache const taxonomyParams = []; if (categories.length > 0) taxonomyParams.push('categories'); if (tags.length > 0) taxonomyParams.push('tags'); const cacheKey = `pages_${taxonomyParams.join(',')}`; this.parameterSupportCache.set(cacheKey, false); // Fazer fallback para filtragem no cliente useServerSideFiltering = false; } else { // Re-lançar outros tipos de erro throw serverError; } } else { throw serverError; } } } if (!useServerSideFiltering || !needsTaxonomyParams) { // Fazer requisição sem parâmetros de taxonomia const basicParams = { ...params }; delete basicParams.categories; delete basicParams.tags; if (!useServerSideFiltering && needsTaxonomyParams) { // Para filtragem no cliente, buscar mais páginas para garantir resultados suficientes const originalPerPage = parseInt(basicParams.per_page); const maxFetchSize = Math.min(originalPerPage * 3, 100); // Máximo de 100 páginas basicParams.per_page = maxFetchSize.toString(); basicParams.page = '1'; // Sempre buscar da primeira página para filtragem } pages = await this.client.get('pages', basicParams); // Aplicar filtragem no cliente se necessário if (!useServerSideFiltering && needsTaxonomyParams && pages && Array.isArray(pages)) { pages = this.filterPostsClientSide(pages, categories, tags); // Aplicar paginação manual após filtragem const startIndex = (page - 1) * perPage; const endIndex = startIndex + perPage; pages = pages.slice(startIndex, endIndex); } } // Se não há páginas, retornar array vazio if (!pages || !Array.isArray(pages)) { return []; } // Processar as páginas e enriquecer com metadados conforme solicitado const enrichedPages = await this.enrichPosts(pages, { includeSeoMetadata, includeFaqs, includeCta, }); return enrichedPages; } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress pages: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Obtém um post específico pelo ID * @param id ID do post * @param options Opções da consulta * @returns Post ou null se não encontrado */ async getPost(id, options = {}) { const { includeSeoMetadata = false, includeFaqs = false, includeCta = false, } = options; try { // Validar se o endpoint específico do post está disponível const endpoint = `posts/${id}`; const isEndpointAvailable = await this.validateEndpointAvailability(endpoint, 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError(endpoint, 'GET'); } // Fazer a requisição const post = await this.client.get(endpoint); // Se o post não foi encontrado, retornar null if (!post) { return null; } // Processar o post e enriquecer com metadados conforme solicitado const [enrichedPost] = await this.enrichPosts([post], { includeSeoMetadata, includeFaqs, includeCta, }); return enrichedPost; } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { // Se é um erro 404, melhorar a mensagem if (error.type === ErrorUtils_1.WordPressErrorType.NOT_FOUND) { throw new ErrorUtils_1.WordPressError(`Post with ID ${id} not found. Please verify the post ID exists and you have permission to access it.`, ErrorUtils_1.WordPressErrorType.NOT_FOUND, error.statusCode, error.originalError); } throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress post with ID ${id}: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Obtém uma página específica pelo ID * @param id ID da página * @param options Opções da consulta * @returns Página ou null se não encontrada */ async getPage(id, options = {}) { const { includeSeoMetadata = false, includeFaqs = false, includeCta = false, } = options; try { // Validar se o endpoint específico da página está disponível const endpoint = `pages/${id}`; const isEndpointAvailable = await this.validateEndpointAvailability(endpoint, 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError(endpoint, 'GET'); } // Fazer a requisição const page = await this.client.get(endpoint); // Se a página não foi encontrada, retornar null if (!page) { return null; } // Processar a página e enriquecer com metadados conforme solicitado const [enrichedPage] = await this.enrichPosts([page], { includeSeoMetadata, includeFaqs, includeCta, }); return enrichedPage; } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { // Se é um erro 404, melhorar a mensagem if (error.type === ErrorUtils_1.WordPressErrorType.NOT_FOUND) { throw new ErrorUtils_1.WordPressError(`Page with ID ${id} not found. Please verify the page ID exists and you have permission to access it.`, ErrorUtils_1.WordPressErrorType.NOT_FOUND, error.statusCode, error.originalError); } throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress page with ID ${id}: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Obtém categorias do WordPress * @param options Opções da consulta * @returns Array de categorias */ async getCategories(options = {}) { const { page = 1, perPage = 10, search = '', } = options; try { // Validar se o endpoint 'categories' está disponível const isEndpointAvailable = await this.validateEndpointAvailability('categories', 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError('categories', 'GET'); } // Montar parâmetros da consulta const params = { page: page.toString(), per_page: perPage.toString(), }; // Adicionar termo de busca se fornecido if (search) { params.search = search; } // Fazer a requisição const categories = await this.client.get('categories', params); // Se não há categorias, retornar array vazio if (!categories || !Array.isArray(categories)) { return []; } // Mapear os dados para o formato da interface return categories.map((category) => ({ id: category.id, name: category.name, slug: category.slug, })); } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress categories: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Obtém tags do WordPress * @param options Opções da consulta * @returns Array de tags */ async getTags(options = {}) { const { page = 1, perPage = 10, search = '', } = options; try { // Validar se o endpoint 'tags' está disponível const isEndpointAvailable = await this.validateEndpointAvailability('tags', 'GET'); if (!isEndpointAvailable) { throw this.createEndpointNotAvailableError('tags', 'GET'); } // Montar parâmetros da consulta const params = { page: page.toString(), per_page: perPage.toString(), }; // Adicionar termo de busca se fornecido if (search) { params.search = search; } // Fazer a requisição const tags = await this.client.get('tags', params); // Se não há tags, retornar array vazio if (!tags || !Array.isArray(tags)) { return []; } // Mapear os dados para o formato da interface return tags.map((tag) => ({ id: tag.id, name: tag.name, slug: tag.slug, })); } catch (error) { if (error instanceof ErrorUtils_1.WordPressError) { throw error; } throw new ErrorUtils_1.WordPressError(`Failed to fetch WordPress tags: ${error.message}`, ErrorUtils_1.WordPressErrorType.SERVER, undefined, error); } } /** * Enriquece os posts com metadados do AI GROWTH - SEO WP * @param posts Posts a serem enriquecidos * @param options Opções de enriquecimento * @returns Posts enriquecidos */ async enrichPosts(posts, options) { const { includeSeoMetadata, includeFaqs, includeCta } = options; // Mapear os posts para o formato da interface const mappedPosts = posts.map((post) => { var _a, _b, _c; return ({ id: post.id, title: ((_a = post.title) === null || _a === void 0 ? void 0 : _a.rendered) || '', content: ((_b = post.content) === null || _b === void 0 ? void 0 : _b.rendered) || '', status: post.status || 'publish', categories: post.categories || [], tags: post.tags || [], image_url: post.featured_media && typeof post.featured_media === 'number' ? '' // Não temos acesso direto à URL da imagem pelo ID : ((_c = post.featured_media) === null || _c === void 0 ? void 0 : _c.source_url) || '', }); }); // Se não é para incluir metadados, retorna os posts mapeados if (!includeSeoMetadata && !includeFaqs && !includeCta) { return mappedPosts; } // Verificar disponibilidade do plugin const pluginInfo = await this.seoService.checkPluginAvailability(); const isPluginActive = pluginInfo.status === 'active'; console.log('[ContentService] enrichPosts - Plugin status:', { isPluginActive, pluginInfo, includeSeoMetadata, includeFaqs, includeCta }); // Enriquecer os posts com metadados const enrichedPosts = await Promise.all(mappedPosts.map(async (post) => { var _a; const metadata = {}; const originalPost = posts.find(p => p.id === post.id); // Obter metadados SEO se solicitado if (includeSeoMetadata && post.id) { if (isPluginActive) { // Plugin ativo - usar API do plugin try { const seoMetadata = await this.seoService.getPostSeoMetadata(post.id); if (seoMetadata) { metadata.meta_title = seoMetadata.meta_title; metadata.meta_description = seoMetadata.meta_description; metadata.meta_keywords = seoMetadata.meta_keywords; metadata.og_title = seoMetadata.og_title; metadata.og_description = seoMetadata.og_description; metadata.twitter_title = seoMetadata.twitter_title; metadata.twitter_description = seoMetadata.twitter_description; } } catch (error) { console.error('[ContentService] Error fetching SEO metadata from plugin:', error); } } else { // Plugin não ativo - extrair meta fields do response do WordPress console.log('[ContentService] Plugin not active, extracting meta fields from WordPress response'); if (originalPost === null || originalPost === void 0 ? void 0 : originalPost.meta) { console.log('[ContentService] Original post meta fields:', { meta_title: originalPost.meta._ai_growth_seo_meta_title, meta_description: originalPost.meta._ai_growth_seo_meta_description, meta_keywords: originalPost.meta._ai_growth_seo_meta_keywords, og_title: originalPost.meta._ai_growth_seo_og_title, og_description: originalPost.meta._ai_growth_seo_og_description, twitter_title: originalPost.meta._ai_growth_seo_twitter_title, twitter_description: originalPost.meta._ai_growth_seo_twitter_description }); // Extrair meta fields do response do WordPress metadata.meta_title = originalPost.meta._ai_growth_seo_meta_title || ''; metadata.meta_description = originalPost.meta._ai_growth_seo_meta_description || ''; metadata.meta_keywords = originalPost.meta._ai_growth_seo_meta_keywords || ''; metadata.og_title = originalPost.meta._ai_growth_seo_og_title || ''; metadata.og_description = originalPost.meta._ai_growth_seo_og_description || ''; metadata.twitter_title = originalPost.meta._ai_growth_seo_twitter_title || ''; metadata.twitter_description = originalPost.meta._ai_growth_seo_twitter_description || ''; } else { console.log('[ContentService] No meta fields found in original post'); } } } // Obter FAQs se solicitado if (includeFaqs && post.id) { if (isPluginActive) { try { const faqs = await this.faqService.getPostFaqs(post.id); if (faqs && faqs.length > 0) { metadata.faq = faqs; } } catch (error) { console.error('[ContentService] Error fetching FAQs:', error); } } else { // Extrair FAQs do response do WordPress se disponível if ((_a = originalPost === null || originalPost === void 0 ? void 0 : originalPost.meta) === null || _a === void 0 ? void 0 : _a._ai_growth_seo_faq_items) { metadata.faq = originalPost.meta._ai_growth_seo_faq_items; } } } // Obter CTA se solicitado if (includeCta && post.id) { if (isPluginActive) { try { const cta = await this.ctaService.getPostCta(post.id); if (cta) { metadata.cta = cta; } } catch (error) { console.error('[ContentService] Error fetching CTA:', error); } } else { // Extrair CTA do response do WordPress se disponível if (originalPost === null || originalPost === void 0 ? void 0 : originalPost.meta) { const ctaData = { text: originalPost.meta._ai_growth_seo_cta_button_text || '', url: originalPost.meta._ai_growth_seo_cta_link_url || '', style: originalPost.meta._ai_growth_seo_cta_button_style || 'primary', target: originalPost.meta._ai_growth_seo_cta_target || '_self', enabled: originalPost.meta._ai_growth_seo_cta_enabled || false, position: originalPost.meta._ai_growth_seo_cta_position || 'bottom', description: originalPost.meta._ai_growth_seo_cta_description || '', icon: originalPost.meta._ai_growth_seo_cta_icon || '' }; // Só incluir CTA se tiver dados relevantes if (ctaData.text || ctaData.url) { metadata.cta = ctaData; } } } } console.log('[ContentService] Final metadata for post', post.id, ':', metadata); // Adicionar metadados ao post return { ...post, ...metadata }; })); return enrichedPosts; } } exports.ContentService = ContentService; //# sourceMappingURL=ContentService.js.map