@lynxflow/seo-engine 1.8.30 → 2.0.1
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 +60 -117
- package/dist/ai-copilot-client.d.ts +2 -0
- package/dist/built-in-locations.d.ts +22 -0
- package/dist/content-ai-arsenal.d.ts +28 -0
- package/dist/existing-media-harvester.d.ts +33 -0
- package/dist/extended-schemas.d.ts +84 -0
- package/dist/features-knowledge-harvester.d.ts +56 -0
- package/dist/geo-citation-scorer.d.ts +34 -0
- package/dist/i18n-detector.d.ts +1 -1
- package/dist/index.d.ts +32 -7
- package/dist/index.js +567 -83
- package/dist/index.mjs +2062 -169
- package/dist/internal-pagerank-graph.d.ts +59 -0
- package/dist/knowledge-bank-builder.d.ts +91 -0
- package/dist/marketing-skills-engine.d.ts +74 -0
- package/dist/matrix-engine.d.ts +29 -0
- package/dist/power-words-psychology.d.ts +27 -15
- package/dist/public-manifest-engine.d.ts +58 -0
- package/dist/rag-knowledge-engine.d.ts +50 -0
- package/dist/rate-limited-translator.d.ts +19 -0
- package/dist/token-quota-manager.d.ts +4 -0
- package/package.json +11 -3
- package/TUTORIEL_INTEGRATION_SITE.md +0 -283
- package/connectors/cloudflare-worker/worker.js +0 -106
- package/connectors/laravel/LynxSeoController.php +0 -37
- package/connectors/wordpress/lynxseo-admin-app.js +0 -2611
- package/connectors/wordpress/lynxseo-connector.php +0 -1712
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/src/ad-intelligence-cro.ts +0 -140
- package/src/ai-bots-log-analyzer.ts +0 -127
- package/src/ai-copilot-client.ts +0 -99
- package/src/analytics-client.ts +0 -315
- package/src/auth-key.ts +0 -262
- package/src/backlinks-client.ts +0 -85
- package/src/brand-dna-calendar.ts +0 -120
- package/src/brand-icons.ts +0 -138
- package/src/built-in-locations.ts +0 -170
- package/src/content-ai-arsenal.ts +0 -268
- package/src/copy-frameworks-master.ts +0 -89
- package/src/cro-copywriting-engine.ts +0 -105
- package/src/crosslink-scorer.ts +0 -101
- package/src/embeddable-seo-widget.ts +0 -57
- package/src/engine.test.ts +0 -386
- package/src/engine.ts +0 -813
- package/src/existing-media-harvester.ts +0 -98
- package/src/extended-schemas.ts +0 -589
- package/src/features-knowledge-harvester.ts +0 -190
- package/src/free-public-tools.ts +0 -224
- package/src/geo-mesh-linking.ts +0 -93
- package/src/google-business-profile.ts +0 -280
- package/src/i18n-detector.ts +0 -98
- package/src/i18n-dictionary.ts +0 -422
- package/src/index.ts +0 -164
- package/src/indexnow-client.ts +0 -94
- package/src/instant-matrix-search.ts +0 -48
- package/src/isr-cache-manager.ts +0 -78
- package/src/keyword-permutator.ts +0 -118
- package/src/knowledge-bank-builder.ts +0 -299
- package/src/knowledge-graph-linker.ts +0 -81
- package/src/lago-token-meter.ts +0 -120
- package/src/legal-disclaimers.ts +0 -407
- package/src/llm-content-cleaner.ts +0 -64
- package/src/llm-prompt.ts +0 -323
- package/src/master-marketing-engine.ts +0 -314
- package/src/matrix-engine.ts +0 -1204
- package/src/mcp-seo-server.ts +0 -108
- package/src/ngram-density-analyzer.ts +0 -108
- package/src/og-image-generator.ts +0 -109
- package/src/power-words-psychology.ts +0 -369
- package/src/public-manifest-engine.ts +0 -109
- package/src/rag-knowledge-engine.ts +0 -163
- package/src/rank-math-parity.ts +0 -235
- package/src/rate-limited-translator.ts +0 -110
- package/src/real-reviews-sync.ts +0 -207
- package/src/rss-syndication-feed.ts +0 -56
- package/src/schema-builder.ts +0 -134
- package/src/seo-opportunities-decay.ts +0 -154
- package/src/serp-client.ts +0 -89
- package/src/serp-history-alerts.ts +0 -75
- package/src/site-auditor.ts +0 -83
- package/src/site-crawler.ts +0 -406
- package/src/slug-engine.ts +0 -183
- package/src/social-ads-seo.ts +0 -211
- package/src/social-growth-suite.ts +0 -310
- package/src/social-trend-seo.ts +0 -103
- package/src/social-video-seo.ts +0 -100
- package/src/team-rbac.ts +0 -61
- package/src/technical-rules-auditor.ts +0 -171
- package/src/token-quota-manager.ts +0 -91
- package/src/types.ts +0 -143
- package/src/ui-icons.ts +0 -134
- package/src/urlytics-engine.ts +0 -97
- package/src/video-youtube-analyzer.ts +0 -81
- package/src/yoast-parity.ts +0 -161
- package/tsconfig.json +0 -15
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🧠 Google Knowledge Graph Entity Linker (Inspiration: advertools/knowledge_graph.py)
|
|
3
|
-
*
|
|
4
|
-
* Resolves official Google Entity IDs (`kgmid`), entity types, and Wikidata URIs
|
|
5
|
-
* to maximize semantic authority and Knowledge Panel recognition (Entity SEO).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface KnowledgeGraphEntity {
|
|
9
|
-
name: string;
|
|
10
|
-
kgmid: string; // e.g. "/m/02zsn" or "kg:/g/11b6t4q7k"
|
|
11
|
-
types: string[]; // e.g. ["Organization", "Corporation", "SoftwareApplication"]
|
|
12
|
-
description?: string;
|
|
13
|
-
detailedDescriptionUrl?: string;
|
|
14
|
-
wikidataUri?: string;
|
|
15
|
-
resultScore: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class KnowledgeGraphLinker {
|
|
19
|
-
/**
|
|
20
|
-
* Constructs search query parameters for the Google Knowledge Graph Search API.
|
|
21
|
-
*/
|
|
22
|
-
static buildSearchUrl(query: string, apiKey: string, limit = 5, languages = ["fr", "en"]): string {
|
|
23
|
-
const params = new URLSearchParams({
|
|
24
|
-
query,
|
|
25
|
-
key: apiKey,
|
|
26
|
-
limit: String(limit),
|
|
27
|
-
indent: "true",
|
|
28
|
-
languages: languages.join(","),
|
|
29
|
-
});
|
|
30
|
-
return `https://kgsearch.googleapis.com/v1/entities:search?${params.toString()}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Parses Google Knowledge Graph JSON-LD response items into normalized entities.
|
|
35
|
-
*/
|
|
36
|
-
static parseApiResponse(jsonResponse: any): KnowledgeGraphEntity[] {
|
|
37
|
-
if (!jsonResponse || !Array.isArray(jsonResponse.itemListElement)) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return jsonResponse.itemListElement.map((item: any) => {
|
|
42
|
-
const result = item.result || {};
|
|
43
|
-
const kgmid = result["@id"] || "";
|
|
44
|
-
const types = Array.isArray(result["@type"]) ? result["@type"] : [result["@type"] || "Thing"];
|
|
45
|
-
const detailed = result.detailedDescription || {};
|
|
46
|
-
|
|
47
|
-
let wikidataUri: string | undefined;
|
|
48
|
-
if (detailed.url && detailed.url.includes("wikipedia.org")) {
|
|
49
|
-
wikidataUri = detailed.url;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
name: result.name || "",
|
|
54
|
-
kgmid,
|
|
55
|
-
types,
|
|
56
|
-
description: detailed.articleBody || result.description,
|
|
57
|
-
detailedDescriptionUrl: detailed.url,
|
|
58
|
-
wikidataUri,
|
|
59
|
-
resultScore: item.resultScore || 0,
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Generates Schema.org `sameAs` entity triples combining Google kgmid and Wikipedia/Wikidata.
|
|
66
|
-
*/
|
|
67
|
-
static generateEntitySameAsLinks(entity: KnowledgeGraphEntity, customUrls: string[] = []): string[] {
|
|
68
|
-
const links: string[] = [...customUrls];
|
|
69
|
-
|
|
70
|
-
if (entity.kgmid) {
|
|
71
|
-
const cleanMid = entity.kgmid.replace(/^kg:/, "");
|
|
72
|
-
links.push(`https://www.google.com/search?kgmid=${encodeURIComponent(cleanMid)}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (entity.wikidataUri) {
|
|
76
|
-
links.push(entity.wikidataUri);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return Array.from(new Set(links));
|
|
80
|
-
}
|
|
81
|
-
}
|
package/src/lago-token-meter.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🦩 Lago Metered Token Billing & Hyperswitch Payment Bridge
|
|
3
|
-
*
|
|
4
|
-
* Reports live `ai_tokens` consumption events to Lago and initiates
|
|
5
|
-
* checkout / subscription sessions through Hyperswitch universal payment router.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
declare const process: any;
|
|
9
|
-
|
|
10
|
-
export interface LagoUsageEvent {
|
|
11
|
-
transactionId: string;
|
|
12
|
-
externalCustomerId: string;
|
|
13
|
-
code: "ai_tokens" | "pseo_pages_generated" | "serp_scrapes";
|
|
14
|
-
units: number;
|
|
15
|
-
timestamp: number; // Unix timestamp
|
|
16
|
-
properties?: Record<string, unknown>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface HyperswitchCheckoutConfig {
|
|
20
|
-
apiKey: string;
|
|
21
|
-
baseUrl?: string;
|
|
22
|
-
profileId?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class LagoTokenMeter {
|
|
26
|
-
private lagoApiUrl: string;
|
|
27
|
-
private apiKey: string;
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
apiKey = (typeof process !== "undefined" && process.env && process.env.LAGO_API_KEY) || "",
|
|
31
|
-
lagoApiUrl = (typeof process !== "undefined" && process.env && process.env.LAGO_API_URL) || "http://localhost:3000/api/v1"
|
|
32
|
-
) {
|
|
33
|
-
this.apiKey = apiKey;
|
|
34
|
-
this.lagoApiUrl = lagoApiUrl;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Sends a real-time token metering event to Lago.
|
|
39
|
-
*/
|
|
40
|
-
async trackTokenUsage(externalCustomerId: string, tokenCount: number, model: string): Promise<boolean> {
|
|
41
|
-
if (!this.apiKey) {
|
|
42
|
-
// Local fallback mode
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const event: LagoUsageEvent = {
|
|
47
|
-
transactionId: `tx_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
48
|
-
externalCustomerId,
|
|
49
|
-
code: "ai_tokens",
|
|
50
|
-
units: tokenCount,
|
|
51
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
52
|
-
properties: { model },
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const res = await fetch(`${this.lagoApiUrl}/events`, {
|
|
57
|
-
method: "POST",
|
|
58
|
-
headers: {
|
|
59
|
-
"Content-Type": "application/json",
|
|
60
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({ event }),
|
|
63
|
-
});
|
|
64
|
-
return res.ok;
|
|
65
|
-
} catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export class HyperswitchGateway {
|
|
72
|
-
private config: HyperswitchCheckoutConfig;
|
|
73
|
-
|
|
74
|
-
constructor(config: HyperswitchCheckoutConfig) {
|
|
75
|
-
this.config = {
|
|
76
|
-
baseUrl: "https://sandbox.hyperswitch.io",
|
|
77
|
-
...config,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Creates a payment intent routed through Hyperswitch.
|
|
83
|
-
*/
|
|
84
|
-
async createPaymentIntent(amountCents: number, currency: string, customerId: string, metadata: Record<string, string> = {}) {
|
|
85
|
-
if (!this.config.apiKey) {
|
|
86
|
-
return {
|
|
87
|
-
paymentId: `hyp_mock_${Date.now()}`,
|
|
88
|
-
status: "succeeded",
|
|
89
|
-
amount: amountCents,
|
|
90
|
-
currency,
|
|
91
|
-
clientSecret: "mock_secret",
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const res = await fetch(`${this.config.baseUrl}/payments`, {
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: {
|
|
99
|
-
"Content-Type": "application/json",
|
|
100
|
-
"api-key": this.config.apiKey,
|
|
101
|
-
},
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
amount: amountCents,
|
|
104
|
-
currency: currency.toUpperCase(),
|
|
105
|
-
customer_id: customerId,
|
|
106
|
-
metadata,
|
|
107
|
-
}),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (!res.ok) throw new Error(`Hyperswitch HTTP ${res.status}`);
|
|
111
|
-
return await res.json();
|
|
112
|
-
} catch (err: any) {
|
|
113
|
-
return {
|
|
114
|
-
error: err.message,
|
|
115
|
-
paymentId: `hyp_fallback_${Date.now()}`,
|
|
116
|
-
status: "requires_payment_method",
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
package/src/legal-disclaimers.ts
DELETED
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global Legal Compliance & Editorial Disclaimer Engine for B2B pSEO
|
|
3
|
-
* Multilingual compliance and anti-disparagement verification across 10+ languages.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type DisclaimerType =
|
|
7
|
-
| "comparison"
|
|
8
|
-
| "pricing"
|
|
9
|
-
| "feature"
|
|
10
|
-
| "exclusivity"
|
|
11
|
-
| "statistics"
|
|
12
|
-
| "alternative"
|
|
13
|
-
| "listicle";
|
|
14
|
-
|
|
15
|
-
export interface DisclaimerTemplate {
|
|
16
|
-
type: DisclaimerType;
|
|
17
|
-
label: string;
|
|
18
|
-
templateText: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Multilingual Disclaimer Templates (English Default + Localized Fallbacks)
|
|
23
|
-
*/
|
|
24
|
-
export const MULTILINGUAL_DISCLAIMERS: Record<string, Record<DisclaimerType, DisclaimerTemplate>> = {
|
|
25
|
-
// 🇬🇧 English (Default)
|
|
26
|
-
en: {
|
|
27
|
-
comparison: {
|
|
28
|
-
type: "comparison",
|
|
29
|
-
label: "Standard Comparison Notice",
|
|
30
|
-
templateText:
|
|
31
|
-
"Editorial note: We research and verify all claims in our comparison articles using publicly available documentation. Pricing and features are subject to change by each provider. Report any inaccuracies to {correctionsEmail}.",
|
|
32
|
-
},
|
|
33
|
-
pricing: {
|
|
34
|
-
type: "pricing",
|
|
35
|
-
label: "Pricing Transparency Disclaimer",
|
|
36
|
-
templateText:
|
|
37
|
-
"Pricing disclaimer: All competitor pricing and plan details were verified on {lastVerifiedDate}. Providers may update their rates at any time without notice. Please verify current rates on the official provider website before making purchasing decisions.",
|
|
38
|
-
},
|
|
39
|
-
feature: {
|
|
40
|
-
type: "feature",
|
|
41
|
-
label: "Feature Capability Note",
|
|
42
|
-
templateText:
|
|
43
|
-
"Feature comparison note: Feature availability and tier limits were verified on {lastVerifiedDate}. Software updates occur continuously; consult each vendor's documentation for live specifications.",
|
|
44
|
-
},
|
|
45
|
-
exclusivity: {
|
|
46
|
-
type: "exclusivity",
|
|
47
|
-
label: "Exclusivity Distinction Claim",
|
|
48
|
-
templateText:
|
|
49
|
-
"Distinction note: To the best of our knowledge as of {lastVerifiedDate}, this architectural capability distinction is accurate based on public benchmarks.",
|
|
50
|
-
},
|
|
51
|
-
statistics: {
|
|
52
|
-
type: "statistics",
|
|
53
|
-
label: "Public Data & Statistics Note",
|
|
54
|
-
templateText:
|
|
55
|
-
"Data note: Statistics and benchmarks cited in this article are derived from public industry reports and official platform metrics as of {lastVerifiedDate}.",
|
|
56
|
-
},
|
|
57
|
-
alternative: {
|
|
58
|
-
type: "alternative",
|
|
59
|
-
label: "Alternative Directory Notice",
|
|
60
|
-
templateText:
|
|
61
|
-
"Editorial note: This page compares software alternatives objectively based on public specifications, user feedback, and architectural differences to help buyers make informed decisions.",
|
|
62
|
-
},
|
|
63
|
-
listicle: {
|
|
64
|
-
type: "listicle",
|
|
65
|
-
label: "Curated Directory & Review Notice",
|
|
66
|
-
templateText:
|
|
67
|
-
"Editorial note: Rankings and software evaluations reflect our objective editorial criteria and testing methodology. Inaccuracies can be flagged to {correctionsEmail}.",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
// 🇫🇷 French
|
|
72
|
-
fr: {
|
|
73
|
-
comparison: {
|
|
74
|
-
type: "comparison",
|
|
75
|
-
label: "Note Éditoriale de Comparatif",
|
|
76
|
-
templateText:
|
|
77
|
-
"Note éditoriale : Nous vérifions scrupuleusement les informations sur la base des documentations publiques. Les fonctionnalités et tarifs évoluent régulièrement. Signalez toute inexactitude à {correctionsEmail}.",
|
|
78
|
-
},
|
|
79
|
-
pricing: {
|
|
80
|
-
type: "pricing",
|
|
81
|
-
label: "Avertissement sur les Tarifs",
|
|
82
|
-
templateText:
|
|
83
|
-
"Avertissement tarifaire : Les prix et offres ont été relevés le {lastVerifiedDate}. Les éditeurs peuvent modifier leurs conditions sans préavis. Vérifiez les tarifs officiels avant toute souscription.",
|
|
84
|
-
},
|
|
85
|
-
feature: {
|
|
86
|
-
type: "feature",
|
|
87
|
-
label: "Vérification des Fonctionnalités",
|
|
88
|
-
templateText:
|
|
89
|
-
"Note sur les fonctionnalités : La disponibilité des options a été vérifiée le {lastVerifiedDate}. Consultez les sites éditeurs pour les spécifications en direct.",
|
|
90
|
-
},
|
|
91
|
-
exclusivity: {
|
|
92
|
-
type: "exclusivity",
|
|
93
|
-
label: "Revendication d'Exclusivité",
|
|
94
|
-
templateText:
|
|
95
|
-
"Note d'exclusivité : Selon nos informations vérifiées au {lastVerifiedDate}, cette capacité technique est exclusive à notre plateforme.",
|
|
96
|
-
},
|
|
97
|
-
statistics: {
|
|
98
|
-
type: "statistics",
|
|
99
|
-
label: "Données et Statistiques Publiques",
|
|
100
|
-
templateText:
|
|
101
|
-
"Source des données : Les métriques citées proviennent de rapports sectoriels publics au {lastVerifiedDate}.",
|
|
102
|
-
},
|
|
103
|
-
alternative: {
|
|
104
|
-
type: "alternative",
|
|
105
|
-
label: "Annuaire des Alternatives",
|
|
106
|
-
templateText:
|
|
107
|
-
"Note éditoriale : Cette sélection compare objectivement les solutions du marché pour éclairer le choix des décideurs.",
|
|
108
|
-
},
|
|
109
|
-
listicle: {
|
|
110
|
-
type: "listicle",
|
|
111
|
-
label: "Méthodologie de Classement",
|
|
112
|
-
templateText:
|
|
113
|
-
"Note éditoriale : Ce classement reflète notre analyse technique indépendante. Contact : {correctionsEmail}.",
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
// 🇩🇪 German
|
|
118
|
-
de: {
|
|
119
|
-
comparison: {
|
|
120
|
-
type: "comparison",
|
|
121
|
-
label: "Redaktioneller Vergleichshinweis",
|
|
122
|
-
templateText:
|
|
123
|
-
"Redaktioneller Hinweis: Wir recherchieren und prüfen alle Angaben anhand öffentlich zugänglicher Dokumentationen. Preise und Funktionen können sich ändern. Bitte melden Sie Unstimmigkeiten an {correctionsEmail}.",
|
|
124
|
-
},
|
|
125
|
-
pricing: {
|
|
126
|
-
type: "pricing",
|
|
127
|
-
label: "Preistransparenz-Hinweis",
|
|
128
|
-
templateText:
|
|
129
|
-
"Preishinweis: Alle Preisangaben wurden am {lastVerifiedDate} überprüft. Anbieter können ihre Preise jederzeit anpassen. Bitte prüfen Sie die offiziellen Anbieterseiten.",
|
|
130
|
-
},
|
|
131
|
-
feature: {
|
|
132
|
-
type: "feature",
|
|
133
|
-
label: "Funktionshinweis",
|
|
134
|
-
templateText:
|
|
135
|
-
"Funktionshinweis: Die Verfügbarkeit von Funktionen wurde am {lastVerifiedDate} verifiziert.",
|
|
136
|
-
},
|
|
137
|
-
exclusivity: {
|
|
138
|
-
type: "exclusivity",
|
|
139
|
-
label: "Exklusivitätsangabe",
|
|
140
|
-
templateText:
|
|
141
|
-
"Hinweis: Nach unserem Kenntnisstand vom {lastVerifiedDate} ist dieses Merkmal eine spezifische Eigenschaft unserer Lösung.",
|
|
142
|
-
},
|
|
143
|
-
statistics: {
|
|
144
|
-
type: "statistics",
|
|
145
|
-
label: "Statistikhinweis",
|
|
146
|
-
templateText:
|
|
147
|
-
"Datenhinweis: Die genannten Statistiken basieren auf öffentlich zugänglichen Branchenberichten mit Stand vom {lastVerifiedDate}.",
|
|
148
|
-
},
|
|
149
|
-
alternative: {
|
|
150
|
-
type: "alternative",
|
|
151
|
-
label: "Alternativen-Verzeichnis",
|
|
152
|
-
templateText:
|
|
153
|
-
"Redaktioneller Hinweis: Dieser Vergleich bietet eine objektive Gegenüberstellung moderner Software-Alternativen.",
|
|
154
|
-
},
|
|
155
|
-
listicle: {
|
|
156
|
-
type: "listicle",
|
|
157
|
-
label: "Bewertungshinweis",
|
|
158
|
-
templateText:
|
|
159
|
-
"Redaktioneller Hinweis: Bewertungen basieren auf unserer unabhängigen Testmethodik. Feedback an {correctionsEmail}.",
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
|
|
163
|
-
// 🇪🇸 Spanish
|
|
164
|
-
es: {
|
|
165
|
-
comparison: {
|
|
166
|
-
type: "comparison",
|
|
167
|
-
label: "Nota Editorial Comparativa",
|
|
168
|
-
templateText:
|
|
169
|
-
"Nota editorial: Investigamos y verificamos la información con datos públicos. Los precios y funciones pueden variar. Notifique cualquier error a {correctionsEmail}.",
|
|
170
|
-
},
|
|
171
|
-
pricing: {
|
|
172
|
-
type: "pricing",
|
|
173
|
-
label: "Aviso sobre Precios",
|
|
174
|
-
templateText:
|
|
175
|
-
"Aviso de precios: Toda la información de precios fue verificada el {lastVerifiedDate}. Los proveedores pueden cambiar sus tarifas sin previo aviso.",
|
|
176
|
-
},
|
|
177
|
-
feature: {
|
|
178
|
-
type: "feature",
|
|
179
|
-
label: "Nota de Funcionalidades",
|
|
180
|
-
templateText:
|
|
181
|
-
"Nota de funciones: La disponibilidad de funciones fue verificada el {lastVerifiedDate}.",
|
|
182
|
-
},
|
|
183
|
-
exclusivity: {
|
|
184
|
-
type: "exclusivity",
|
|
185
|
-
label: "Distinción Exclusiva",
|
|
186
|
-
templateText:
|
|
187
|
-
"Nota de exclusividad: Según los datos verificados al {lastVerifiedDate}, esta capacidad es única de nuestra plataforma.",
|
|
188
|
-
},
|
|
189
|
-
statistics: {
|
|
190
|
-
type: "statistics",
|
|
191
|
-
label: "Datos Estadísticos",
|
|
192
|
-
templateText:
|
|
193
|
-
"Nota de datos: Las estadísticas provienen de informes sectoriales públicos al {lastVerifiedDate}.",
|
|
194
|
-
},
|
|
195
|
-
alternative: {
|
|
196
|
-
type: "alternative",
|
|
197
|
-
label: "Directorio de Alternativas",
|
|
198
|
-
templateText:
|
|
199
|
-
"Nota editorial: Esta página compara alternativas de software de forma objetiva para ayudar en la toma de decisiones.",
|
|
200
|
-
},
|
|
201
|
-
listicle: {
|
|
202
|
-
type: "listicle",
|
|
203
|
-
label: "Metodología del Ranking",
|
|
204
|
-
templateText:
|
|
205
|
-
"Nota editorial: Las recomendaciones se basan en nuestro análisis objetivo. Contacto: {correctionsEmail}.",
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Multilingual Banned & Disparaging Words Dictionary across 10 Languages
|
|
212
|
-
*/
|
|
213
|
-
export const MULTILINGUAL_BANNED_DISPARAGING_WORDS: Record<string, string[]> = {
|
|
214
|
-
// 🇬🇧 English
|
|
215
|
-
en: [
|
|
216
|
-
"is a scam",
|
|
217
|
-
"fraudulent",
|
|
218
|
-
"worst software",
|
|
219
|
-
"useless product",
|
|
220
|
-
"terrible company",
|
|
221
|
-
"don't trust",
|
|
222
|
-
"rip-off",
|
|
223
|
-
"garbage software",
|
|
224
|
-
"incompetent",
|
|
225
|
-
],
|
|
226
|
-
|
|
227
|
-
// 🇫🇷 French
|
|
228
|
-
fr: [
|
|
229
|
-
"est une arnaque",
|
|
230
|
-
"frauduleux",
|
|
231
|
-
"pire logiciel",
|
|
232
|
-
"produit inutile",
|
|
233
|
-
"entreprise malhonnête",
|
|
234
|
-
"ne faites pas confiance",
|
|
235
|
-
"escroquerie",
|
|
236
|
-
"logiciel poubelle",
|
|
237
|
-
"incompétent",
|
|
238
|
-
],
|
|
239
|
-
|
|
240
|
-
// 🇩🇪 German
|
|
241
|
-
de: [
|
|
242
|
-
"ist ein betrug",
|
|
243
|
-
"betrügerisch",
|
|
244
|
-
"schlechteste software",
|
|
245
|
-
"nutzloses produkt",
|
|
246
|
-
"unseriöse firma",
|
|
247
|
-
"nicht vertrauen",
|
|
248
|
-
"abzocke",
|
|
249
|
-
"müll software",
|
|
250
|
-
"inkompetent",
|
|
251
|
-
],
|
|
252
|
-
|
|
253
|
-
// 🇪🇸 Spanish
|
|
254
|
-
es: [
|
|
255
|
-
"es una estafa",
|
|
256
|
-
"fraudulento",
|
|
257
|
-
"peor software",
|
|
258
|
-
"producto inutil",
|
|
259
|
-
"empresa deshonesta",
|
|
260
|
-
"no confie",
|
|
261
|
-
"timo",
|
|
262
|
-
"software basura",
|
|
263
|
-
"incompetente",
|
|
264
|
-
],
|
|
265
|
-
|
|
266
|
-
// 🇮🇹 Italian
|
|
267
|
-
it: [
|
|
268
|
-
"e una truffa",
|
|
269
|
-
"fraudolento",
|
|
270
|
-
"peggior software",
|
|
271
|
-
"prodotto inutile",
|
|
272
|
-
"azienda disonesta",
|
|
273
|
-
"non fidarti",
|
|
274
|
-
"fregatura",
|
|
275
|
-
"software spazzatura",
|
|
276
|
-
],
|
|
277
|
-
|
|
278
|
-
// 🇵🇹 Portuguese
|
|
279
|
-
pt: [
|
|
280
|
-
"e um golpe",
|
|
281
|
-
"fraudulento",
|
|
282
|
-
"pior software",
|
|
283
|
-
"produto inutil",
|
|
284
|
-
"empresa desonesta",
|
|
285
|
-
"nao confie",
|
|
286
|
-
"engranacao",
|
|
287
|
-
"software lixo",
|
|
288
|
-
],
|
|
289
|
-
|
|
290
|
-
// 🇳🇱 Dutch
|
|
291
|
-
nl: [
|
|
292
|
-
"is oplichterij",
|
|
293
|
-
"frauduleus",
|
|
294
|
-
"slechtste software",
|
|
295
|
-
"nutteloos product",
|
|
296
|
-
"onbetrouwbaar bedrijf",
|
|
297
|
-
"niet vertrouwen",
|
|
298
|
-
],
|
|
299
|
-
|
|
300
|
-
// 🇷🇺 Russian (Latinized)
|
|
301
|
-
ru: [
|
|
302
|
-
"eto moshennichestvo",
|
|
303
|
-
"hudshiy soft",
|
|
304
|
-
"bespoleznyy produkt",
|
|
305
|
-
"obman",
|
|
306
|
-
],
|
|
307
|
-
|
|
308
|
-
// 🇸🇪 Swedish
|
|
309
|
-
sv: [
|
|
310
|
-
"ar en bluff",
|
|
311
|
-
"bedraglig",
|
|
312
|
-
"samsta mjukvaran",
|
|
313
|
-
"vardelos produkt",
|
|
314
|
-
"lita inte pa",
|
|
315
|
-
],
|
|
316
|
-
|
|
317
|
-
// 🇵🇱 Polish
|
|
318
|
-
pl: [
|
|
319
|
-
"to oszustwo",
|
|
320
|
-
"najgorsze oprogramowanie",
|
|
321
|
-
"bezuzyteczny produkt",
|
|
322
|
-
"nie ufaj",
|
|
323
|
-
],
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
export interface LegalEngineOptions {
|
|
327
|
-
companyName?: string;
|
|
328
|
-
correctionsEmail?: string;
|
|
329
|
-
dataStalenessDays?: number;
|
|
330
|
-
language?: string;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export class LegalDisclaimerEngine {
|
|
334
|
-
private companyName: string;
|
|
335
|
-
private correctionsEmail: string;
|
|
336
|
-
private dataStalenessDays: number;
|
|
337
|
-
private defaultLanguage: string;
|
|
338
|
-
|
|
339
|
-
constructor(options: LegalEngineOptions = {}) {
|
|
340
|
-
this.companyName = options.companyName || "Our Platform";
|
|
341
|
-
this.correctionsEmail = options.correctionsEmail || "compliance@platform.com";
|
|
342
|
-
this.dataStalenessDays = options.dataStalenessDays ?? 90;
|
|
343
|
-
this.defaultLanguage = options.language || "en";
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Generates a fully resolved disclaimer note in the target language (defaults to English).
|
|
348
|
-
*/
|
|
349
|
-
getDisclaimer(type: DisclaimerType, lastVerifiedDate?: string, language?: string): string {
|
|
350
|
-
const lang = language || this.defaultLanguage;
|
|
351
|
-
const langTemplates = MULTILINGUAL_DISCLAIMERS[lang] || MULTILINGUAL_DISCLAIMERS.en;
|
|
352
|
-
const template = langTemplates[type]?.templateText || MULTILINGUAL_DISCLAIMERS.en.comparison.templateText;
|
|
353
|
-
const dateStr = lastVerifiedDate || new Date().toISOString().split("T")[0];
|
|
354
|
-
|
|
355
|
-
return template
|
|
356
|
-
.replace(/{companyName}/g, this.companyName)
|
|
357
|
-
.replace(/{correctionsEmail}/g, this.correctionsEmail)
|
|
358
|
-
.replace(/{lastVerifiedDate}/g, dateStr);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Evaluates if competitor pricing or feature claim data is older than the allowed staleness window (default: 90 days).
|
|
363
|
-
*/
|
|
364
|
-
isDataStale(lastVerifiedDateStr?: string): boolean {
|
|
365
|
-
if (!lastVerifiedDateStr) return true;
|
|
366
|
-
const verified = new Date(lastVerifiedDateStr).getTime();
|
|
367
|
-
if (isNaN(verified)) return true;
|
|
368
|
-
|
|
369
|
-
const diffDays = (Date.now() - verified) / (1000 * 60 * 60 * 24);
|
|
370
|
-
return diffDays > this.dataStalenessDays;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Validates content against disparaging or legally hazardous statements across all supported languages.
|
|
375
|
-
*/
|
|
376
|
-
validateCompliance(content: string, competitorName: string, language: string = "all"): { isCompliant: boolean; warnings: string[] } {
|
|
377
|
-
const warnings: string[] = [];
|
|
378
|
-
const lowerContent = content.toLowerCase();
|
|
379
|
-
const lowerComp = competitorName.toLowerCase();
|
|
380
|
-
|
|
381
|
-
// Check banned phrases in target language or across all languages
|
|
382
|
-
const activeLanguages = language === "all"
|
|
383
|
-
? Object.keys(MULTILINGUAL_BANNED_DISPARAGING_WORDS)
|
|
384
|
-
: [language];
|
|
385
|
-
|
|
386
|
-
for (const lang of activeLanguages) {
|
|
387
|
-
const phrases = MULTILINGUAL_BANNED_DISPARAGING_WORDS[lang] || [];
|
|
388
|
-
for (const phrase of phrases) {
|
|
389
|
-
if (lowerContent.includes(phrase)) {
|
|
390
|
-
warnings.push(`[${lang.toUpperCase()}] Hazardous disparaging claim detected: "${phrase}". Maintain objective technical framing.`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Mention frequency check (prevent trademark keyword stuffing)
|
|
396
|
-
const regex = new RegExp(`\\b${lowerComp}\\b`, "gi");
|
|
397
|
-
const count = (lowerContent.match(regex) || []).length;
|
|
398
|
-
if (count > 25) {
|
|
399
|
-
warnings.push(`Excessive competitor trademark mentions (${count} times). Reduce to under 20 to avoid trademark dilution flags.`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return {
|
|
403
|
-
isCompliant: warnings.length === 0,
|
|
404
|
-
warnings,
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🧹 Boilerplate Stripper & Clean Markdown Extractor for LLMs (Inspiration: openserp/extract)
|
|
3
|
-
*
|
|
4
|
-
* Strips navigation, headers, footers, cookie banners, and advertisements from raw HTML
|
|
5
|
-
* to produce clean, high-density markdown text for AI search ingestion (/llms.txt & ChatGPT).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export class LlmContentCleaner {
|
|
9
|
-
/**
|
|
10
|
-
* Alias: Cleans text or markdown for LLMs.
|
|
11
|
-
*/
|
|
12
|
-
static cleanForLlm(content: string): string {
|
|
13
|
-
return this.htmlToCleanMarkdown(content);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Cleans raw HTML and returns pure readable markdown content.
|
|
18
|
-
*/
|
|
19
|
-
static htmlToCleanMarkdown(rawHtml: string): string {
|
|
20
|
-
if (!rawHtml) return "";
|
|
21
|
-
|
|
22
|
-
let cleaned = rawHtml
|
|
23
|
-
// Remove scripts, styles, iframes, SVGs
|
|
24
|
-
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, " ")
|
|
25
|
-
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, " ")
|
|
26
|
-
.replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi, " ")
|
|
27
|
-
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, " ")
|
|
28
|
-
// Remove navigation, headers, footers, modals, cookie banners
|
|
29
|
-
.replace(/<nav\b[^<]*(?:(?!<\/nav>)<[^<]*)*<\/nav>/gi, " ")
|
|
30
|
-
.replace(/<footer\b[^<]*(?:(?!<\/footer>)<[^<]*)*<\/footer>/gi, " ")
|
|
31
|
-
.replace(/<header\b[^<]*(?:(?!<\/header>)<[^<]*)*<\/header>/gi, " ")
|
|
32
|
-
.replace(/<aside\b[^<]*(?:(?!<\/aside>)<[^<]*)*<\/aside>/gi, " ")
|
|
33
|
-
// Convert headings
|
|
34
|
-
.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n")
|
|
35
|
-
.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n")
|
|
36
|
-
.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n")
|
|
37
|
-
.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/gi, "\n#### $1\n")
|
|
38
|
-
// Convert lists
|
|
39
|
-
.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, "\n* $1")
|
|
40
|
-
// Convert paragraphs and line breaks
|
|
41
|
-
.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, "\n$1\n")
|
|
42
|
-
.replace(/<br\s*[\/]?>/gi, "\n")
|
|
43
|
-
// Convert links
|
|
44
|
-
.replace(/<a[^>]*href=["']([^"']*)["'][^>]*>([\s\S]*?)<\/a>/gi, "[$2]($1)")
|
|
45
|
-
// Strip remaining HTML tags
|
|
46
|
-
.replace(/<[^>]+>/g, " ")
|
|
47
|
-
// Decode basic HTML entities
|
|
48
|
-
.replace(/ /g, " ")
|
|
49
|
-
.replace(/&/g, "&")
|
|
50
|
-
.replace(/</g, "<")
|
|
51
|
-
.replace(/>/g, ">")
|
|
52
|
-
.replace(/"/g, '"')
|
|
53
|
-
.replace(/'/g, "'");
|
|
54
|
-
|
|
55
|
-
// Clean whitespace and repeated linebreaks
|
|
56
|
-
cleaned = cleaned
|
|
57
|
-
.split("\n")
|
|
58
|
-
.map((line) => line.trim())
|
|
59
|
-
.filter((line, i, arr) => line !== "" || (i > 0 && arr[i - 1] !== ""))
|
|
60
|
-
.join("\n");
|
|
61
|
-
|
|
62
|
-
return cleaned.trim();
|
|
63
|
-
}
|
|
64
|
-
}
|