mcp-wordpress 2.6.4 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cache/CacheInvalidation.d.ts +25 -6
- package/dist/cache/CacheInvalidation.d.ts.map +1 -1
- package/dist/cache/CacheInvalidation.js +168 -16
- package/dist/cache/CacheInvalidation.js.map +1 -1
- package/dist/cache/HttpCacheWrapper.d.ts.map +1 -1
- package/dist/cache/HttpCacheWrapper.js +3 -4
- package/dist/cache/HttpCacheWrapper.js.map +1 -1
- package/dist/cache/SEOCacheManager.d.ts +150 -0
- package/dist/cache/SEOCacheManager.d.ts.map +1 -0
- package/dist/cache/SEOCacheManager.js +275 -0
- package/dist/cache/SEOCacheManager.js.map +1 -0
- package/dist/client/SEOWordPressClient.d.ts +164 -0
- package/dist/client/SEOWordPressClient.d.ts.map +1 -0
- package/dist/client/SEOWordPressClient.js +674 -0
- package/dist/client/SEOWordPressClient.js.map +1 -0
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +50 -20
- package/dist/client/api.js.map +1 -1
- package/dist/client/auth.js +19 -19
- package/dist/client/auth.js.map +1 -1
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +14 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/managers/AuthManager.d.ts +39 -0
- package/dist/client/managers/AuthManager.d.ts.map +1 -0
- package/dist/client/managers/AuthManager.js +142 -0
- package/dist/client/managers/AuthManager.js.map +1 -0
- package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
- package/dist/client/managers/AuthenticationManager.js +10 -9
- package/dist/client/managers/AuthenticationManager.js.map +1 -1
- package/dist/client/managers/BaseManager.d.ts.map +1 -1
- package/dist/client/managers/BaseManager.js +12 -0
- package/dist/client/managers/BaseManager.js.map +1 -1
- package/dist/client/managers/ComposedAuthenticationManager.d.ts +94 -0
- package/dist/client/managers/ComposedAuthenticationManager.d.ts.map +1 -0
- package/dist/client/managers/ComposedAuthenticationManager.js +340 -0
- package/dist/client/managers/ComposedAuthenticationManager.js.map +1 -0
- package/dist/client/managers/ComposedManagerFactory.d.ts +104 -0
- package/dist/client/managers/ComposedManagerFactory.d.ts.map +1 -0
- package/dist/client/managers/ComposedManagerFactory.js +180 -0
- package/dist/client/managers/ComposedManagerFactory.js.map +1 -0
- package/dist/client/managers/ComposedRequestManager.d.ts +82 -0
- package/dist/client/managers/ComposedRequestManager.d.ts.map +1 -0
- package/dist/client/managers/ComposedRequestManager.js +260 -0
- package/dist/client/managers/ComposedRequestManager.js.map +1 -0
- package/dist/client/managers/JWTAuthImplementation.d.ts +86 -0
- package/dist/client/managers/JWTAuthImplementation.d.ts.map +1 -0
- package/dist/client/managers/JWTAuthImplementation.js +240 -0
- package/dist/client/managers/JWTAuthImplementation.js.map +1 -0
- package/dist/client/managers/ManagersIndex.d.ts +6 -0
- package/dist/client/managers/ManagersIndex.d.ts.map +1 -0
- package/dist/client/managers/ManagersIndex.js +6 -0
- package/dist/client/managers/ManagersIndex.js.map +1 -0
- package/dist/client/managers/RequestManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.js +5 -3
- package/dist/client/managers/RequestManager.js.map +1 -1
- package/dist/client/managers/composed/MigrationAdapter.d.ts +80 -0
- package/dist/client/managers/composed/MigrationAdapter.d.ts.map +1 -0
- package/dist/client/managers/composed/MigrationAdapter.js +214 -0
- package/dist/client/managers/composed/MigrationAdapter.js.map +1 -0
- package/dist/client/managers/composed/index.d.ts +23 -0
- package/dist/client/managers/composed/index.d.ts.map +1 -0
- package/dist/client/managers/composed/index.js +26 -0
- package/dist/client/managers/composed/index.js.map +1 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts +27 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.js +41 -0
- package/dist/client/managers/implementations/ConfigurationProviderImpl.js.map +1 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts +31 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.js +73 -0
- package/dist/client/managers/implementations/ErrorHandlerImpl.js.map +1 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts +47 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts.map +1 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.js +141 -0
- package/dist/client/managers/implementations/ParameterValidatorImpl.js.map +1 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.d.ts +147 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.d.ts.map +1 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.js +6 -0
- package/dist/client/managers/interfaces/ManagerInterfaces.js.map +1 -0
- package/dist/config/Config.d.ts +30 -0
- package/dist/config/Config.d.ts.map +1 -1
- package/dist/config/Config.js +30 -0
- package/dist/config/Config.js.map +1 -1
- package/dist/config/ConfigurationSchema.d.ts +75 -198
- package/dist/config/ConfigurationSchema.d.ts.map +1 -1
- package/dist/config/ConfigurationSchema.js +17 -17
- package/dist/config/ConfigurationSchema.js.map +1 -1
- package/dist/config/ServerConfiguration.d.ts +2 -2
- package/dist/config/ServerConfiguration.d.ts.map +1 -1
- package/dist/config/ServerConfiguration.js +15 -13
- package/dist/config/ServerConfiguration.js.map +1 -1
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +11 -0
- package/dist/config/index.js.map +1 -0
- package/dist/docs/DocumentationGenerator.js +2 -2
- package/dist/docs/DocumentationGenerator.js.map +1 -1
- package/dist/dxt-entry.js +3 -3
- package/dist/dxt-entry.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -37
- package/dist/index.js.map +1 -1
- package/dist/performance/MetricsCollector.d.ts.map +1 -1
- package/dist/performance/MetricsCollector.js +5 -4
- package/dist/performance/MetricsCollector.js.map +1 -1
- package/dist/security/AISecurityScanner.js +7 -7
- package/dist/security/AISecurityScanner.js.map +1 -1
- package/dist/security/AutomatedRemediation.d.ts.map +1 -1
- package/dist/security/AutomatedRemediation.js +11 -11
- package/dist/security/AutomatedRemediation.js.map +1 -1
- package/dist/security/InputValidator.d.ts +50 -126
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +9 -9
- package/dist/security/InputValidator.js.map +1 -1
- package/dist/security/SecurityCIPipeline.d.ts +47 -5
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
- package/dist/security/SecurityCIPipeline.js +390 -49
- package/dist/security/SecurityCIPipeline.js.map +1 -1
- package/dist/security/SecurityConfigManager.js +10 -10
- package/dist/security/SecurityConfigManager.js.map +1 -1
- package/dist/security/SecurityMonitoring.js +4 -4
- package/dist/security/SecurityMonitoring.js.map +1 -1
- package/dist/security/SecurityReviewer.d.ts.map +1 -1
- package/dist/security/SecurityReviewer.js +13 -6
- package/dist/security/SecurityReviewer.js.map +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/ConnectionTester.js +5 -5
- package/dist/server/ConnectionTester.js.map +1 -1
- package/dist/server/ToolRegistry.d.ts +2 -2
- package/dist/server/ToolRegistry.d.ts.map +1 -1
- package/dist/server/ToolRegistry.js +7 -6
- package/dist/server/ToolRegistry.js.map +1 -1
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -0
- package/dist/tools/BaseToolManager.d.ts.map +1 -1
- package/dist/tools/BaseToolManager.js +11 -11
- package/dist/tools/BaseToolManager.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +7 -7
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +14 -14
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +14 -11
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +12 -12
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +13 -11
- package/dist/tools/performance.js.map +1 -1
- package/dist/tools/posts/PostHandlers.d.ts.map +1 -1
- package/dist/tools/posts/PostHandlers.js +16 -16
- package/dist/tools/posts/PostHandlers.js.map +1 -1
- package/dist/tools/posts/PostToolDefinitions.d.ts.map +1 -1
- package/dist/tools/posts/index.d.ts.map +1 -1
- package/dist/tools/seo/BulkOperations.d.ts +113 -0
- package/dist/tools/seo/BulkOperations.d.ts.map +1 -0
- package/dist/tools/seo/BulkOperations.js +398 -0
- package/dist/tools/seo/BulkOperations.js.map +1 -0
- package/dist/tools/seo/SEOHandlers.d.ts +55 -0
- package/dist/tools/seo/SEOHandlers.d.ts.map +1 -0
- package/dist/tools/seo/SEOHandlers.js +255 -0
- package/dist/tools/seo/SEOHandlers.js.map +1 -0
- package/dist/tools/seo/SEOToolDefinitions.d.ts +59 -0
- package/dist/tools/seo/SEOToolDefinitions.d.ts.map +1 -0
- package/dist/tools/seo/SEOToolDefinitions.js +385 -0
- package/dist/tools/seo/SEOToolDefinitions.js.map +1 -0
- package/dist/tools/seo/SEOTools.d.ts +203 -0
- package/dist/tools/seo/SEOTools.d.ts.map +1 -0
- package/dist/tools/seo/SEOTools.js +708 -0
- package/dist/tools/seo/SEOTools.js.map +1 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts +94 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts.map +1 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.js +402 -0
- package/dist/tools/seo/analyzers/ContentAnalyzer.js.map +1 -0
- package/dist/tools/seo/auditors/SiteAuditor.d.ts +121 -0
- package/dist/tools/seo/auditors/SiteAuditor.d.ts.map +1 -0
- package/dist/tools/seo/auditors/SiteAuditor.js +600 -0
- package/dist/tools/seo/auditors/SiteAuditor.js.map +1 -0
- package/dist/tools/seo/generators/MetaGenerator.d.ts +128 -0
- package/dist/tools/seo/generators/MetaGenerator.d.ts.map +1 -0
- package/dist/tools/seo/generators/MetaGenerator.js +547 -0
- package/dist/tools/seo/generators/MetaGenerator.js.map +1 -0
- package/dist/tools/seo/generators/SchemaGenerator.d.ts +204 -0
- package/dist/tools/seo/generators/SchemaGenerator.d.ts.map +1 -0
- package/dist/tools/seo/generators/SchemaGenerator.js +670 -0
- package/dist/tools/seo/generators/SchemaGenerator.js.map +1 -0
- package/dist/tools/seo/index.d.ts +17 -0
- package/dist/tools/seo/index.d.ts.map +1 -0
- package/dist/tools/seo/index.js +18 -0
- package/dist/tools/seo/index.js.map +1 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts +186 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts.map +1 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.js +683 -0
- package/dist/tools/seo/optimizers/InternalLinkingSuggester.js.map +1 -0
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js +12 -12
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/taxonomies.d.ts.map +1 -1
- package/dist/tools/taxonomies.js +20 -20
- package/dist/tools/taxonomies.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +12 -12
- package/dist/tools/users.js.map +1 -1
- package/dist/types/client.d.ts +8 -6
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/client.js.map +1 -1
- package/dist/types/seo.d.ts +473 -0
- package/dist/types/seo.d.ts.map +1 -0
- package/dist/types/seo.js +94 -0
- package/dist/types/seo.js.map +1 -0
- package/dist/utils/enhancedError.js +1 -1
- package/dist/utils/enhancedError.js.map +1 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +0 -1
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.js +3 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts +2 -2
- package/dist/utils/toolWrapper.js +8 -8
- package/dist/utils/toolWrapper.js.map +1 -1
- package/dist/utils/validation/core.d.ts.map +1 -1
- package/dist/utils/validation/core.js.map +1 -1
- package/dist/utils/validation/index.d.ts.map +1 -1
- package/dist/utils/validation/index.js.map +1 -1
- package/dist/utils/validation/network.js +3 -3
- package/dist/utils/validation/network.js.map +1 -1
- package/dist/utils/validation/rateLimit.js.map +1 -1
- package/dist/utils/validation/security.js.map +1 -1
- package/dist/utils/validation/wordpress.js.map +1 -1
- package/dist/utils/version.d.ts +144 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +318 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +21 -55
- package/src/cache/CacheInvalidation.ts +183 -20
- package/src/cache/HttpCacheWrapper.ts +8 -5
- package/src/cache/SEOCacheManager.ts +330 -0
- package/src/cache/__tests__/CacheInvalidation.test.ts +6 -11
- package/src/cache/__tests__/CachedWordPressClient.test.ts +37 -62
- package/src/client/SEOWordPressClient.ts +876 -0
- package/src/client/api.ts +50 -21
- package/src/client/auth.ts +19 -19
- package/src/client/index.ts +16 -0
- package/src/client/managers/AuthManager.ts +175 -0
- package/src/client/managers/AuthenticationManager.ts +16 -14
- package/src/client/managers/BaseManager.ts +24 -5
- package/src/client/managers/ComposedAuthenticationManager.ts +409 -0
- package/src/client/managers/ComposedManagerFactory.ts +231 -0
- package/src/client/managers/ComposedRequestManager.ts +336 -0
- package/src/client/managers/JWTAuthImplementation.ts +326 -0
- package/src/client/managers/ManagersIndex.ts +6 -0
- package/src/client/managers/RequestManager.ts +9 -7
- package/src/client/managers/composed/MigrationAdapter.ts +263 -0
- package/src/client/managers/composed/index.ts +47 -0
- package/src/client/managers/implementations/ConfigurationProviderImpl.ts +52 -0
- package/src/client/managers/implementations/ErrorHandlerImpl.ts +102 -0
- package/src/client/managers/implementations/ParameterValidatorImpl.ts +221 -0
- package/src/client/managers/interfaces/ManagerInterfaces.ts +171 -0
- package/src/config/Config.ts +63 -0
- package/src/config/ConfigurationSchema.ts +17 -17
- package/src/config/ServerConfiguration.ts +18 -16
- package/src/config/index.ts +13 -0
- package/src/docs/DocumentationGenerator.ts +2 -2
- package/src/dxt-entry.ts +3 -3
- package/src/index.ts +43 -43
- package/src/performance/MetricsCollector.ts +15 -11
- package/src/security/AISecurityScanner.ts +7 -7
- package/src/security/AutomatedRemediation.ts +13 -11
- package/src/security/InputValidator.ts +10 -9
- package/src/security/SecurityCIPipeline.ts +494 -56
- package/src/security/SecurityConfigManager.ts +10 -10
- package/src/security/SecurityMonitoring.ts +5 -5
- package/src/security/SecurityReviewer.ts +13 -6
- package/src/security/index.ts +3 -3
- package/src/server/ConnectionTester.ts +5 -5
- package/src/server/ToolRegistry.ts +9 -8
- package/src/server/index.ts +10 -0
- package/src/tools/BaseToolManager.ts +55 -83
- package/src/tools/auth.ts +21 -12
- package/src/tools/comments.ts +23 -19
- package/src/tools/index.ts +1 -0
- package/src/tools/media.ts +23 -20
- package/src/tools/pages.ts +20 -13
- package/src/tools/performance.ts +101 -32
- package/src/tools/posts/PostHandlers.ts +23 -23
- package/src/tools/posts/PostToolDefinitions.ts +1 -1
- package/src/tools/posts/index.ts +2 -2
- package/src/tools/seo/BulkOperations.ts +557 -0
- package/src/tools/seo/SEOHandlers.ts +296 -0
- package/src/tools/seo/SEOToolDefinitions.ts +402 -0
- package/src/tools/seo/SEOTools.ts +871 -0
- package/src/tools/seo/analyzers/ContentAnalyzer.ts +493 -0
- package/src/tools/seo/auditors/SiteAuditor.ts +787 -0
- package/src/tools/seo/generators/MetaGenerator.ts +694 -0
- package/src/tools/seo/generators/SchemaGenerator.ts +955 -0
- package/src/tools/seo/index.ts +47 -0
- package/src/tools/seo/optimizers/InternalLinkingSuggester.ts +934 -0
- package/src/tools/site.ts +27 -26
- package/src/tools/taxonomies.ts +29 -25
- package/src/tools/users.ts +20 -13
- package/src/types/client.ts +8 -6
- package/src/types/seo.ts +546 -0
- package/src/utils/enhancedError.ts +1 -1
- package/src/utils/error.ts +1 -2
- package/src/utils/index.ts +23 -0
- package/src/utils/logger.ts +3 -3
- package/src/utils/toolWrapper.ts +10 -10
- package/src/utils/validation/core.ts +2 -2
- package/src/utils/validation/index.ts +2 -2
- package/src/utils/validation/network.ts +5 -5
- package/src/utils/validation/rateLimit.ts +1 -1
- package/src/utils/validation/security.ts +1 -1
- package/src/utils/validation/wordpress.ts +1 -1
- package/src/utils/version.ts +402 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO Metadata Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates optimized SEO metadata including title tags, meta descriptions,
|
|
5
|
+
* OpenGraph tags, and Twitter Card metadata with AI assistance and safety filters.
|
|
6
|
+
*
|
|
7
|
+
* @since 2.7.0
|
|
8
|
+
*/
|
|
9
|
+
import type { WordPressPost } from "../../../types/wordpress.js";
|
|
10
|
+
import type { SEOMetadata, SEOToolParams } from "../../../types/seo.js";
|
|
11
|
+
/**
|
|
12
|
+
* Metadata generation options
|
|
13
|
+
*/
|
|
14
|
+
interface MetaGenerationOptions {
|
|
15
|
+
includeKeywords?: boolean;
|
|
16
|
+
brandVoice?: "professional" | "casual" | "technical" | "friendly";
|
|
17
|
+
targetAudience?: "general" | "technical" | "beginner" | "expert";
|
|
18
|
+
includeCallToAction?: boolean;
|
|
19
|
+
preserveExisting?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* SEO metadata generator with AI assistance and safety validation
|
|
23
|
+
*/
|
|
24
|
+
export declare class MetaGenerator {
|
|
25
|
+
private logger;
|
|
26
|
+
private config;
|
|
27
|
+
private readonly safetyFilters;
|
|
28
|
+
/**
|
|
29
|
+
* Generate comprehensive SEO metadata for a WordPress post
|
|
30
|
+
*
|
|
31
|
+
* @param post - WordPress post data
|
|
32
|
+
* @param params - Generation parameters
|
|
33
|
+
* @param options - Generation options
|
|
34
|
+
* @returns Optimized SEO metadata
|
|
35
|
+
*/
|
|
36
|
+
generateMetadata(post: WordPressPost, params: SEOToolParams, options?: MetaGenerationOptions): Promise<SEOMetadata>;
|
|
37
|
+
/**
|
|
38
|
+
* Generate optimized title tag
|
|
39
|
+
*
|
|
40
|
+
* @param post - WordPress post
|
|
41
|
+
* @param content - Extracted content
|
|
42
|
+
* @param focusKeyword - Primary keyword
|
|
43
|
+
* @param options - Generation options
|
|
44
|
+
* @returns Optimized title
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private generateTitle;
|
|
48
|
+
/**
|
|
49
|
+
* Generate meta description
|
|
50
|
+
*
|
|
51
|
+
* @param post - WordPress post
|
|
52
|
+
* @param content - Extracted content
|
|
53
|
+
* @param focusKeyword - Primary keyword
|
|
54
|
+
* @param options - Generation options
|
|
55
|
+
* @returns Optimized description
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
private generateDescription;
|
|
59
|
+
/**
|
|
60
|
+
* Generate OpenGraph metadata
|
|
61
|
+
*
|
|
62
|
+
* @param post - WordPress post
|
|
63
|
+
* @param title - Optimized title
|
|
64
|
+
* @param description - Optimized description
|
|
65
|
+
* @param options - Generation options
|
|
66
|
+
* @returns OpenGraph metadata
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
private generateOpenGraph;
|
|
70
|
+
/**
|
|
71
|
+
* Generate Twitter Card metadata
|
|
72
|
+
*
|
|
73
|
+
* @param post - WordPress post
|
|
74
|
+
* @param title - Optimized title
|
|
75
|
+
* @param description - Optimized description
|
|
76
|
+
* @param options - Generation options
|
|
77
|
+
* @returns Twitter Card metadata
|
|
78
|
+
* @private
|
|
79
|
+
*/
|
|
80
|
+
private generateTwitterCard;
|
|
81
|
+
/**
|
|
82
|
+
* Generate canonical URL
|
|
83
|
+
*
|
|
84
|
+
* @param post - WordPress post
|
|
85
|
+
* @returns Canonical URL
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
private generateCanonicalUrl;
|
|
89
|
+
/**
|
|
90
|
+
* Generate robots meta directives
|
|
91
|
+
*
|
|
92
|
+
* @param post - WordPress post
|
|
93
|
+
* @param options - Generation options
|
|
94
|
+
* @returns Robots directives
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
private generateRobotsDirectives;
|
|
98
|
+
/**
|
|
99
|
+
* Apply safety filters to metadata
|
|
100
|
+
*
|
|
101
|
+
* @param metadata - Metadata to filter
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
private applySafetyFilters;
|
|
105
|
+
/**
|
|
106
|
+
* Validate metadata meets requirements
|
|
107
|
+
*
|
|
108
|
+
* @param metadata - Metadata to validate
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
private validateMetadata;
|
|
112
|
+
private extractPostContent;
|
|
113
|
+
private stripHtml;
|
|
114
|
+
private extractFirstSentences;
|
|
115
|
+
private insertKeywordNaturally;
|
|
116
|
+
private addCallToAction;
|
|
117
|
+
private optimizeDescriptionLength;
|
|
118
|
+
private truncateTitle;
|
|
119
|
+
private applyBrandVoice;
|
|
120
|
+
private determineOpenGraphType;
|
|
121
|
+
private getSiteName;
|
|
122
|
+
private getFeaturedImageUrl;
|
|
123
|
+
private getTwitterHandle;
|
|
124
|
+
private getSafeAlternative;
|
|
125
|
+
private sanitizeText;
|
|
126
|
+
}
|
|
127
|
+
export default MetaGenerator;
|
|
128
|
+
//# sourceMappingURL=MetaGenerator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetaGenerator.d.ts","sourceRoot":"","sources":["../../../../src/tools/seo/generators/MetaGenerator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAaxE;;GAEG;AACH,UAAU,qBAAqB;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,cAAc,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IAClE,cAAc,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,CAAC;IACjE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAwC;IACtD,OAAO,CAAC,MAAM,CAAkC;IAEhD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAiB5B;IAEF;;;;;;;OAOG;IACG,gBAAgB,CACpB,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,WAAW,CAAC;IAwEvB;;;;;;;;;OASG;YACW,aAAa;IAgD3B;;;;;;;;;OASG;YACW,mBAAmB;IA+BjC;;;;;;;;;OASG;YACW,iBAAiB;IAwB/B;;;;;;;;;OASG;YACW,mBAAmB;IA4BjC;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB;IAYhC;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAwBxB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,yBAAyB;IAiFjC,OAAO,CAAC,aAAa;IA4ErB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,YAAY;CAOrB;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO Metadata Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates optimized SEO metadata including title tags, meta descriptions,
|
|
5
|
+
* OpenGraph tags, and Twitter Card metadata with AI assistance and safety filters.
|
|
6
|
+
*
|
|
7
|
+
* @since 2.7.0
|
|
8
|
+
*/
|
|
9
|
+
import { LoggerFactory } from "../../../utils/logger.js";
|
|
10
|
+
import { Config } from "../../../config/Config.js";
|
|
11
|
+
/**
|
|
12
|
+
* SEO metadata generator with AI assistance and safety validation
|
|
13
|
+
*/
|
|
14
|
+
export class MetaGenerator {
|
|
15
|
+
logger = LoggerFactory.tool("meta_generator");
|
|
16
|
+
config = Config.getInstance().get().seo;
|
|
17
|
+
safetyFilters = {
|
|
18
|
+
maxTitleLength: this.config.metadata.titleMaxLength || 60,
|
|
19
|
+
minDescriptionLength: this.config.metadata.descriptionMinLength || 155,
|
|
20
|
+
maxDescriptionLength: this.config.metadata.descriptionMaxLength || 160,
|
|
21
|
+
forbiddenWords: [
|
|
22
|
+
"spam",
|
|
23
|
+
"scam",
|
|
24
|
+
"fake",
|
|
25
|
+
"clickbait",
|
|
26
|
+
"hack",
|
|
27
|
+
"cheat",
|
|
28
|
+
"guaranteed",
|
|
29
|
+
"instant",
|
|
30
|
+
"miracle",
|
|
31
|
+
"secret",
|
|
32
|
+
],
|
|
33
|
+
requiredFields: ["title", "description"],
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Generate comprehensive SEO metadata for a WordPress post
|
|
37
|
+
*
|
|
38
|
+
* @param post - WordPress post data
|
|
39
|
+
* @param params - Generation parameters
|
|
40
|
+
* @param options - Generation options
|
|
41
|
+
* @returns Optimized SEO metadata
|
|
42
|
+
*/
|
|
43
|
+
async generateMetadata(post, params, options = {}) {
|
|
44
|
+
const siteLogger = LoggerFactory.tool("seo_generate_metadata", params.site);
|
|
45
|
+
return await siteLogger.time("Generate SEO metadata", async () => {
|
|
46
|
+
// Extract content for analysis
|
|
47
|
+
const content = this.extractPostContent(post);
|
|
48
|
+
const focusKeyword = params.focusKeywords?.[0] || "";
|
|
49
|
+
// Generate title tag
|
|
50
|
+
const title = await this.generateTitle(post, content, focusKeyword, options);
|
|
51
|
+
// Generate meta description
|
|
52
|
+
const description = await this.generateDescription(post, content, focusKeyword, options);
|
|
53
|
+
// Generate OpenGraph metadata
|
|
54
|
+
const openGraph = await this.generateOpenGraph(post, title, description, options);
|
|
55
|
+
// Generate Twitter Card metadata
|
|
56
|
+
const twitterCard = await this.generateTwitterCard(post, title, description, options);
|
|
57
|
+
// Create canonical URL
|
|
58
|
+
const canonical = this.generateCanonicalUrl(post);
|
|
59
|
+
// Set robots directives
|
|
60
|
+
const robots = this.generateRobotsDirectives(post, options);
|
|
61
|
+
const metadata = {
|
|
62
|
+
title,
|
|
63
|
+
description,
|
|
64
|
+
};
|
|
65
|
+
if (focusKeyword) {
|
|
66
|
+
metadata.focusKeyword = focusKeyword;
|
|
67
|
+
}
|
|
68
|
+
if (canonical) {
|
|
69
|
+
metadata.canonical = canonical;
|
|
70
|
+
}
|
|
71
|
+
if (robots) {
|
|
72
|
+
metadata.robots = robots;
|
|
73
|
+
}
|
|
74
|
+
if (openGraph) {
|
|
75
|
+
metadata.openGraph = openGraph;
|
|
76
|
+
}
|
|
77
|
+
if (twitterCard) {
|
|
78
|
+
metadata.twitterCard = twitterCard;
|
|
79
|
+
}
|
|
80
|
+
// Apply safety filters
|
|
81
|
+
this.applySafetyFilters(metadata);
|
|
82
|
+
// Re-optimize description length after safety filters may have shortened it
|
|
83
|
+
metadata.description = this.optimizeDescriptionLength(metadata.description);
|
|
84
|
+
// Validate required fields
|
|
85
|
+
this.validateMetadata(metadata);
|
|
86
|
+
siteLogger.info("Generated SEO metadata", {
|
|
87
|
+
titleLength: title.length,
|
|
88
|
+
descriptionLength: description.length,
|
|
89
|
+
hasOpenGraph: Boolean(openGraph),
|
|
90
|
+
hasTwitterCard: Boolean(twitterCard),
|
|
91
|
+
focusKeyword,
|
|
92
|
+
});
|
|
93
|
+
return metadata;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate optimized title tag
|
|
98
|
+
*
|
|
99
|
+
* @param post - WordPress post
|
|
100
|
+
* @param content - Extracted content
|
|
101
|
+
* @param focusKeyword - Primary keyword
|
|
102
|
+
* @param options - Generation options
|
|
103
|
+
* @returns Optimized title
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
async generateTitle(post, content, focusKeyword, options) {
|
|
107
|
+
const originalTitle = typeof post.title === "object" ? post.title.rendered : post.title || "";
|
|
108
|
+
// Check if title is completely empty - this should be an error
|
|
109
|
+
if (!originalTitle || originalTitle.trim() === "") {
|
|
110
|
+
throw new Error("Title is required and cannot be empty");
|
|
111
|
+
}
|
|
112
|
+
// If preserving existing and title exists, return it if it's good
|
|
113
|
+
if (options.preserveExisting && originalTitle && originalTitle.length <= this.safetyFilters.maxTitleLength) {
|
|
114
|
+
return originalTitle;
|
|
115
|
+
}
|
|
116
|
+
// Generate optimized title
|
|
117
|
+
let optimizedTitle = originalTitle;
|
|
118
|
+
// Truncate first if too long, then add focus keyword if needed
|
|
119
|
+
if (optimizedTitle.length > this.safetyFilters.maxTitleLength) {
|
|
120
|
+
optimizedTitle = this.truncateTitle(optimizedTitle, focusKeyword);
|
|
121
|
+
}
|
|
122
|
+
// Add focus keyword if not present and specified, and there's room
|
|
123
|
+
if (focusKeyword && !optimizedTitle.toLowerCase().includes(focusKeyword.toLowerCase())) {
|
|
124
|
+
const withKeyword = `${focusKeyword}: ${optimizedTitle}`;
|
|
125
|
+
if (withKeyword.length <= this.safetyFilters.maxTitleLength) {
|
|
126
|
+
optimizedTitle = withKeyword;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Try to fit keyword by shortening original title
|
|
130
|
+
const availableSpace = this.safetyFilters.maxTitleLength - focusKeyword.length - 2; // 2 for ": "
|
|
131
|
+
if (availableSpace > 10) {
|
|
132
|
+
// Minimum meaningful title length
|
|
133
|
+
const shortened = optimizedTitle.substring(0, availableSpace - 3) + "...";
|
|
134
|
+
optimizedTitle = `${focusKeyword}: ${shortened}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Apply brand voice modifications
|
|
139
|
+
optimizedTitle = this.applyBrandVoice(optimizedTitle, options.brandVoice || "professional");
|
|
140
|
+
return optimizedTitle;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Generate meta description
|
|
144
|
+
*
|
|
145
|
+
* @param post - WordPress post
|
|
146
|
+
* @param content - Extracted content
|
|
147
|
+
* @param focusKeyword - Primary keyword
|
|
148
|
+
* @param options - Generation options
|
|
149
|
+
* @returns Optimized description
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
async generateDescription(post, content, focusKeyword, options) {
|
|
153
|
+
const excerpt = typeof post.excerpt === "object" ? post.excerpt.rendered : post.excerpt || "";
|
|
154
|
+
const plainContent = this.stripHtml(content);
|
|
155
|
+
// Use excerpt as starting point if available
|
|
156
|
+
let description = excerpt || this.extractFirstSentences(plainContent, 2);
|
|
157
|
+
// Ensure focus keyword is included
|
|
158
|
+
if (focusKeyword && !description.toLowerCase().includes(focusKeyword.toLowerCase())) {
|
|
159
|
+
description = this.insertKeywordNaturally(description, focusKeyword);
|
|
160
|
+
}
|
|
161
|
+
// Apply brand voice first (before length optimization)
|
|
162
|
+
description = this.applyBrandVoice(description, options.brandVoice || "professional");
|
|
163
|
+
// Add call to action if requested (before length optimization)
|
|
164
|
+
if (options.includeCallToAction) {
|
|
165
|
+
description = this.addCallToAction(description, options.brandVoice || "professional");
|
|
166
|
+
}
|
|
167
|
+
// Ensure proper length (this should be the final step)
|
|
168
|
+
description = this.optimizeDescriptionLength(description);
|
|
169
|
+
return description;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Generate OpenGraph metadata
|
|
173
|
+
*
|
|
174
|
+
* @param post - WordPress post
|
|
175
|
+
* @param title - Optimized title
|
|
176
|
+
* @param description - Optimized description
|
|
177
|
+
* @param options - Generation options
|
|
178
|
+
* @returns OpenGraph metadata
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
async generateOpenGraph(post, title, description, options) {
|
|
182
|
+
const featuredImage = this.getFeaturedImageUrl(post);
|
|
183
|
+
const openGraph = {
|
|
184
|
+
title,
|
|
185
|
+
description,
|
|
186
|
+
type: this.determineOpenGraphType(post),
|
|
187
|
+
url: post.link,
|
|
188
|
+
siteName: this.getSiteName(),
|
|
189
|
+
locale: "en_US", // Could be made configurable
|
|
190
|
+
};
|
|
191
|
+
if (featuredImage) {
|
|
192
|
+
openGraph.image = featuredImage;
|
|
193
|
+
}
|
|
194
|
+
return openGraph;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Generate Twitter Card metadata
|
|
198
|
+
*
|
|
199
|
+
* @param post - WordPress post
|
|
200
|
+
* @param title - Optimized title
|
|
201
|
+
* @param description - Optimized description
|
|
202
|
+
* @param options - Generation options
|
|
203
|
+
* @returns Twitter Card metadata
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
async generateTwitterCard(post, title, description, options) {
|
|
207
|
+
const featuredImage = this.getFeaturedImageUrl(post);
|
|
208
|
+
const twitterHandle = this.getTwitterHandle();
|
|
209
|
+
const hasImage = Boolean(featuredImage);
|
|
210
|
+
const twitterCard = {
|
|
211
|
+
card: hasImage ? "summary_large_image" : "summary",
|
|
212
|
+
title,
|
|
213
|
+
description,
|
|
214
|
+
};
|
|
215
|
+
if (featuredImage) {
|
|
216
|
+
twitterCard.image = featuredImage;
|
|
217
|
+
}
|
|
218
|
+
if (twitterHandle) {
|
|
219
|
+
twitterCard.site = twitterHandle;
|
|
220
|
+
twitterCard.creator = twitterHandle;
|
|
221
|
+
}
|
|
222
|
+
return twitterCard;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Generate canonical URL
|
|
226
|
+
*
|
|
227
|
+
* @param post - WordPress post
|
|
228
|
+
* @returns Canonical URL
|
|
229
|
+
* @private
|
|
230
|
+
*/
|
|
231
|
+
generateCanonicalUrl(post) {
|
|
232
|
+
// Use the post's permalink, ensuring HTTPS
|
|
233
|
+
return post.link?.replace(/^http:/, "https:") || "";
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Generate robots meta directives
|
|
237
|
+
*
|
|
238
|
+
* @param post - WordPress post
|
|
239
|
+
* @param options - Generation options
|
|
240
|
+
* @returns Robots directives
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
generateRobotsDirectives(post, options) {
|
|
244
|
+
const shouldIndex = post.status === "publish";
|
|
245
|
+
return {
|
|
246
|
+
index: shouldIndex,
|
|
247
|
+
follow: true,
|
|
248
|
+
archive: shouldIndex,
|
|
249
|
+
snippet: true,
|
|
250
|
+
imageindex: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Apply safety filters to metadata
|
|
255
|
+
*
|
|
256
|
+
* @param metadata - Metadata to filter
|
|
257
|
+
* @private
|
|
258
|
+
*/
|
|
259
|
+
applySafetyFilters(metadata) {
|
|
260
|
+
// Check for forbidden words
|
|
261
|
+
const forbiddenRegex = new RegExp(this.safetyFilters.forbiddenWords.join("|"), "gi");
|
|
262
|
+
if (forbiddenRegex.test(metadata.title)) {
|
|
263
|
+
this.logger.warn("Forbidden words detected in title", { title: metadata.title });
|
|
264
|
+
// Replace forbidden words with alternatives or remove them
|
|
265
|
+
metadata.title = metadata.title.replace(forbiddenRegex, (match) => {
|
|
266
|
+
return this.getSafeAlternative(match.toLowerCase());
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (forbiddenRegex.test(metadata.description)) {
|
|
270
|
+
this.logger.warn("Forbidden words detected in description", {
|
|
271
|
+
description: metadata.description.substring(0, 50),
|
|
272
|
+
});
|
|
273
|
+
metadata.description = metadata.description.replace(forbiddenRegex, (match) => {
|
|
274
|
+
return this.getSafeAlternative(match.toLowerCase());
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// Remove any potential script tags or HTML
|
|
278
|
+
metadata.title = this.sanitizeText(metadata.title);
|
|
279
|
+
metadata.description = this.sanitizeText(metadata.description);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate metadata meets requirements
|
|
283
|
+
*
|
|
284
|
+
* @param metadata - Metadata to validate
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
validateMetadata(metadata) {
|
|
288
|
+
if (!metadata.title || metadata.title.length === 0) {
|
|
289
|
+
throw new Error("Title is required for SEO metadata");
|
|
290
|
+
}
|
|
291
|
+
if (metadata.title.length > this.safetyFilters.maxTitleLength) {
|
|
292
|
+
throw new Error(`Title exceeds maximum length of ${this.safetyFilters.maxTitleLength} characters`);
|
|
293
|
+
}
|
|
294
|
+
if (!metadata.description || metadata.description.length === 0) {
|
|
295
|
+
throw new Error("Description is required for SEO metadata");
|
|
296
|
+
}
|
|
297
|
+
if (metadata.description.length < this.safetyFilters.minDescriptionLength) {
|
|
298
|
+
throw new Error(`Description must be at least ${this.safetyFilters.minDescriptionLength} characters`);
|
|
299
|
+
}
|
|
300
|
+
if (metadata.description.length > this.safetyFilters.maxDescriptionLength) {
|
|
301
|
+
throw new Error(`Description exceeds maximum length of ${this.safetyFilters.maxDescriptionLength} characters`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Helper methods
|
|
305
|
+
extractPostContent(post) {
|
|
306
|
+
const titleContent = typeof post.title === "object" ? post.title.rendered : post.title || "";
|
|
307
|
+
const bodyContent = typeof post.content === "object" ? post.content.rendered : post.content || "";
|
|
308
|
+
const excerptContent = typeof post.excerpt === "object" ? post.excerpt.rendered : post.excerpt || "";
|
|
309
|
+
return `${titleContent} ${bodyContent} ${excerptContent}`.trim();
|
|
310
|
+
}
|
|
311
|
+
stripHtml(html) {
|
|
312
|
+
return html
|
|
313
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
314
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
|
|
315
|
+
.replace(/<[^>]*>/g, "")
|
|
316
|
+
.replace(/ /g, " ")
|
|
317
|
+
.replace(/&[#\w]+;/g, "")
|
|
318
|
+
.replace(/\s+/g, " ")
|
|
319
|
+
.trim();
|
|
320
|
+
}
|
|
321
|
+
extractFirstSentences(text, count) {
|
|
322
|
+
const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
323
|
+
const result = sentences.slice(0, count).join(". ");
|
|
324
|
+
// Always add a period at the end if there's content
|
|
325
|
+
return result ? result + "." : "";
|
|
326
|
+
}
|
|
327
|
+
insertKeywordNaturally(text, keyword) {
|
|
328
|
+
// Simple implementation - could be enhanced with NLP
|
|
329
|
+
if (text.length < 50) {
|
|
330
|
+
return `${keyword}: ${text}`;
|
|
331
|
+
}
|
|
332
|
+
// Insert after first sentence
|
|
333
|
+
const sentences = text.split(". ");
|
|
334
|
+
if (sentences.length > 1) {
|
|
335
|
+
sentences[0] += ` ${keyword}`;
|
|
336
|
+
return sentences.join(". ");
|
|
337
|
+
}
|
|
338
|
+
return `${text} Learn about ${keyword}.`;
|
|
339
|
+
}
|
|
340
|
+
addCallToAction(description, brandVoice) {
|
|
341
|
+
const ctas = {
|
|
342
|
+
professional: "Learn more today.",
|
|
343
|
+
casual: "Check it out!",
|
|
344
|
+
technical: "Read the full guide.",
|
|
345
|
+
friendly: "Discover more!",
|
|
346
|
+
};
|
|
347
|
+
const cta = ctas[brandVoice] || ctas.professional;
|
|
348
|
+
// Add CTA if there's room
|
|
349
|
+
if (description.length + cta.length + 1 <= this.safetyFilters.maxDescriptionLength) {
|
|
350
|
+
return `${description} ${cta}`;
|
|
351
|
+
}
|
|
352
|
+
return description;
|
|
353
|
+
}
|
|
354
|
+
optimizeDescriptionLength(description) {
|
|
355
|
+
const minLength = this.safetyFilters.minDescriptionLength;
|
|
356
|
+
const maxLength = this.safetyFilters.maxDescriptionLength;
|
|
357
|
+
let result = description.trim();
|
|
358
|
+
// If already perfect length, return as is
|
|
359
|
+
if (result.length >= minLength && result.length <= maxLength) {
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
// If too long, truncate with word boundary
|
|
363
|
+
if (result.length > maxLength) {
|
|
364
|
+
const truncated = result.substring(0, maxLength - 3);
|
|
365
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
366
|
+
return (lastSpace > 0 ? truncated.substring(0, lastSpace) : truncated).trim() + "...";
|
|
367
|
+
}
|
|
368
|
+
// If too short, extend systematically to reach exactly minLength
|
|
369
|
+
if (result.length < minLength) {
|
|
370
|
+
const extensions = [
|
|
371
|
+
" This comprehensive guide provides detailed insights and practical tips for better results.",
|
|
372
|
+
" Learn more about best practices and implementation strategies.",
|
|
373
|
+
" Discover effective techniques and proven methods.",
|
|
374
|
+
" Get expert advice and actionable recommendations.",
|
|
375
|
+
" Additional content for completeness and better SEO optimization.",
|
|
376
|
+
];
|
|
377
|
+
// Add extensions one by one
|
|
378
|
+
for (const extension of extensions) {
|
|
379
|
+
if (result.length >= minLength)
|
|
380
|
+
break;
|
|
381
|
+
const needed = minLength - result.length;
|
|
382
|
+
if (result.length + extension.length <= maxLength) {
|
|
383
|
+
// Add full extension
|
|
384
|
+
result += extension;
|
|
385
|
+
}
|
|
386
|
+
else if (needed <= maxLength - result.length) {
|
|
387
|
+
// Add partial extension to reach exactly minLength
|
|
388
|
+
result += extension.substring(0, needed);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Final padding if still short (should rarely happen)
|
|
393
|
+
while (result.length < minLength && result.length < maxLength) {
|
|
394
|
+
const needed = minLength - result.length;
|
|
395
|
+
const padding = " Additional content.";
|
|
396
|
+
if (result.length + padding.length <= maxLength) {
|
|
397
|
+
result += padding;
|
|
398
|
+
}
|
|
399
|
+
else if (needed > 0) {
|
|
400
|
+
result += padding.substring(0, Math.min(needed, maxLength - result.length));
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Final safety check and guarantee minimum length
|
|
409
|
+
if (result.length > maxLength) {
|
|
410
|
+
result = result.substring(0, maxLength);
|
|
411
|
+
}
|
|
412
|
+
// Absolute final guarantee - ensure we meet minimum length
|
|
413
|
+
if (result.length < minLength) {
|
|
414
|
+
const shortfall = minLength - result.length;
|
|
415
|
+
// Add exactly the needed characters
|
|
416
|
+
for (let i = 0; i < shortfall && result.length < maxLength; i++) {
|
|
417
|
+
result += ".";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// One more check to be absolutely sure
|
|
421
|
+
if (result.length < minLength && result.length < maxLength) {
|
|
422
|
+
// This should never happen, but as a final failsafe
|
|
423
|
+
result = result.padEnd(minLength, ".");
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
truncateTitle(title, focusKeyword) {
|
|
428
|
+
const maxLength = this.safetyFilters.maxTitleLength;
|
|
429
|
+
// If already within length, return as is
|
|
430
|
+
if (title.length <= maxLength) {
|
|
431
|
+
return title;
|
|
432
|
+
}
|
|
433
|
+
// Simple truncation at word boundary if no focus keyword
|
|
434
|
+
if (!focusKeyword || !title.toLowerCase().includes(focusKeyword.toLowerCase())) {
|
|
435
|
+
const truncated = title.substring(0, maxLength - 3);
|
|
436
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
437
|
+
const result = truncated.substring(0, lastSpace > 0 ? lastSpace : truncated.length).trim() + "...";
|
|
438
|
+
return result.length <= maxLength ? result : title.substring(0, maxLength);
|
|
439
|
+
}
|
|
440
|
+
// Find keyword position
|
|
441
|
+
const keywordIndex = title.toLowerCase().indexOf(focusKeyword.toLowerCase());
|
|
442
|
+
const keywordEnd = keywordIndex + focusKeyword.length;
|
|
443
|
+
// Strategy: Try to keep keyword and balanced context
|
|
444
|
+
// Available space for context around keyword
|
|
445
|
+
const spaceAroundKeyword = maxLength - focusKeyword.length;
|
|
446
|
+
// If we need ellipsis on both sides, reserve space for them
|
|
447
|
+
const maxContextLength = spaceAroundKeyword - 6; // Reserve for "..." on both sides
|
|
448
|
+
if (maxContextLength <= 0) {
|
|
449
|
+
// Not enough space for context, just return truncated keyword
|
|
450
|
+
return focusKeyword.substring(0, maxLength);
|
|
451
|
+
}
|
|
452
|
+
// Calculate how much context to include before and after
|
|
453
|
+
const beforeAvailable = keywordIndex;
|
|
454
|
+
const afterAvailable = title.length - keywordEnd;
|
|
455
|
+
let beforeLength = Math.min(beforeAvailable, Math.floor(maxContextLength / 2));
|
|
456
|
+
let afterLength = Math.min(afterAvailable, maxContextLength - beforeLength);
|
|
457
|
+
// Adjust if we have extra space
|
|
458
|
+
if (beforeLength < Math.floor(maxContextLength / 2) && afterAvailable > afterLength) {
|
|
459
|
+
afterLength = Math.min(afterAvailable, maxContextLength - beforeLength);
|
|
460
|
+
}
|
|
461
|
+
if (afterLength < Math.floor(maxContextLength / 2) && beforeAvailable > beforeLength) {
|
|
462
|
+
beforeLength = Math.min(beforeAvailable, maxContextLength - afterLength);
|
|
463
|
+
}
|
|
464
|
+
// Build the result
|
|
465
|
+
let result = "";
|
|
466
|
+
// Add beginning with ellipsis if needed
|
|
467
|
+
if (beforeLength < beforeAvailable) {
|
|
468
|
+
const start = keywordIndex - beforeLength;
|
|
469
|
+
result += "..." + title.substring(start, keywordIndex);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
result += title.substring(0, keywordIndex);
|
|
473
|
+
}
|
|
474
|
+
// Add keyword
|
|
475
|
+
result += title.substring(keywordIndex, keywordEnd);
|
|
476
|
+
// Add end with ellipsis if needed
|
|
477
|
+
if (afterLength < afterAvailable) {
|
|
478
|
+
result += title.substring(keywordEnd, keywordEnd + afterLength) + "...";
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
result += title.substring(keywordEnd);
|
|
482
|
+
}
|
|
483
|
+
// Emergency truncation if still too long
|
|
484
|
+
if (result.length > maxLength) {
|
|
485
|
+
result = result.substring(0, maxLength);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
applyBrandVoice(text, brandVoice) {
|
|
490
|
+
// Simple brand voice application - could be enhanced with AI
|
|
491
|
+
switch (brandVoice) {
|
|
492
|
+
case "casual":
|
|
493
|
+
return text.replace(/\.$/, "!");
|
|
494
|
+
case "technical":
|
|
495
|
+
// Keep formal tone
|
|
496
|
+
return text;
|
|
497
|
+
case "friendly":
|
|
498
|
+
if (!text.includes("!") && !text.includes("?")) {
|
|
499
|
+
return text.replace(/\.$/, " 😊");
|
|
500
|
+
}
|
|
501
|
+
return text;
|
|
502
|
+
default:
|
|
503
|
+
return text;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
determineOpenGraphType(post) {
|
|
507
|
+
// Could be enhanced based on post type or content analysis
|
|
508
|
+
return post.type === "page" ? "website" : "article";
|
|
509
|
+
}
|
|
510
|
+
getSiteName() {
|
|
511
|
+
// This would ideally come from WordPress site settings
|
|
512
|
+
return "WordPress Site";
|
|
513
|
+
}
|
|
514
|
+
getFeaturedImageUrl(post) {
|
|
515
|
+
// This would need to be fetched from WordPress media API
|
|
516
|
+
// For now, return undefined
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
getTwitterHandle() {
|
|
520
|
+
// This would come from site configuration
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
getSafeAlternative(forbiddenWord) {
|
|
524
|
+
const alternatives = {
|
|
525
|
+
spam: "unwanted",
|
|
526
|
+
scam: "fraud",
|
|
527
|
+
fake: "artificial",
|
|
528
|
+
clickbait: "attention-grabbing",
|
|
529
|
+
hack: "tip",
|
|
530
|
+
cheat: "shortcut",
|
|
531
|
+
guaranteed: "reliable",
|
|
532
|
+
instant: "quick",
|
|
533
|
+
miracle: "effective",
|
|
534
|
+
secret: "insider",
|
|
535
|
+
};
|
|
536
|
+
return alternatives[forbiddenWord] || "[filtered]";
|
|
537
|
+
}
|
|
538
|
+
sanitizeText(text) {
|
|
539
|
+
return text
|
|
540
|
+
.replace(/<[^>]*>/g, "") // Remove HTML tags
|
|
541
|
+
.replace(/javascript:/gi, "") // Remove javascript: urls
|
|
542
|
+
.replace(/on\w+\s*=/gi, "") // Remove event handlers
|
|
543
|
+
.trim();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
export default MetaGenerator;
|
|
547
|
+
//# sourceMappingURL=MetaGenerator.js.map
|