Sebastien Larinier f3a10ae0c5
Moves plugin files to "releases" directory
Relocates the plugin files from the "fichiers à télécharger" directory to a "releases" directory.

This change ensures a cleaner separation between development files and release artifacts.
Updates the CI workflow to reflect the new location for zipping and committing the plugin archive.
2025-07-23 09:51:58 +02:00

474 строки
13 KiB
JavaScript

// Technique Analyzer Module
// Responsible for analyzing manipulation techniques in text
class TechniqueAnalyzer {
constructor(
settings,
enhancedKeywords,
contextPatterns,
techniques,
pageType = "general"
) {
this.settings = settings || {
enhancedKeywords: true,
minKeywordLength: 3,
debugMode: false,
};
this.enhancedKeywords = enhancedKeywords;
this.contextPatterns = contextPatterns;
this.techniques = techniques;
this.pageType = pageType;
}
log(message, data = null) {
if (this.settings.debugMode) {
console.log(`TechniqueAnalyzer: ${message}`, data || "");
}
}
performAnalysis(title, content) {
const fullText = (title + " " + content).toLowerCase();
const detected = [];
let totalScore = 0;
const phaseScores = {};
this.log("Analyse du texte...", fullText.substring(0, 200));
// Analyser SEULEMENT les techniques (TE), pas les tactiques (TA)
const techniques = this.techniques.filter(
(item) => item.type === "technique"
);
for (const technique of techniques) {
const analysis = this.analyzeTechnique(technique, fullText);
if (analysis.score > 0) {
detected.push(analysis);
totalScore += analysis.weightedScore;
// Calcul par phase
if (!phaseScores[technique.phase]) {
phaseScores[technique.phase] = 0;
}
phaseScores[technique.phase] += analysis.weightedScore;
this.log(`Technique détectée: ${technique.index} (${analysis.score})`);
}
}
// Score global avec normalisation améliorée
const globalScore = Math.min(Math.round(totalScore * 3), 100);
return {
globalScore,
detectedTechniques: detected.sort(
(a, b) => b.weightedScore - a.weightedScore
),
phaseScores,
riskLevel: this.calculateRiskLevel(globalScore),
riskColor: this.getColor(globalScore),
url: window.location.href,
title: title.substring(0, 200),
contentLength: content.length,
analyzedText: fullText.length,
timestamp: new Date().toISOString(),
};
}
analyzeTechnique(technique, fullText) {
// Utiliser le système amélioré si disponible et activé
if (
this.settings.enhancedKeywords &&
this.enhancedKeywords[technique.index]
) {
return this.analyzeEnhancedTechnique(technique, fullText);
}
// Fallback vers l'ancienne méthode
return this.analyzeBasicTechnique(technique, fullText);
}
analyzeEnhancedTechnique(technique, fullText) {
const enhancedData = this.enhancedKeywords[technique.index];
const results = {
matches: [],
score: 0,
contextBoosts: [],
};
// 1. Analyse des mots-clés de base
if (enhancedData.core) {
const coreMatches = this.findKeywordMatches(
fullText,
enhancedData.core,
1.0
);
results.matches.push(...coreMatches);
}
// 2. Analyse des variantes
if (enhancedData.variants) {
for (const [category, variants] of Object.entries(
enhancedData.variants
)) {
if (Array.isArray(variants)) {
const weight = this.getVariantWeight(category);
const variantMatches = this.findKeywordMatches(
fullText,
variants,
weight
);
results.matches.push(
...variantMatches.map((m) => ({ ...m, category }))
);
} else if (typeof variants === "object") {
// Variantes avec sous-catégories (ex: intensity.strong)
for (const [subcat, subvariants] of Object.entries(variants)) {
const weight = this.getIntensityWeight(subcat);
const subMatches = this.findKeywordMatches(
fullText,
subvariants,
weight
);
results.matches.push(
...subMatches.map((m) => ({
...m,
category: `${category}.${subcat}`,
}))
);
}
}
}
}
// 3. Analyse des patterns regex
if (enhancedData.patterns) {
for (const pattern of enhancedData.patterns) {
const patternMatches = this.findPatternMatches(fullText, pattern);
results.matches.push(...patternMatches);
}
}
// 4. Analyse contextuelle
const contextBoosts = this.analyzeContext(fullText, technique.index);
results.contextBoosts = contextBoosts;
// 5. Calcul du score
results.score = results.matches.reduce(
(sum, match) => sum + match.weight,
0
);
// 6. Application des boosts contextuels
let finalScore = results.score;
for (const boost of contextBoosts) {
finalScore *= boost.boost;
}
// 7. Pondération contextuelle et dynamique
let contextualWeight = this.calculateContextualWeight(
technique,
this.pageType
);
let dynamicWeight = this.calculateDynamicWeight(technique, finalScore);
const totalWeight =
(technique.weight || 1.0) * contextualWeight * dynamicWeight;
const weightedScore = finalScore * totalWeight;
const confidence = Math.min(
Math.round(results.score * 15 + results.matches.length * 10),
100
);
return {
index: technique.index,
nom: technique.nom,
phase: technique.phase,
description: technique.description || "",
score: Math.round(finalScore),
weightedScore,
finalWeight: totalWeight,
contextualWeight,
dynamicWeight,
confidence,
matchedKeywords: this.formatEnhancedMatches(results.matches),
enhancedAnalysis: {
coreMatches: results.matches.filter((m) => m.type === "core").length,
variantMatches: results.matches.filter((m) => m.type === "variant")
.length,
patternMatches: results.matches.filter((m) => m.type === "pattern")
.length,
contextBoosts: results.contextBoosts,
},
};
}
analyzeBasicTechnique(technique, fullText) {
let score = 0;
const matchedKeywords = [];
for (const keyword of technique.mots_cles) {
if (keyword.length < this.settings.minKeywordLength) continue;
const keywordLower = keyword.toLowerCase();
const matches = this.findKeywordMatches(fullText, [keywordLower], 1.0);
if (matches.length > 0) {
score += matches.length;
matchedKeywords.push({
keyword,
count: matches.length,
type: "basic",
});
}
}
// Pondération contextuelle et dynamique
let contextualWeight = this.calculateContextualWeight(
technique,
this.pageType
);
let dynamicWeight = this.calculateDynamicWeight(technique, score);
const finalWeight =
(technique.weight || 1.0) * contextualWeight * dynamicWeight;
const weightedScore = score * finalWeight;
const confidence = Math.min(
Math.round(score * 15 + weightedScore * 5),
100
);
return {
index: technique.index,
nom: technique.nom,
phase: technique.phase,
description: technique.description || "",
score,
weightedScore,
finalWeight,
contextualWeight,
dynamicWeight,
confidence,
matchedKeywords,
};
}
findKeywordMatches(text, keywords, weight = 1.0) {
const matches = [];
for (const keyword of keywords) {
const keywordLower = keyword.toLowerCase();
let regex;
if (keywordLower.includes(" ")) {
// Expression avec espaces
regex = new RegExp(this.escapeRegex(keywordLower), "gi");
} else {
// Mot simple avec frontières
regex = new RegExp(
"\\b" + this.escapeRegex(keywordLower) + "\\b",
"gi"
);
}
let match;
while ((match = regex.exec(text)) !== null) {
matches.push({
type: "core",
keyword: keyword,
position: match.index,
weight: weight,
});
}
}
return matches;
}
findPatternMatches(text, pattern) {
const matches = [];
let match;
// Réinitialiser le regex pour éviter les problèmes de state
pattern.lastIndex = 0;
while ((match = pattern.exec(text)) !== null) {
matches.push({
type: "pattern",
keyword: match[0],
position: match.index,
weight: 1.5, // Les patterns ont un poids plus élevé
});
// Éviter les boucles infinies
if (!pattern.global) break;
}
return matches;
}
analyzeContext(text, techniqueId) {
const boosts = [];
for (const [contextType, contextData] of Object.entries(
this.contextPatterns
)) {
if (contextData.techniques.includes(techniqueId)) {
for (const pattern of contextData.patterns) {
if (pattern.test(text)) {
boosts.push({
type: contextType,
boost: contextData.boost,
pattern: pattern.source,
});
}
}
}
}
return boosts;
}
getVariantWeight(category) {
const weights = {
formal: 0.9,
informal: 1.1,
clickbait_formulas: 1.6,
emotional_hooks: 1.4,
curiosity_gaps: 1.5,
urgency: 1.3,
scarcity: 1.4,
temporal: 1.2,
};
return weights[category] || 1.0;
}
getIntensityWeight(intensity) {
const weights = {
weak: 0.7,
strong: 1.5,
};
return weights[intensity] || 1.0;
}
formatEnhancedMatches(matches) {
const grouped = {};
for (const match of matches) {
const key = match.keyword;
if (!grouped[key]) {
grouped[key] = {
keyword: key,
count: 0,
type: match.type,
category: match.category,
totalWeight: 0,
};
}
grouped[key].count++;
grouped[key].totalWeight += match.weight;
}
return Object.values(grouped);
}
escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
calculateRiskLevel(score) {
if (score < 15) return "Faible";
if (score < 30) return "Modéré";
if (score < 50) return "Élevé";
if (score < 75) return "Très Élevé";
return "Critique";
}
calculateContextualWeight(technique, pageType) {
let contextualWeight = 1.0;
switch (pageType) {
case "news":
if (technique.index === "TE0500") contextualWeight = 1.4;
if (technique.index === "TE0132") contextualWeight = 1.3;
if (technique.index === "TE0221") contextualWeight = 1.5;
if (technique.index === "TE0212") contextualWeight = 1.3;
if (technique.index === "TE0261") contextualWeight = 0.8;
break;
case "social":
if (technique.index === "TE0132") contextualWeight = 0.9;
if (technique.index === "TE0131") contextualWeight = 0.8;
if (technique.index === "TE0501") contextualWeight = 1.3;
if (technique.index === "TE0221") contextualWeight = 1.6;
if (technique.index === "TE0251") contextualWeight = 1.2;
break;
case "commerce":
if (technique.index === "TE0501") contextualWeight = 0.9;
if (technique.index === "TE0141") contextualWeight = 0.8;
if (technique.index === "TE0143") contextualWeight = 0.7;
if (technique.index === "TE0422") contextualWeight = 1.2;
if (technique.index === "TE0411") contextualWeight = 1.1;
break;
case "blog":
if (technique.index === "TE0212") contextualWeight = 0.8;
if (technique.index === "TE0314") contextualWeight = 0.9;
if (technique.index === "TE0261") contextualWeight = 0.7;
if (technique.index === "TE0321") contextualWeight = 1.1;
break;
}
this.log(
`Poids contextuel pour ${technique.index} sur ${pageType}: ${contextualWeight}`
);
return contextualWeight;
}
calculateDynamicWeight(technique, occurrences) {
let dynamicWeight = 1.0;
// Plus une technique apparaît, plus elle devient suspecte
if (occurrences >= 10) {
dynamicWeight = 1.4;
} else if (occurrences >= 7) {
dynamicWeight = 1.3;
} else if (occurrences >= 5) {
dynamicWeight = 1.2;
} else if (occurrences >= 3) {
dynamicWeight = 1.1;
}
// Cas spéciaux : certaines techniques sont plus graves même avec peu d'occurrences
const criticalTechniques = ["TE0221", "TE0500", "TE0132", "TE0501"];
if (criticalTechniques.includes(technique.index) && occurrences >= 2) {
dynamicWeight *= 1.1;
}
// Réduire le poids si technique très fréquente mais bénigne
const benignTechniques = ["TE0143", "TE0232", "TE0333"];
if (benignTechniques.includes(technique.index) && occurrences >= 5) {
dynamicWeight *= 0.9;
}
this.log(
`Poids dynamique pour ${technique.index} (${occurrences} occ.): ${dynamicWeight}`
);
return dynamicWeight;
}
getColor(score) {
if (score < 15) return "#27ae60"; // Vert
if (score < 30) return "#f39c12"; // Orange clair
if (score < 50) return "#e67e22"; // Orange
if (score < 75) return "#d35400"; // Rouge-orange
return "#c0392b"; // Rouge foncé
}
}
// Make TechniqueAnalyzer available globally for Chrome extension
window.TechniqueAnalyzer = TechniqueAnalyzer;