"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SEOAnalyzerService = void 0; const axios_1 = __importDefault(require("axios")); const url_1 = require("url"); const logger_1 = require("../lib/logger"); class SEOAnalyzerService { async analyzeSEO(url) { logger_1.logger.info(`Starting SEO analysis for: ${url}`); const result = { url, flags: { robotsTxtStatus: 'not_found', robotsTxtRules: { status: 'not_found', rules: [], sitemaps: [] }, sitemapPresent: false, noindex: false, nofollow: false, hasTitle: false, hasDescription: false, openGraphPresent: false, twitterCardPresent: false, }, metaTags: { openGraph: {}, twitter: {}, }, recommendations: [], warnings: [], score: 0, }; try { const robotsAnalysis = await this.analyzeRobotsTxt(url); result.flags.robotsTxtStatus = robotsAnalysis.status; result.flags.robotsTxtRules = robotsAnalysis; result.flags.sitemapPresent = robotsAnalysis.sitemaps.length > 0; const pageAnalysis = await this.analyzePageContent(url); result.metaTags = pageAnalysis.metaTags; result.flags = { ...result.flags, ...pageAnalysis.flags }; this.generateSEORecommendations(result); logger_1.logger.info(`SEO analysis completed for: ${url}`, { score: result.score, robotsStatus: result.flags.robotsTxtStatus, hasTitle: result.flags.hasTitle, noindex: result.flags.noindex }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown SEO analysis error'; result.warnings.push(`SEO analysis failed: ${errorMessage}`); logger_1.logger.error(`SEO analysis failed for ${url}:`, error); } return result; } async analyzeRobotsTxt(url) { try { const parsedUrl = new url_1.URL(url); const robotsUrl = `${parsedUrl.protocol}//${parsedUrl.host}/robots.txt`; const response = await axios_1.default.get(robotsUrl, { timeout: 5000, headers: { 'User-Agent': 'RedirectIntelligence-Bot/2.0', }, }); const robotsContent = response.data; return this.parseRobotsTxt(robotsContent); } catch (error) { if (axios_1.default.isAxiosError(error) && error.response?.status === 404) { return { status: 'not_found', rules: [], sitemaps: [] }; } logger_1.logger.warn(`Failed to fetch robots.txt for ${url}:`, error); return { status: 'error', rules: [], sitemaps: [] }; } } parseRobotsTxt(content) { const lines = content.split('\n').map(line => line.trim()); const rules = []; const sitemaps = []; let currentRule = null; for (const line of lines) { if (line.startsWith('#') || line === '') continue; const [directive, ...valueParts] = line.split(':'); const value = valueParts.join(':').trim(); switch (directive.toLowerCase()) { case 'user-agent': if (currentRule) { rules.push(currentRule); } currentRule = { userAgent: value, allow: [], disallow: [], }; break; case 'allow': if (currentRule) { currentRule.allow.push(value); } break; case 'disallow': if (currentRule) { currentRule.disallow.push(value); } break; case 'crawl-delay': if (currentRule) { currentRule.crawlDelay = parseInt(value) || undefined; } break; case 'sitemap': sitemaps.push(value); break; } } if (currentRule) { rules.push(currentRule); } return { status: 'found', rules, sitemaps, }; } async analyzePageContent(url) { try { const response = await axios_1.default.get(url, { timeout: 10000, headers: { 'User-Agent': 'RedirectIntelligence-Bot/2.0 (SEO Analysis)', }, maxContentLength: 1024 * 1024, }); const html = response.data; return this.parseHTMLContent(html); } catch (error) { logger_1.logger.warn(`Failed to fetch page content for ${url}:`, error); return { metaTags: { openGraph: {}, twitter: {} }, flags: { hasTitle: false, hasDescription: false, noindex: false, nofollow: false, openGraphPresent: false, twitterCardPresent: false, }, }; } } parseHTMLContent(html) { const metaTags = { openGraph: {}, twitter: {}, }; const flags = { hasTitle: false, hasDescription: false, noindex: false, nofollow: false, openGraphPresent: false, twitterCardPresent: false, }; const titleMatch = html.match(/]*>(.*?)<\/title>/is); if (titleMatch) { metaTags.title = this.cleanText(titleMatch[1]); flags.hasTitle = true; flags.titleLength = metaTags.title.length; } const metaTagRegex = /]*?)>/gi; let metaMatch; while ((metaMatch = metaTagRegex.exec(html)) !== null) { const attributes = this.parseAttributes(metaMatch[1]); if (attributes.name === 'description') { metaTags.description = attributes.content; flags.hasDescription = true; flags.descriptionLength = attributes.content?.length || 0; } else if (attributes.name === 'robots') { metaTags.robots = attributes.content; flags.metaRobots = attributes.content; if (attributes.content?.toLowerCase().includes('noindex')) { flags.noindex = true; } if (attributes.content?.toLowerCase().includes('nofollow')) { flags.nofollow = true; } } else if (attributes.name === 'viewport') { metaTags.viewport = attributes.content; } else if (attributes.property?.startsWith('og:')) { const ogProperty = attributes.property.substring(3); metaTags.openGraph[ogProperty] = attributes.content; flags.openGraphPresent = true; } else if (attributes.name?.startsWith('twitter:')) { const twitterProperty = attributes.name.substring(8); metaTags.twitter[twitterProperty] = attributes.content; flags.twitterCardPresent = true; } } const canonicalMatch = html.match(/]*?)rel=['"]canonical['"][^>]*>/i); if (canonicalMatch) { const attributes = this.parseAttributes(canonicalMatch[1]); metaTags.canonical = attributes.href; flags.canonicalUrl = attributes.href; } return { metaTags, flags }; } parseAttributes(attributeString) { const attributes = {}; const attrRegex = /(\w+)=['"]([^'"]*)['"]/g; let match; while ((match = attrRegex.exec(attributeString)) !== null) { attributes[match[1].toLowerCase()] = match[2]; } return attributes; } cleanText(text) { return text.replace(/\s+/g, ' ').trim(); } generateSEORecommendations(result) { let score = 100; if (!result.flags.hasTitle) { result.warnings.push('Missing page title'); result.recommendations.push('Add a descriptive page title'); score -= 15; } else if (result.flags.titleLength) { if (result.flags.titleLength < 30) { result.warnings.push('Title is too short'); result.recommendations.push('Expand title to 30-60 characters for better SEO'); score -= 5; } else if (result.flags.titleLength > 60) { result.warnings.push('Title is too long'); result.recommendations.push('Shorten title to under 60 characters'); score -= 5; } } if (!result.flags.hasDescription) { result.warnings.push('Missing meta description'); result.recommendations.push('Add a meta description (150-160 characters)'); score -= 10; } else if (result.flags.descriptionLength) { if (result.flags.descriptionLength < 120) { result.warnings.push('Meta description is too short'); result.recommendations.push('Expand meta description to 150-160 characters'); score -= 3; } else if (result.flags.descriptionLength > 160) { result.warnings.push('Meta description is too long'); result.recommendations.push('Shorten meta description to under 160 characters'); score -= 3; } } if (result.flags.robotsTxtStatus === 'not_found') { result.recommendations.push('Consider adding a robots.txt file'); score -= 5; } else if (result.flags.robotsTxtStatus === 'error') { result.warnings.push('Robots.txt file has errors'); result.recommendations.push('Fix robots.txt file errors'); score -= 8; } if (!result.flags.sitemapPresent) { result.recommendations.push('Add XML sitemap references to robots.txt'); score -= 5; } if (result.flags.noindex) { result.warnings.push('Page is set to noindex'); result.recommendations.push('Remove noindex if you want this page indexed'); score -= 20; } if (!result.flags.openGraphPresent) { result.recommendations.push('Add Open Graph meta tags for social media sharing'); score -= 5; } if (!result.flags.twitterCardPresent) { result.recommendations.push('Add Twitter Card meta tags for Twitter sharing'); score -= 3; } if (!result.flags.canonicalUrl) { result.recommendations.push('Add canonical URL to prevent duplicate content issues'); score -= 5; } result.score = Math.max(0, score); if (result.score >= 90) { result.recommendations.push('Excellent SEO setup!'); } else if (result.score >= 70) { result.recommendations.push('Good SEO with room for minor improvements'); } else if (result.score >= 50) { result.recommendations.push('SEO needs improvement for better search visibility'); } else { result.recommendations.push('SEO requires immediate attention'); } } async quickSEOCheck(url) { try { const analysis = await this.analyzeSEO(url); const robotsBlocked = analysis.flags.robotsTxtRules.rules.some(rule => rule.userAgent === '*' && rule.disallow.includes('/')); return { noindex: analysis.flags.noindex, nofollow: analysis.flags.nofollow, robotsBlocked, hasTitle: analysis.flags.hasTitle, }; } catch (error) { logger_1.logger.warn(`Quick SEO check failed for ${url}:`, error); return { noindex: false, nofollow: false, robotsBlocked: false, hasTitle: false, }; } } } exports.SEOAnalyzerService = SEOAnalyzerService; //# sourceMappingURL=seo-analyzer.service.js.map