@lynxflow/seo-engine 1.7.5 → 1.7.7
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/connectors/wordpress/lynxseo-connector.php +291 -414
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/google-business-profile.d.ts +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +270 -0
- package/dist/index.mjs +270 -0
- package/dist/video-youtube-analyzer.d.ts +28 -0
- package/package.json +1 -1
- package/src/google-business-profile.ts +280 -0
- package/src/index.ts +2 -0
- package/src/video-youtube-analyzer.ts +81 -0
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface GoogleBusinessProfileConfig {
|
|
2
|
+
placeId?: string;
|
|
3
|
+
businessName: string;
|
|
4
|
+
category: string;
|
|
5
|
+
streetAddress: string;
|
|
6
|
+
addressLocality: string;
|
|
7
|
+
postalCode: string;
|
|
8
|
+
addressCountry: string;
|
|
9
|
+
telephone: string;
|
|
10
|
+
priceRange?: string;
|
|
11
|
+
latitude?: number;
|
|
12
|
+
longitude?: number;
|
|
13
|
+
openingHours?: string[];
|
|
14
|
+
ratingValue?: number;
|
|
15
|
+
reviewCount?: number;
|
|
16
|
+
googleMapsUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface GbpAuditResult {
|
|
19
|
+
score: number;
|
|
20
|
+
grade: "A+" | "A" | "B" | "C" | "D";
|
|
21
|
+
passedChecks: number;
|
|
22
|
+
totalChecks: number;
|
|
23
|
+
checks: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
status: "pass" | "warn" | "fail";
|
|
26
|
+
points: number;
|
|
27
|
+
description: string;
|
|
28
|
+
recommendation?: string;
|
|
29
|
+
}>;
|
|
30
|
+
summary: string;
|
|
31
|
+
localPackEligibility: "Excellente" | "Moyenne" | "Faible";
|
|
32
|
+
}
|
|
33
|
+
export declare class GoogleBusinessProfileEngine {
|
|
34
|
+
/**
|
|
35
|
+
* Generates Schema.org LocalBusiness JSON-LD structure matching Google specifications
|
|
36
|
+
*/
|
|
37
|
+
static generateLocalBusinessSchema(config: GoogleBusinessProfileConfig): Record<string, any>;
|
|
38
|
+
/**
|
|
39
|
+
* Generates interactive HTML Badge with Google Review stars and direct Maps link
|
|
40
|
+
*/
|
|
41
|
+
static generateGbpBadgeHtml(config: GoogleBusinessProfileConfig): string;
|
|
42
|
+
/**
|
|
43
|
+
* Complete Diagnostic & Audit of a Google Business Profile / Local SEO setup
|
|
44
|
+
*/
|
|
45
|
+
static auditProfile(config: GoogleBusinessProfileConfig): GbpAuditResult;
|
|
46
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ export * from "./yoast-parity";
|
|
|
62
62
|
export * from "./social-growth-suite";
|
|
63
63
|
export * from "./team-rbac";
|
|
64
64
|
export * from "./real-reviews-sync";
|
|
65
|
+
export * from "./google-business-profile";
|
|
66
|
+
export * from "./video-youtube-analyzer";
|
|
65
67
|
import { LynxSeoEngine, type EngineConfig } from "./engine";
|
|
66
68
|
import { ApiKeyGuardian } from "./auth-key";
|
|
67
69
|
import { TokenQuotaManager } from "./token-quota-manager";
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,7 @@ __export(exports_src, {
|
|
|
50
50
|
createLynxSeoEngine: () => createLynxSeoEngine,
|
|
51
51
|
cleanSeoSlug: () => cleanSeoSlug,
|
|
52
52
|
YoastParityEngine: () => YoastParityEngine,
|
|
53
|
+
VideoYouTubeAnalyzer: () => VideoYouTubeAnalyzer,
|
|
53
54
|
UrlyticsEngine: () => UrlyticsEngine,
|
|
54
55
|
UI_ICONS: () => UI_ICONS,
|
|
55
56
|
TokenQuotaManager: () => TokenQuotaManager,
|
|
@@ -91,6 +92,7 @@ __export(exports_src, {
|
|
|
91
92
|
IndexNowClient: () => IndexNowClient,
|
|
92
93
|
I18nDetector: () => I18nDetector,
|
|
93
94
|
HyperswitchGateway: () => HyperswitchGateway,
|
|
95
|
+
GoogleBusinessProfileEngine: () => GoogleBusinessProfileEngine,
|
|
94
96
|
GeoMeshLinkingEngine: () => GeoMeshLinkingEngine,
|
|
95
97
|
FreePublicToolsEngine: () => FreePublicToolsEngine,
|
|
96
98
|
ExtendedSchemaGraphBuilder: () => ExtendedSchemaGraphBuilder,
|
|
@@ -7076,6 +7078,274 @@ class RealReviewsSyncEngine {
|
|
|
7076
7078
|
`;
|
|
7077
7079
|
}
|
|
7078
7080
|
}
|
|
7081
|
+
// src/google-business-profile.ts
|
|
7082
|
+
class GoogleBusinessProfileEngine {
|
|
7083
|
+
static generateLocalBusinessSchema(config) {
|
|
7084
|
+
const schema = {
|
|
7085
|
+
"@context": "https://schema.org",
|
|
7086
|
+
"@type": config.category || "LocalBusiness",
|
|
7087
|
+
name: config.businessName,
|
|
7088
|
+
address: {
|
|
7089
|
+
"@type": "PostalAddress",
|
|
7090
|
+
streetAddress: config.streetAddress,
|
|
7091
|
+
addressLocality: config.addressLocality,
|
|
7092
|
+
postalCode: config.postalCode,
|
|
7093
|
+
addressCountry: config.addressCountry || "FR"
|
|
7094
|
+
},
|
|
7095
|
+
telephone: config.telephone,
|
|
7096
|
+
priceRange: config.priceRange || "€€"
|
|
7097
|
+
};
|
|
7098
|
+
if (config.latitude && config.longitude) {
|
|
7099
|
+
schema.geo = {
|
|
7100
|
+
"@type": "GeoCoordinates",
|
|
7101
|
+
latitude: config.latitude,
|
|
7102
|
+
longitude: config.longitude
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
if (config.openingHours && config.openingHours.length > 0) {
|
|
7106
|
+
schema.openingHours = config.openingHours;
|
|
7107
|
+
}
|
|
7108
|
+
if (config.ratingValue && config.reviewCount) {
|
|
7109
|
+
schema.aggregateRating = {
|
|
7110
|
+
"@type": "AggregateRating",
|
|
7111
|
+
ratingValue: config.ratingValue.toString(),
|
|
7112
|
+
reviewCount: config.reviewCount.toString(),
|
|
7113
|
+
bestRating: "5",
|
|
7114
|
+
worstRating: "1"
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
if (config.googleMapsUrl || config.placeId) {
|
|
7118
|
+
schema.hasMap = config.googleMapsUrl || `https://www.google.com/maps/place/?q=place_id:${config.placeId}`;
|
|
7119
|
+
}
|
|
7120
|
+
return schema;
|
|
7121
|
+
}
|
|
7122
|
+
static generateGbpBadgeHtml(config) {
|
|
7123
|
+
const stars = "★".repeat(Math.round(config.ratingValue || 5)) + "☆".repeat(5 - Math.round(config.ratingValue || 5));
|
|
7124
|
+
const mapsUrl = config.googleMapsUrl || (config.placeId ? `https://www.google.com/maps/place/?q=place_id:${config.placeId}` : "#");
|
|
7125
|
+
return `
|
|
7126
|
+
<div style="background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px;max-width:420px;box-shadow:0 2px 4px rgba(0,0,0,0.04);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
7127
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
|
7128
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
7129
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="#4285F4"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>
|
|
7130
|
+
<strong style="font-size:15px;color:#0f172a;">${config.businessName}</strong>
|
|
7131
|
+
</div>
|
|
7132
|
+
<span style="font-size:11px;font-weight:700;color:#166534;background:#dcfce7;padding:2px 8px;border-radius:999px;">Vérifié Google</span>
|
|
7133
|
+
</div>
|
|
7134
|
+
<div style="font-size:13px;color:#475569;margin-bottom:8px;">
|
|
7135
|
+
${config.streetAddress}, ${config.postalCode} ${config.addressLocality}
|
|
7136
|
+
</div>
|
|
7137
|
+
<div style="display:flex;align-items:center;justify-content:space-between;border-top:1px solid #f1f5f9;padding-top:10px;">
|
|
7138
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
7139
|
+
<span style="color:#f59e0b;letter-spacing:1px;font-size:14px;">${stars}</span>
|
|
7140
|
+
<strong style="font-size:13px;color:#0f172a;">${config.ratingValue || "4.9"}/5</strong>
|
|
7141
|
+
<span style="font-size:12px;color:#64748b;">(${config.reviewCount || "128"} avis)</span>
|
|
7142
|
+
</div>
|
|
7143
|
+
<a href="${mapsUrl}" target="_blank" rel="noopener noreferrer" style="font-size:12px;color:#2563eb;font-weight:600;text-decoration:none;">Voir sur Google Maps →</a>
|
|
7144
|
+
</div>
|
|
7145
|
+
</div>
|
|
7146
|
+
`.trim();
|
|
7147
|
+
}
|
|
7148
|
+
static auditProfile(config) {
|
|
7149
|
+
const checks = [];
|
|
7150
|
+
let score = 0;
|
|
7151
|
+
if (config.businessName && config.businessName.length >= 3) {
|
|
7152
|
+
checks.push({
|
|
7153
|
+
name: "Nom de l'établissement (NAP)",
|
|
7154
|
+
status: "pass",
|
|
7155
|
+
points: 15,
|
|
7156
|
+
description: `Nom correctement configuré : "${config.businessName}".`
|
|
7157
|
+
});
|
|
7158
|
+
score += 15;
|
|
7159
|
+
} else {
|
|
7160
|
+
checks.push({
|
|
7161
|
+
name: "Nom de l'établissement (NAP)",
|
|
7162
|
+
status: "fail",
|
|
7163
|
+
points: 15,
|
|
7164
|
+
description: "Nom d'établissement manquant ou trop court.",
|
|
7165
|
+
recommendation: "Renseignez le nom exact de votre entreprise tel qu'affiché sur Google Maps."
|
|
7166
|
+
});
|
|
7167
|
+
}
|
|
7168
|
+
if (config.streetAddress && config.postalCode && config.addressLocality) {
|
|
7169
|
+
checks.push({
|
|
7170
|
+
name: "Adresse physique complète",
|
|
7171
|
+
status: "pass",
|
|
7172
|
+
points: 20,
|
|
7173
|
+
description: `Adresse géolocalisée : ${config.streetAddress}, ${config.postalCode} ${config.addressLocality}.`
|
|
7174
|
+
});
|
|
7175
|
+
score += 20;
|
|
7176
|
+
} else {
|
|
7177
|
+
checks.push({
|
|
7178
|
+
name: "Adresse physique complète",
|
|
7179
|
+
status: "fail",
|
|
7180
|
+
points: 20,
|
|
7181
|
+
description: "Adresse ou code postal incomplet.",
|
|
7182
|
+
recommendation: "Renseignez la rue, le code postal et la ville pour le ciblage Google Maps Local Pack."
|
|
7183
|
+
});
|
|
7184
|
+
}
|
|
7185
|
+
if (config.telephone && config.telephone.length >= 8) {
|
|
7186
|
+
checks.push({
|
|
7187
|
+
name: "Téléphone direct vérifié",
|
|
7188
|
+
status: "pass",
|
|
7189
|
+
points: 10,
|
|
7190
|
+
description: `Numéro de contact : ${config.telephone}.`
|
|
7191
|
+
});
|
|
7192
|
+
score += 10;
|
|
7193
|
+
} else {
|
|
7194
|
+
checks.push({
|
|
7195
|
+
name: "Téléphone direct vérifié",
|
|
7196
|
+
status: "warn",
|
|
7197
|
+
points: 10,
|
|
7198
|
+
description: "Numéro de téléphone absent.",
|
|
7199
|
+
recommendation: "Ajoutez un numéro de téléphone fixe ou mobile local pour permettre les appels directs depuis les SERP."
|
|
7200
|
+
});
|
|
7201
|
+
}
|
|
7202
|
+
if (config.latitude && config.longitude && !isNaN(config.latitude) && !isNaN(config.longitude)) {
|
|
7203
|
+
checks.push({
|
|
7204
|
+
name: "Coordonnées GPS (Lat / Long)",
|
|
7205
|
+
status: "pass",
|
|
7206
|
+
points: 15,
|
|
7207
|
+
description: `Positionnement précis : ${config.latitude}, ${config.longitude}.`
|
|
7208
|
+
});
|
|
7209
|
+
score += 15;
|
|
7210
|
+
} else {
|
|
7211
|
+
checks.push({
|
|
7212
|
+
name: "Coordonnées GPS (Lat / Long)",
|
|
7213
|
+
status: "fail",
|
|
7214
|
+
points: 15,
|
|
7215
|
+
description: "Coordonnées GPS manquantes dans le schéma.",
|
|
7216
|
+
recommendation: "Ajoutez les coordonnées de latitude/longitude exactes pour optimiser l'affichage dans le Google 3-Pack."
|
|
7217
|
+
});
|
|
7218
|
+
}
|
|
7219
|
+
if (config.openingHours && config.openingHours.length > 0) {
|
|
7220
|
+
checks.push({
|
|
7221
|
+
name: "Horaires d'ouverture",
|
|
7222
|
+
status: "pass",
|
|
7223
|
+
points: 10,
|
|
7224
|
+
description: `${config.openingHours.length} créneau(x) d'ouverture configuré(s).`
|
|
7225
|
+
});
|
|
7226
|
+
score += 10;
|
|
7227
|
+
} else {
|
|
7228
|
+
checks.push({
|
|
7229
|
+
name: "Horaires d'ouverture",
|
|
7230
|
+
status: "warn",
|
|
7231
|
+
points: 10,
|
|
7232
|
+
description: "Horaires d'ouverture non spécifiés.",
|
|
7233
|
+
recommendation: "Indiquez vos horaires (ex: Mo-Fr 09:00-18:00) pour rassurer les clients et éviter le statut 'Fermé'."
|
|
7234
|
+
});
|
|
7235
|
+
}
|
|
7236
|
+
const rating = config.ratingValue || 0;
|
|
7237
|
+
const reviews = config.reviewCount || 0;
|
|
7238
|
+
if (rating >= 4.5 && reviews >= 20) {
|
|
7239
|
+
checks.push({
|
|
7240
|
+
name: "Réputation & Preuve Sociale",
|
|
7241
|
+
status: "pass",
|
|
7242
|
+
points: 20,
|
|
7243
|
+
description: `Excellente réputation : ${rating}/5 étoiles sur ${reviews} avis.`
|
|
7244
|
+
});
|
|
7245
|
+
score += 20;
|
|
7246
|
+
} else if (rating >= 4 && reviews > 0) {
|
|
7247
|
+
checks.push({
|
|
7248
|
+
name: "Réputation & Preuve Sociale",
|
|
7249
|
+
status: "warn",
|
|
7250
|
+
points: 10,
|
|
7251
|
+
description: `Note de ${rating}/5 avec ${reviews} avis.`,
|
|
7252
|
+
recommendation: "Augmentez le volume d'avis certifiés au-delà de 20 avis pour maximiser le taux de conversion."
|
|
7253
|
+
});
|
|
7254
|
+
score += 10;
|
|
7255
|
+
} else {
|
|
7256
|
+
checks.push({
|
|
7257
|
+
name: "Réputation & Preuve Sociale",
|
|
7258
|
+
status: "fail",
|
|
7259
|
+
points: 20,
|
|
7260
|
+
description: "Avis clients absents ou note inférieure à 4.0.",
|
|
7261
|
+
recommendation: "Activez la collecte d'avis clients réels pour afficher les étoiles dorées dans Google."
|
|
7262
|
+
});
|
|
7263
|
+
}
|
|
7264
|
+
if (config.placeId || config.googleMapsUrl) {
|
|
7265
|
+
checks.push({
|
|
7266
|
+
name: "Lien Google Place ID officiel",
|
|
7267
|
+
status: "pass",
|
|
7268
|
+
points: 10,
|
|
7269
|
+
description: "Lien direct vers la fiche Maps configuré."
|
|
7270
|
+
});
|
|
7271
|
+
score += 10;
|
|
7272
|
+
} else {
|
|
7273
|
+
checks.push({
|
|
7274
|
+
name: "Lien Google Place ID officiel",
|
|
7275
|
+
status: "warn",
|
|
7276
|
+
points: 10,
|
|
7277
|
+
description: "Place ID Google non renseigné.",
|
|
7278
|
+
recommendation: "Associez votre Place ID Google pour faciliter l'indexation par Google Maps."
|
|
7279
|
+
});
|
|
7280
|
+
}
|
|
7281
|
+
const passedChecks = checks.filter((c) => c.status === "pass").length;
|
|
7282
|
+
const grade = score >= 90 ? "A+" : score >= 75 ? "A" : score >= 60 ? "B" : score >= 40 ? "C" : "D";
|
|
7283
|
+
const eligibility = score >= 80 ? "Excellente" : score >= 50 ? "Moyenne" : "Faible";
|
|
7284
|
+
return {
|
|
7285
|
+
score,
|
|
7286
|
+
grade,
|
|
7287
|
+
passedChecks,
|
|
7288
|
+
totalChecks: checks.length,
|
|
7289
|
+
checks,
|
|
7290
|
+
summary: `Score Local SEO de ${score}/100 (Grade ${grade}) — Éligibilité Google Local 3-Pack : ${eligibility}.`,
|
|
7291
|
+
localPackEligibility: eligibility
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
// src/video-youtube-analyzer.ts
|
|
7296
|
+
class VideoYouTubeAnalyzer {
|
|
7297
|
+
static extractVideoId(urlOrIframe) {
|
|
7298
|
+
const regExp = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
7299
|
+
const match = urlOrIframe.match(regExp);
|
|
7300
|
+
return match ? match[1] : null;
|
|
7301
|
+
}
|
|
7302
|
+
static generateVideoObjectSchema(meta) {
|
|
7303
|
+
const videoId = meta.videoId;
|
|
7304
|
+
const thumbnail = meta.thumbnailUrl || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
|
7305
|
+
const embedUrl = meta.embedUrl || `https://www.youtube.com/embed/${videoId}`;
|
|
7306
|
+
const schema = {
|
|
7307
|
+
"@context": "https://schema.org",
|
|
7308
|
+
"@type": "VideoObject",
|
|
7309
|
+
name: meta.title,
|
|
7310
|
+
description: meta.description,
|
|
7311
|
+
thumbnailUrl: [thumbnail],
|
|
7312
|
+
uploadDate: meta.uploadDate || new Date().toISOString().split("T")[0],
|
|
7313
|
+
embedUrl,
|
|
7314
|
+
contentUrl: `https://www.youtube.com/watch?v=${videoId}`
|
|
7315
|
+
};
|
|
7316
|
+
if (meta.duration) {
|
|
7317
|
+
schema.duration = meta.duration;
|
|
7318
|
+
}
|
|
7319
|
+
if (meta.chapters && meta.chapters.length > 0) {
|
|
7320
|
+
schema.hasPart = meta.chapters.map((c) => ({
|
|
7321
|
+
"@type": "Clip",
|
|
7322
|
+
name: c.title,
|
|
7323
|
+
startOffset: c.time,
|
|
7324
|
+
url: `https://www.youtube.com/watch?v=${videoId}&t=${c.time}s`
|
|
7325
|
+
}));
|
|
7326
|
+
}
|
|
7327
|
+
return schema;
|
|
7328
|
+
}
|
|
7329
|
+
static generateSeoVideoEmbedHtml(meta) {
|
|
7330
|
+
const videoId = meta.videoId;
|
|
7331
|
+
const jsonLd = JSON.stringify(this.generateVideoObjectSchema(meta));
|
|
7332
|
+
return `
|
|
7333
|
+
<div class="lynxseo-video-wrapper" style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,0.08);margin:24px 0;">
|
|
7334
|
+
<iframe
|
|
7335
|
+
src="https://www.youtube.com/embed/${videoId}"
|
|
7336
|
+
title="${meta.title.replace(/"/g, """)}"
|
|
7337
|
+
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
|
|
7338
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
7339
|
+
allowfullscreen
|
|
7340
|
+
loading="lazy">
|
|
7341
|
+
</iframe>
|
|
7342
|
+
</div>
|
|
7343
|
+
<script type="application/ld+json">
|
|
7344
|
+
${jsonLd}
|
|
7345
|
+
</script>
|
|
7346
|
+
`.trim();
|
|
7347
|
+
}
|
|
7348
|
+
}
|
|
7079
7349
|
// src/index.ts
|
|
7080
7350
|
function createLynxSeoEngine(config) {
|
|
7081
7351
|
return new LynxSeoEngine(config);
|
package/dist/index.mjs
CHANGED
|
@@ -6964,6 +6964,274 @@ class RealReviewsSyncEngine {
|
|
|
6964
6964
|
`;
|
|
6965
6965
|
}
|
|
6966
6966
|
}
|
|
6967
|
+
// src/google-business-profile.ts
|
|
6968
|
+
class GoogleBusinessProfileEngine {
|
|
6969
|
+
static generateLocalBusinessSchema(config) {
|
|
6970
|
+
const schema = {
|
|
6971
|
+
"@context": "https://schema.org",
|
|
6972
|
+
"@type": config.category || "LocalBusiness",
|
|
6973
|
+
name: config.businessName,
|
|
6974
|
+
address: {
|
|
6975
|
+
"@type": "PostalAddress",
|
|
6976
|
+
streetAddress: config.streetAddress,
|
|
6977
|
+
addressLocality: config.addressLocality,
|
|
6978
|
+
postalCode: config.postalCode,
|
|
6979
|
+
addressCountry: config.addressCountry || "FR"
|
|
6980
|
+
},
|
|
6981
|
+
telephone: config.telephone,
|
|
6982
|
+
priceRange: config.priceRange || "€€"
|
|
6983
|
+
};
|
|
6984
|
+
if (config.latitude && config.longitude) {
|
|
6985
|
+
schema.geo = {
|
|
6986
|
+
"@type": "GeoCoordinates",
|
|
6987
|
+
latitude: config.latitude,
|
|
6988
|
+
longitude: config.longitude
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6991
|
+
if (config.openingHours && config.openingHours.length > 0) {
|
|
6992
|
+
schema.openingHours = config.openingHours;
|
|
6993
|
+
}
|
|
6994
|
+
if (config.ratingValue && config.reviewCount) {
|
|
6995
|
+
schema.aggregateRating = {
|
|
6996
|
+
"@type": "AggregateRating",
|
|
6997
|
+
ratingValue: config.ratingValue.toString(),
|
|
6998
|
+
reviewCount: config.reviewCount.toString(),
|
|
6999
|
+
bestRating: "5",
|
|
7000
|
+
worstRating: "1"
|
|
7001
|
+
};
|
|
7002
|
+
}
|
|
7003
|
+
if (config.googleMapsUrl || config.placeId) {
|
|
7004
|
+
schema.hasMap = config.googleMapsUrl || `https://www.google.com/maps/place/?q=place_id:${config.placeId}`;
|
|
7005
|
+
}
|
|
7006
|
+
return schema;
|
|
7007
|
+
}
|
|
7008
|
+
static generateGbpBadgeHtml(config) {
|
|
7009
|
+
const stars = "★".repeat(Math.round(config.ratingValue || 5)) + "☆".repeat(5 - Math.round(config.ratingValue || 5));
|
|
7010
|
+
const mapsUrl = config.googleMapsUrl || (config.placeId ? `https://www.google.com/maps/place/?q=place_id:${config.placeId}` : "#");
|
|
7011
|
+
return `
|
|
7012
|
+
<div style="background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px;max-width:420px;box-shadow:0 2px 4px rgba(0,0,0,0.04);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
7013
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
|
|
7014
|
+
<div style="display:flex;align-items:center;gap:8px;">
|
|
7015
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="#4285F4"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>
|
|
7016
|
+
<strong style="font-size:15px;color:#0f172a;">${config.businessName}</strong>
|
|
7017
|
+
</div>
|
|
7018
|
+
<span style="font-size:11px;font-weight:700;color:#166534;background:#dcfce7;padding:2px 8px;border-radius:999px;">Vérifié Google</span>
|
|
7019
|
+
</div>
|
|
7020
|
+
<div style="font-size:13px;color:#475569;margin-bottom:8px;">
|
|
7021
|
+
${config.streetAddress}, ${config.postalCode} ${config.addressLocality}
|
|
7022
|
+
</div>
|
|
7023
|
+
<div style="display:flex;align-items:center;justify-content:space-between;border-top:1px solid #f1f5f9;padding-top:10px;">
|
|
7024
|
+
<div style="display:flex;align-items:center;gap:6px;">
|
|
7025
|
+
<span style="color:#f59e0b;letter-spacing:1px;font-size:14px;">${stars}</span>
|
|
7026
|
+
<strong style="font-size:13px;color:#0f172a;">${config.ratingValue || "4.9"}/5</strong>
|
|
7027
|
+
<span style="font-size:12px;color:#64748b;">(${config.reviewCount || "128"} avis)</span>
|
|
7028
|
+
</div>
|
|
7029
|
+
<a href="${mapsUrl}" target="_blank" rel="noopener noreferrer" style="font-size:12px;color:#2563eb;font-weight:600;text-decoration:none;">Voir sur Google Maps →</a>
|
|
7030
|
+
</div>
|
|
7031
|
+
</div>
|
|
7032
|
+
`.trim();
|
|
7033
|
+
}
|
|
7034
|
+
static auditProfile(config) {
|
|
7035
|
+
const checks = [];
|
|
7036
|
+
let score = 0;
|
|
7037
|
+
if (config.businessName && config.businessName.length >= 3) {
|
|
7038
|
+
checks.push({
|
|
7039
|
+
name: "Nom de l'établissement (NAP)",
|
|
7040
|
+
status: "pass",
|
|
7041
|
+
points: 15,
|
|
7042
|
+
description: `Nom correctement configuré : "${config.businessName}".`
|
|
7043
|
+
});
|
|
7044
|
+
score += 15;
|
|
7045
|
+
} else {
|
|
7046
|
+
checks.push({
|
|
7047
|
+
name: "Nom de l'établissement (NAP)",
|
|
7048
|
+
status: "fail",
|
|
7049
|
+
points: 15,
|
|
7050
|
+
description: "Nom d'établissement manquant ou trop court.",
|
|
7051
|
+
recommendation: "Renseignez le nom exact de votre entreprise tel qu'affiché sur Google Maps."
|
|
7052
|
+
});
|
|
7053
|
+
}
|
|
7054
|
+
if (config.streetAddress && config.postalCode && config.addressLocality) {
|
|
7055
|
+
checks.push({
|
|
7056
|
+
name: "Adresse physique complète",
|
|
7057
|
+
status: "pass",
|
|
7058
|
+
points: 20,
|
|
7059
|
+
description: `Adresse géolocalisée : ${config.streetAddress}, ${config.postalCode} ${config.addressLocality}.`
|
|
7060
|
+
});
|
|
7061
|
+
score += 20;
|
|
7062
|
+
} else {
|
|
7063
|
+
checks.push({
|
|
7064
|
+
name: "Adresse physique complète",
|
|
7065
|
+
status: "fail",
|
|
7066
|
+
points: 20,
|
|
7067
|
+
description: "Adresse ou code postal incomplet.",
|
|
7068
|
+
recommendation: "Renseignez la rue, le code postal et la ville pour le ciblage Google Maps Local Pack."
|
|
7069
|
+
});
|
|
7070
|
+
}
|
|
7071
|
+
if (config.telephone && config.telephone.length >= 8) {
|
|
7072
|
+
checks.push({
|
|
7073
|
+
name: "Téléphone direct vérifié",
|
|
7074
|
+
status: "pass",
|
|
7075
|
+
points: 10,
|
|
7076
|
+
description: `Numéro de contact : ${config.telephone}.`
|
|
7077
|
+
});
|
|
7078
|
+
score += 10;
|
|
7079
|
+
} else {
|
|
7080
|
+
checks.push({
|
|
7081
|
+
name: "Téléphone direct vérifié",
|
|
7082
|
+
status: "warn",
|
|
7083
|
+
points: 10,
|
|
7084
|
+
description: "Numéro de téléphone absent.",
|
|
7085
|
+
recommendation: "Ajoutez un numéro de téléphone fixe ou mobile local pour permettre les appels directs depuis les SERP."
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
if (config.latitude && config.longitude && !isNaN(config.latitude) && !isNaN(config.longitude)) {
|
|
7089
|
+
checks.push({
|
|
7090
|
+
name: "Coordonnées GPS (Lat / Long)",
|
|
7091
|
+
status: "pass",
|
|
7092
|
+
points: 15,
|
|
7093
|
+
description: `Positionnement précis : ${config.latitude}, ${config.longitude}.`
|
|
7094
|
+
});
|
|
7095
|
+
score += 15;
|
|
7096
|
+
} else {
|
|
7097
|
+
checks.push({
|
|
7098
|
+
name: "Coordonnées GPS (Lat / Long)",
|
|
7099
|
+
status: "fail",
|
|
7100
|
+
points: 15,
|
|
7101
|
+
description: "Coordonnées GPS manquantes dans le schéma.",
|
|
7102
|
+
recommendation: "Ajoutez les coordonnées de latitude/longitude exactes pour optimiser l'affichage dans le Google 3-Pack."
|
|
7103
|
+
});
|
|
7104
|
+
}
|
|
7105
|
+
if (config.openingHours && config.openingHours.length > 0) {
|
|
7106
|
+
checks.push({
|
|
7107
|
+
name: "Horaires d'ouverture",
|
|
7108
|
+
status: "pass",
|
|
7109
|
+
points: 10,
|
|
7110
|
+
description: `${config.openingHours.length} créneau(x) d'ouverture configuré(s).`
|
|
7111
|
+
});
|
|
7112
|
+
score += 10;
|
|
7113
|
+
} else {
|
|
7114
|
+
checks.push({
|
|
7115
|
+
name: "Horaires d'ouverture",
|
|
7116
|
+
status: "warn",
|
|
7117
|
+
points: 10,
|
|
7118
|
+
description: "Horaires d'ouverture non spécifiés.",
|
|
7119
|
+
recommendation: "Indiquez vos horaires (ex: Mo-Fr 09:00-18:00) pour rassurer les clients et éviter le statut 'Fermé'."
|
|
7120
|
+
});
|
|
7121
|
+
}
|
|
7122
|
+
const rating = config.ratingValue || 0;
|
|
7123
|
+
const reviews = config.reviewCount || 0;
|
|
7124
|
+
if (rating >= 4.5 && reviews >= 20) {
|
|
7125
|
+
checks.push({
|
|
7126
|
+
name: "Réputation & Preuve Sociale",
|
|
7127
|
+
status: "pass",
|
|
7128
|
+
points: 20,
|
|
7129
|
+
description: `Excellente réputation : ${rating}/5 étoiles sur ${reviews} avis.`
|
|
7130
|
+
});
|
|
7131
|
+
score += 20;
|
|
7132
|
+
} else if (rating >= 4 && reviews > 0) {
|
|
7133
|
+
checks.push({
|
|
7134
|
+
name: "Réputation & Preuve Sociale",
|
|
7135
|
+
status: "warn",
|
|
7136
|
+
points: 10,
|
|
7137
|
+
description: `Note de ${rating}/5 avec ${reviews} avis.`,
|
|
7138
|
+
recommendation: "Augmentez le volume d'avis certifiés au-delà de 20 avis pour maximiser le taux de conversion."
|
|
7139
|
+
});
|
|
7140
|
+
score += 10;
|
|
7141
|
+
} else {
|
|
7142
|
+
checks.push({
|
|
7143
|
+
name: "Réputation & Preuve Sociale",
|
|
7144
|
+
status: "fail",
|
|
7145
|
+
points: 20,
|
|
7146
|
+
description: "Avis clients absents ou note inférieure à 4.0.",
|
|
7147
|
+
recommendation: "Activez la collecte d'avis clients réels pour afficher les étoiles dorées dans Google."
|
|
7148
|
+
});
|
|
7149
|
+
}
|
|
7150
|
+
if (config.placeId || config.googleMapsUrl) {
|
|
7151
|
+
checks.push({
|
|
7152
|
+
name: "Lien Google Place ID officiel",
|
|
7153
|
+
status: "pass",
|
|
7154
|
+
points: 10,
|
|
7155
|
+
description: "Lien direct vers la fiche Maps configuré."
|
|
7156
|
+
});
|
|
7157
|
+
score += 10;
|
|
7158
|
+
} else {
|
|
7159
|
+
checks.push({
|
|
7160
|
+
name: "Lien Google Place ID officiel",
|
|
7161
|
+
status: "warn",
|
|
7162
|
+
points: 10,
|
|
7163
|
+
description: "Place ID Google non renseigné.",
|
|
7164
|
+
recommendation: "Associez votre Place ID Google pour faciliter l'indexation par Google Maps."
|
|
7165
|
+
});
|
|
7166
|
+
}
|
|
7167
|
+
const passedChecks = checks.filter((c) => c.status === "pass").length;
|
|
7168
|
+
const grade = score >= 90 ? "A+" : score >= 75 ? "A" : score >= 60 ? "B" : score >= 40 ? "C" : "D";
|
|
7169
|
+
const eligibility = score >= 80 ? "Excellente" : score >= 50 ? "Moyenne" : "Faible";
|
|
7170
|
+
return {
|
|
7171
|
+
score,
|
|
7172
|
+
grade,
|
|
7173
|
+
passedChecks,
|
|
7174
|
+
totalChecks: checks.length,
|
|
7175
|
+
checks,
|
|
7176
|
+
summary: `Score Local SEO de ${score}/100 (Grade ${grade}) — Éligibilité Google Local 3-Pack : ${eligibility}.`,
|
|
7177
|
+
localPackEligibility: eligibility
|
|
7178
|
+
};
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
// src/video-youtube-analyzer.ts
|
|
7182
|
+
class VideoYouTubeAnalyzer {
|
|
7183
|
+
static extractVideoId(urlOrIframe) {
|
|
7184
|
+
const regExp = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
|
|
7185
|
+
const match = urlOrIframe.match(regExp);
|
|
7186
|
+
return match ? match[1] : null;
|
|
7187
|
+
}
|
|
7188
|
+
static generateVideoObjectSchema(meta) {
|
|
7189
|
+
const videoId = meta.videoId;
|
|
7190
|
+
const thumbnail = meta.thumbnailUrl || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
|
7191
|
+
const embedUrl = meta.embedUrl || `https://www.youtube.com/embed/${videoId}`;
|
|
7192
|
+
const schema = {
|
|
7193
|
+
"@context": "https://schema.org",
|
|
7194
|
+
"@type": "VideoObject",
|
|
7195
|
+
name: meta.title,
|
|
7196
|
+
description: meta.description,
|
|
7197
|
+
thumbnailUrl: [thumbnail],
|
|
7198
|
+
uploadDate: meta.uploadDate || new Date().toISOString().split("T")[0],
|
|
7199
|
+
embedUrl,
|
|
7200
|
+
contentUrl: `https://www.youtube.com/watch?v=${videoId}`
|
|
7201
|
+
};
|
|
7202
|
+
if (meta.duration) {
|
|
7203
|
+
schema.duration = meta.duration;
|
|
7204
|
+
}
|
|
7205
|
+
if (meta.chapters && meta.chapters.length > 0) {
|
|
7206
|
+
schema.hasPart = meta.chapters.map((c) => ({
|
|
7207
|
+
"@type": "Clip",
|
|
7208
|
+
name: c.title,
|
|
7209
|
+
startOffset: c.time,
|
|
7210
|
+
url: `https://www.youtube.com/watch?v=${videoId}&t=${c.time}s`
|
|
7211
|
+
}));
|
|
7212
|
+
}
|
|
7213
|
+
return schema;
|
|
7214
|
+
}
|
|
7215
|
+
static generateSeoVideoEmbedHtml(meta) {
|
|
7216
|
+
const videoId = meta.videoId;
|
|
7217
|
+
const jsonLd = JSON.stringify(this.generateVideoObjectSchema(meta));
|
|
7218
|
+
return `
|
|
7219
|
+
<div class="lynxseo-video-wrapper" style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,0.08);margin:24px 0;">
|
|
7220
|
+
<iframe
|
|
7221
|
+
src="https://www.youtube.com/embed/${videoId}"
|
|
7222
|
+
title="${meta.title.replace(/"/g, """)}"
|
|
7223
|
+
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
|
|
7224
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
7225
|
+
allowfullscreen
|
|
7226
|
+
loading="lazy">
|
|
7227
|
+
</iframe>
|
|
7228
|
+
</div>
|
|
7229
|
+
<script type="application/ld+json">
|
|
7230
|
+
${jsonLd}
|
|
7231
|
+
</script>
|
|
7232
|
+
`.trim();
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
6967
7235
|
// src/index.ts
|
|
6968
7236
|
function createLynxSeoEngine(config) {
|
|
6969
7237
|
return new LynxSeoEngine(config);
|
|
@@ -7021,6 +7289,7 @@ export {
|
|
|
7021
7289
|
createLynxSeoEngine,
|
|
7022
7290
|
cleanSeoSlug,
|
|
7023
7291
|
YoastParityEngine,
|
|
7292
|
+
VideoYouTubeAnalyzer,
|
|
7024
7293
|
UrlyticsEngine,
|
|
7025
7294
|
UI_ICONS,
|
|
7026
7295
|
TokenQuotaManager,
|
|
@@ -7062,6 +7331,7 @@ export {
|
|
|
7062
7331
|
IndexNowClient,
|
|
7063
7332
|
I18nDetector,
|
|
7064
7333
|
HyperswitchGateway,
|
|
7334
|
+
GoogleBusinessProfileEngine,
|
|
7065
7335
|
GeoMeshLinkingEngine,
|
|
7066
7336
|
FreePublicToolsEngine,
|
|
7067
7337
|
ExtendedSchemaGraphBuilder,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface YouTubeVideoMetadata {
|
|
2
|
+
videoId: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
uploadDate?: string;
|
|
6
|
+
duration?: string;
|
|
7
|
+
thumbnailUrl?: string;
|
|
8
|
+
embedUrl?: string;
|
|
9
|
+
hasChapters?: boolean;
|
|
10
|
+
chapters?: Array<{
|
|
11
|
+
time: number;
|
|
12
|
+
title: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare class VideoYouTubeAnalyzer {
|
|
16
|
+
/**
|
|
17
|
+
* Extracts YouTube Video ID from any standard URL or iframe string
|
|
18
|
+
*/
|
|
19
|
+
static extractVideoId(urlOrIframe: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Generates Schema.org VideoObject JSON-LD for rich snippets on Google Video Carousel
|
|
22
|
+
*/
|
|
23
|
+
static generateVideoObjectSchema(meta: YouTubeVideoMetadata): Record<string, any>;
|
|
24
|
+
/**
|
|
25
|
+
* Generates responsive, lazy-loaded, SEO-optimized HTML embed with Schema markup
|
|
26
|
+
*/
|
|
27
|
+
static generateSeoVideoEmbedHtml(meta: YouTubeVideoMetadata): string;
|
|
28
|
+
}
|