Merge pull request #23 from M82-project/Cybart-patch-3

ajout test sites sur rapport de désinfo
Этот коммит содержится в:
Sebastien Larinier 2025-10-23 16:02:48 +02:00 коммит произвёл GitHub
родитель 82bf96445a a715246f0b
Коммит 98aa04e1e4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 13648 добавлений и 153 удалений

Просмотреть файл

@ -1,8 +1,126 @@
// Plugin DIMA - content.js - Version finale consolidée
// Plugin DIMA - content.js - Version consolidée
// Détection de manipulation cognitive - M82 Project
// Version: 3.0 Refactored with ContentExtractor
// Note: All dependencies are loaded via manifest.json in correct order
// ============================================================================
// PARTIE 1: DÉTECTION DE SITES SUSPECTS (NOUVEAU)
// ============================================================================
/**
* Vérifie si le site actuel est dans la liste des sites suspects
* Cette fonction est fournie par suspiciousSitesManager.js
* et fonctionne automatiquement dès le chargement de la page
*/
function checkCurrentSiteInSuspiciousList() {
const currentUrl = window.location.href;
// Utiliser la fonction fournie par suspiciousSitesManager.js
const result = checkSuspiciousSite(currentUrl);
if (result.isSuspicious) {
console.log('⚠️ DIMA: Site suspect détecté!');
console.log('Source:', result.siteInfo.source);
console.log('Raison:', result.siteInfo.reason);
console.log('Niveau de risque:', result.siteInfo.riskLevel);
// Afficher une alerte visuelle
showSuspiciousSiteAlert(result);
}
}
/**
* Affiche une alerte pour un site suspect
*/
function showSuspiciousSiteAlert(result) {
// Créer un bandeau d'alerte en haut de la page
const alertBanner = document.createElement('div');
alertBanner.id = 'dima-suspicious-site-alert';
alertBanner.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, ${result.riskConfig.color}, ${result.riskConfig.color}dd);
color: white;
padding: 15px 20px;
z-index: 999999;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
animation: slideDown 0.5s ease-out;
`;
alertBanner.innerHTML = `
<div style="display: flex; align-items: center; gap: 15px; flex: 1;">
<span style="font-size: 24px;">${result.riskConfig.icon}</span>
<div>
<div style="font-weight: bold; font-size: 16px; margin-bottom: 5px;">
${result.riskConfig.label} - ${result.siteInfo.source}
</div>
<div style="font-size: 14px; opacity: 0.95;">
${result.siteInfo.reason}
</div>
<a href="${result.siteInfo.reportUrl}" target="_blank"
style="color: white; text-decoration: underline; font-size: 13px; margin-top: 5px; display: inline-block;">
Consulter le rapport source
</a>
</div>
</div>
<button id="dima-close-alert" style="
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
">
Fermer
</button>
`;
// Animation CSS
const style = document.createElement('style');
style.textContent = `
@keyframes slideDown {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
#dima-close-alert:hover {
background: rgba(255,255,255,0.3) !important;
}
`;
document.head.appendChild(style);
// Ajouter au body
document.body.insertBefore(alertBanner, document.body.firstChild);
// Gérer la fermeture
document.getElementById('dima-close-alert').addEventListener('click', () => {
alertBanner.style.animation = 'slideDown 0.3s ease-out reverse';
setTimeout(() => alertBanner.remove(), 300);
});
// Ajuster le padding du body pour ne pas cacher le contenu
document.body.style.paddingTop = `${alertBanner.offsetHeight}px`;
}
// ============================================================================
// PARTIE 2: ANALYSE DIMA du site visité
// ============================================================================
// ===== CLASSE PRINCIPALE DIMA =====
class DIMAAnalyzer {
constructor() {

Просмотреть файл

@ -0,0 +1,769 @@
// DIMA - Base de données de l'infrastructure BAYBRIDGE
// Opération d'influence chinoise ciblant des audiences étrangères via des sociétés de marketing digital
// Sources: Tadaweb & Paul Charon, Focus Report 2025
/**
* OPÉRATION BAYBRIDGE
* ===================
*
* Description: Vaste écosystème d'influence informationnelle chinoise opéré depuis la région
* de Greater Bay Area (Guangdong). L'infrastructure technique combine des campagnes de
* marketing digital avec de la manipulation informationnelle ciblant des dizaines de pays.
*
* Acteurs principaux:
* - Shenzhen Haimai Yunxiang Media Co., Ltd. (深圳市海卖云享传媒有限公司)
* - Shanghai Haixun Technology Co., Ltd (海讯社文化传播有限公司)
*
* Caractéristiques:
* - Création de centaines de sites d'information inauthentiques
* - Diffusion de contenu aligné avec Pékin et Moscou
* - Narratives contradictoires: "positive energy" chinoise + propagande pro-Kremlin
* - Traductions de mauvaise qualité, absence de supervision éditoriale
* - Inefficacité remarquable malgré une infrastructure technique sophistiquée
*
* Date d'identification: 2025
* Pays ciblés: USA, Europe, Asie, Amérique Latine, Afrique
*/
const baybridgeDomains = [
// ===== ENTITÉS COMMERCIALES PRINCIPALES =====
{
domain: "haipress.com",
matchType: "exact",
reason: "Site commercial principal de Shanghai Haixun Technology Co. Ltd., proposant des services de distribution de contenu à l'international",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Marketing-Digital",
"Sites-Commerciaux",
"Haixun"
]
},
{
domain: "hmedium.com",
matchType: "exact",
reason: "Site commercial principal de Haimai, proposant 'increase the value of your brand'. Interface client pour services de marketing",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Marketing-Digital",
"Sites-Commerciaux",
"Haimai"
]
},
{
domain: "hmedium.net",
matchType: "exact",
reason: "Site commercial de Haimai hébergeant les offres commerciales détaillées (packages ciblant audiences étrangères)",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Marketing-Digital",
"Sites-Commerciaux",
"Haimai"
]
},
{
domain: "haixunpress.com",
matchType: "exact",
reason: "Site commercial principal de Haixun. Lien d'infrastructure avec sihaimai.com (IP 47.91.170.222, jan-oct 2024)",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Marketing-Digital",
"Sites-Commerciaux",
"Haixun"
]
},
{
domain: "sihaimai.com",
matchType: "exact",
reason: "Infrastructure de services d'hébergement et de dissémination liée à Haimai. Lien technique avec haixunpress.com",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Hébergement",
"Haimai"
]
},
{
domain: "aisugao.com",
matchType: "exact",
reason: "Plateforme de marketing de marque pilotée par IA de Haixun, offrant traduction automatique d'articles via LLMs natifs",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2024-04-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"LLM",
"IA-Générative",
"Haixun",
"Infrastructure"
]
},
{
domain: "ebuypress.com",
matchType: "exact",
reason: "Site de distribution de contenu (content provider) lié à Haixun. API payante disponible: api.haipress.com/api/media/resources",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Distribution-Contenu",
"Haixun"
]
},
{
domain: "globerelease.com",
matchType: "exact",
reason: "Premier site enregistré par Haimai, affichant contenu générique distribué sur de nombreux autres sites de l'écosystème",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Distribution-Contenu",
"Haimai"
]
},
{
domain: "shiworld.cn",
matchType: "contains",
reason: "Site d'hébergement lié à Haimai. Contient news.shiworld.cn hébergeant packages commerciaux",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Hébergement",
"Haimai"
]
},
{
domain: "mlzgb.cn",
matchType: "exact",
reason: "Site d'hébergement des packages commerciaux de Haimai (fichiers Excel, liens directs). 92% des packages Haimai hébergés ici",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Infrastructure",
"Hébergement",
"Haimai"
]
},
// ===== FOURNISSEURS DE CONTENU (CONTENT PROVIDERS) =====
{
domain: "timesnewswire.com",
matchType: "exact",
reason: "Principal fournisseur de contenu du réseau (13% des articles). Diffuse communiqués de presse + contenu propagande (CGTN, Global Times)",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Distribution-Contenu",
"Propagande",
"Positive-Energy"
]
},
{
domain: "updatenews.info",
matchType: "exact",
reason: "PRINCIPAL fournisseur de contenu (77% des articles sur sites finaux). Diffuse massivement narratives pro-Kremlin + contenu chinois",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "critical",
tags: [
"BAYBRIDGE",
"Russie",
"Chine",
"Distribution-Contenu",
"Propagande-Pro-Kremlin",
"Ukraine",
"LLM-Intoxication"
]
},
{
domain: "meijiedaka.com",
matchType: "exact",
reason: "Fournisseur de contenu identifié dans l'écosystème, alimentant sites finaux de Haimai",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Distribution-Contenu"
]
},
// ===== SITES FINAUX - FRANCE (Package "Propagande Politique") =====
{
domain: "alpsbiz.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Diffuse contenu pro-Kremlin + narratives chinoises contradictoires",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "rmtcityfr.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Traductions de mauvaise qualité, erreurs grammaticales multiples",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "provencedaily.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Amplifie narratives pro-Kremlin, cite TASS, RIA Novosti",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "louispress.org",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Usurpation d'identité (naming convention trompeur). Hébergé cluster FR",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Usurpation-Identité",
"Anti-Ukraine"
]
},
{
domain: "friendlyparis.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France ('Paris Amical'). Fautes d'orthographe, images manquantes",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "eiffelpost.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Republie articles CGTN + contenu pro-Kremlin. Faible qualité éditoriale",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Positive-Energy",
"Anti-Ukraine"
]
},
{
domain: "fr.wdpp.org",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Sous-domaine spécifique audience francophone",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "fr.euleader.org",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Version francophone de euleader.org",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Sites-UE",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "fftribune.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Naming convention mimant média légitime",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Usurpation-Identité",
"Anti-Ukraine"
]
},
{
domain: "economyfr.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Focus thématique économie",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "froneplus.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Contenu synchronisé avec autres sites du réseau",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "frnewsfeed.com",
matchType: "exact",
reason: "Site final du package 'Propagande Politique' France. Publication synchronisée, architecture similaire Jeecg-Boot",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-France",
"Propagande",
"Anti-Ukraine"
]
},
// ===== SITES FINAUX - AUTRES PAYS EUROPÉENS =====
{
domain: "euleader.org",
matchType: "exact",
reason: "Site ciblant audience UE. Diffuse contenu BTS, actualités Shenzhen, narratives pro-Kremlin. Exemple: article BTS 27/05/2025",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-UE",
"Propagande",
"Anti-Ukraine"
]
},
{
domain: "londonclup.com",
matchType: "exact",
reason: "Site package UK. Contenu en vietnamien par erreur (exemple page d'accueil 08/07/2025), images manquantes",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-UK",
"Propagande"
]
},
// ===== SITES FINAUX - RUSSIE =====
{
domain: "findmoscow.com",
matchType: "exact",
reason: "Site ciblant audience russe ('Найти Москву'). Hébergé sur cluster serveur russe avec sites ciblant Russie",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-Russie",
"Propagande"
]
},
{
domain: "ekaterintech.com",
matchType: "exact",
reason: "Site ciblant audience russe. Hébergé IP 18.171.181.70 avec cluster sites RU. Lien avec louispress.org (même IP FR)",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Russie",
"Sites-Russie",
"Infrastructure"
]
},
// ===== SITES FINAUX - AUSTRALIE =====
{
domain: "capitalsydney.com",
matchType: "exact",
reason: "Site ciblant audience australienne ('Sydney News'). Naming convention usurpation identité",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Sites-Australie",
"Usurpation-Identité"
]
},
// ===== EXEMPLES TYPOSQUATTING =====
{
domain: "dertagesspiegel.com",
matchType: "exact",
reason: "Typosquatting de 'Der Tagesspiegel' (journal allemand). Site identifié dans packages commerciaux",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Sites-Allemagne",
"Usurpation-Identité",
"Typosquatting"
]
},
{
domain: "nrchandelsblad.com",
matchType: "exact",
reason: "Typosquatting de 'NRC Handelsblad' (journal néerlandais). Site identifié dans packages commerciaux",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Sites-Pays-Bas",
"Usurpation-Identité",
"Typosquatting"
]
},
{
domain: "kanagawa-ken.com",
matchType: "exact",
reason: "Typosquatting ciblant audience japonaise. Site identifié dans packages commerciaux",
source: "Tadaweb & Paul Charon - Focus BAYBRIDGE 2025",
reportUrl: "https://www.tadaweb.com/hub/68e3e66e4f7350000150899b",
identifiedDate: "2025-03-01",
riskLevel: "high",
tags: [
"BAYBRIDGE",
"Chine",
"Sites-Japon",
"Usurpation-Identité",
"Typosquatting"
]
}
];
// =============================================================================
// FONCTIONS UTILITAIRES
// =============================================================================
// Filtrer par tag
function filterBaybridgeByTag(tag) {
return baybridgeDomains.filter(d => d.tags.includes(tag));
}
// Filtrer par niveau de risque
function filterBaybridgeByRiskLevel(level) {
return baybridgeDomains.filter(d => d.riskLevel === level);
}
// Obtenir tous les tags uniques
function getBaybridgeTags() {
const allTags = new Set();
baybridgeDomains.forEach(d => {
d.tags.forEach(tag => allTags.add(tag));
});
return Array.from(allTags).sort();
}
// Obtenir les statistiques
function getBaybridgeStats() {
return {
total: baybridgeDomains.length,
critical: baybridgeDomains.filter(d => d.riskLevel === "critical").length,
highRisk: baybridgeDomains.filter(d => d.riskLevel === "high").length,
mediumRisk: baybridgeDomains.filter(d => d.riskLevel === "medium").length,
lowRisk: baybridgeDomains.filter(d => d.riskLevel === "low").length,
tags: getBaybridgeTags()
};
}
// Obtenir sites par catégorie
function getBaybridgeByCategory() {
return {
infrastructure: filterBaybridgeByTag("Infrastructure"),
contentProviders: filterBaybridgeByTag("Distribution-Contenu"),
france: filterBaybridgeByTag("Sites-France"),
russia: filterBaybridgeByTag("Sites-Russie"),
uk: filterBaybridgeByTag("Sites-UK"),
propaganda: filterBaybridgeByTag("Propagande"),
typosquatting: filterBaybridgeByTag("Typosquatting")
};
}
// =============================================================================
// EXPORTS ET DISPONIBILITÉ GLOBALE
// =============================================================================
// Export pour Node.js / modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
baybridgeDomains,
filterBaybridgeByTag,
filterBaybridgeByRiskLevel,
getBaybridgeTags,
getBaybridgeStats,
getBaybridgeByCategory
};
}
// Disponibilité globale pour le navigateur
if (typeof window !== 'undefined') {
window.baybridgeDomains = baybridgeDomains;
window.baybridgeUtils = {
filterByTag: filterBaybridgeByTag,
filterByRiskLevel: filterBaybridgeByRiskLevel,
getTags: getBaybridgeTags,
getStats: getBaybridgeStats,
getByCategory: getBaybridgeByCategory
};
}
// Log de chargement
console.log(`Liste BAYBRIDGE chargée: ${baybridgeDomains.length} domaines identifiés`);
if (baybridgeDomains.length > 0) {
console.log("Statistiques BAYBRIDGE:", getBaybridgeStats());
console.log("Catégories:", getBaybridgeByCategory());
}
// =============================================================================
// NOTES IMPORTANTES SUR L'OPÉRATION
// =============================================================================
/**
* CONTEXTE GÉOPOLITIQUE:
*
* Acteurs identifiés:
* - Wu Yanni (吴燕妮): Chercheur SZAS, membre du Comité de propagande municipale de Shenzhen,
* directrice exécutive Haimai Yunxiang Media
* - Zhu Haisong (朱海松): Expert en marketing, chercheur, PDG Haixun, liens avec le
* Département de propagande du Guangdong
*
* Caractéristiques techniques:
* - Infrastructure partagée entre Haimai et Haixun (IP 47.91.170.222)
* - 24% d'overlap dans les offres commerciales internationales (104 packages communs)
* - Utilisation de Jeecg-Boot pour génération automatique de sites web
* - API Haixun: api.haipress.com/api/media/resources
* - Traduction IA via aisugao.com (LLMs natifs)
*
* Pays principalement ciblés:
* - USA (6% des packages, cible #1)
* - Asie du Sud-Est (Corée du Sud, Inde, Vietnam, Thaïlande, Japon, Taiwan)
* - Europe: UK (21 packages), Espagne (20), Italie (18), Portugal (17), France (15), Allemagne (15)
*
* Narratives diffusées:
* 1. Contenu chinois "positive energy" (< 5% du volume):
* - Republication CGTN, Global Times
* - Focus: harmony, win-win cooperation, innovation, développement durable
* - Vocabulaire récurrent: "innovation" (68%), "transformation" (54%), "leadership" (47%)
*
* 2. Contenu pro-Kremlin (volume dominant):
* - Sources: TASS, RIA Novosti, RT, Tsargrad TV, Rambler.ru
* - Focus: guerre Ukraine, anti-OTAN, amplification Florian Philippot
* - Channels Telegram: Maria Zakharova, Alexey Pushkov
* - Similarités avec réseau Portal Kombat / Pravda
*
* Inefficacité opérationnelle:
* - Traductions automatiques de très mauvaise qualité
* - Narratives contradictoires (Chine positive vs. Russie agressive)
* - Aucune traction sur réseaux sociaux (SimilarWeb: pas de données)
* - Système en boucle fermée (sites s'auto-citent)
* - Erreurs techniques: encodage cyrillique raté (oct 2024 - fév 2025)
* - Contenu vietnamien sur sites UK, images manquantes
*
* Risque LLM:
* - Infrastructure potentiellement utilisée pour "intoxiquer" les LLMs
* - Contournement sanctions médias russes (RT, Sputnik) via "laundering machine"
* - Aucune preuve directe d'assimilation par ChatGPT/Copilot/Gemini/DeepSeek (avril 2025)
*
* Date pivot: 09 mars 2024
* Apparition massive contenu pro-Kremlin sur updatenews.info (hors catégorie TimesNewsWire)
* Hypothèse: appropriation infrastructure chinoise par acteurs russes
*
* Connexion Portal Kombat:
* - Similarités narratives et sources avec réseau "Pravda"
* - Pas de lien technique formel établi
* - Exemple: même citation Philippot sur updatenews.info (05/01/25) et
* france.news-pravda.com (06/01/25)
*/

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,169 @@
// DIMA - Template pour nouvelle base de données d'opération
// REMPLACEZ "OPERATION_NAME" par le nom de votre opération (ex: Doppelganger, Portal_Kombat, etc.)
/**
* INSTRUCTIONS D'UTILISATION
* ==========================
*
* 1. Copiez ce fichier et renommez-le (ex: Doppelganger.js)
* 2. Remplacez tous les "OPERATION_NAME" par le nom de l'opération
* 3. Remplissez les domaines dans le tableau
* 4. Chargez ce fichier AVANT suspiciousSites.js dans votre HTML
*
* EXEMPLE:
* <script src="data/Copycop.js"></script>
* <script src="data/Doppelganger.js"></script>
* <script src="data/suspiciousSites.js"></script>
*/
// Nom de la variable globale (à adapter selon votre opération)
// Exemples:
// - copycopDomains (déjà existant)
// - doppelgangerDomains
// - portalKombatDomains
// - yourOperationDomains
const OPERATION_NAMEDomains = [
// ===== EXEMPLE D'ENTRÉE =====
{
domain: "example-fake-news.com",
matchType: "exact", // "exact", "contains", ou "pattern"
reason: "Site identifié dans l'opération [NOM], diffusant de la désinformation ciblée",
source: "Nom de l'organisation source (ex: EU DisinfoLab, DFRLab, etc.)",
reportUrl: "https://lien-vers-le-rapport-complet.com",
identifiedDate: "2025-01-15", // Format: YYYY-MM-DD
riskLevel: "high", // "high", "medium", ou "low"
tags: [
"OPERATION_NAME", // Tag obligatoire : nom de l'opération
"Russie", // Origine géographique si connue
"USA", // Pays ciblé
"Anti-Ukraine", // Thématique
"Élections" // Type de campagne
]
},
// ===== AJOUTEZ VOS DOMAINES ICI =====
/*
// Template à copier pour chaque nouveau domaine:
{
domain: "votre-domaine.com",
matchType: "exact",
reason: "Description précise de la raison",
source: "Organisation source",
reportUrl: "https://...",
identifiedDate: "YYYY-MM-DD",
riskLevel: "high|medium|low",
tags: ["OPERATION_NAME", "tag1", "tag2"]
},
*/
];
// =============================================================================
// FONCTIONS UTILITAIRES (OPTIONNELLES)
// =============================================================================
/**
* Ces fonctions sont optionnelles mais recommandées pour faciliter
* l'utilisation de votre base de données indépendamment du gestionnaire principal
*/
// Filtrer par tag
function filterOPERATION_NAMEByTag(tag) {
return OPERATION_NAMEDomains.filter(d => d.tags.includes(tag));
}
// Filtrer par niveau de risque
function filterOPERATION_NAMEByRiskLevel(level) {
return OPERATION_NAMEDomains.filter(d => d.riskLevel === level);
}
// Obtenir tous les tags uniques
function getOPERATION_NAMETags() {
const allTags = new Set();
OPERATION_NAMEDomains.forEach(d => {
d.tags.forEach(tag => allTags.add(tag));
});
return Array.from(allTags).sort();
}
// Obtenir les statistiques
function getOPERATION_NAMEStats() {
return {
total: OPERATION_NAMEDomains.length,
highRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "high").length,
mediumRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "medium").length,
lowRisk: OPERATION_NAMEDomains.filter(d => d.riskLevel === "low").length,
tags: getOPERATION_NAMETags()
};
}
// =============================================================================
// EXPORTS ET DISPONIBILITÉ GLOBALE
// =============================================================================
// Export pour Node.js / modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
OPERATION_NAMEDomains,
filterOPERATION_NAMEByTag,
filterOPERATION_NAMEByRiskLevel,
getOPERATION_NAMETags,
getOPERATION_NAMEStats
};
}
// Disponibilité globale pour le navigateur
if (typeof window !== 'undefined') {
window.OPERATION_NAMEDomains = OPERATION_NAMEDomains;
window.OPERATION_NAMEUtils = {
filterByTag: filterOPERATION_NAMEByTag,
filterByRiskLevel: filterOPERATION_NAMEByRiskLevel,
getTags: getOPERATION_NAMETags,
getStats: getOPERATION_NAMEStats
};
}
// Log de chargement
console.log(`Liste OPERATION_NAME chargée: ${OPERATION_NAMEDomains.length} domaines identifiés`);
if (OPERATION_NAMEDomains.length > 0) {
console.log("Statistiques OPERATION_NAME:", getOPERATION_NAMEStats());
}
// =============================================================================
// GUIDE DES TAGS RECOMMANDÉS
// =============================================================================
/**
* TAGS OBLIGATOIRES:
* - Le nom de votre opération (ex: "Doppelganger", "Portal_Kombat")
*
* TAGS GÉOGRAPHIQUES (origine):
* - Russie, Chine, Iran, Corée_du_Nord, etc.
*
* TAGS GÉOGRAPHIQUES (cible):
* - USA, France, Canada, UK, Allemagne, Ukraine, etc.
* - Sites-US, Sites-France, Sites-Canada (pour collections de sites locaux)
*
* TAGS THÉMATIQUES:
* - Anti-Ukraine
* - Élections
* - COVID-19
* - Climat
* - Immigration
* - Santé
*
* TAGS TECHNIQUES:
* - LLM (contenu généré par IA)
* - Deepfake
* - Usurpation-Identité
* - Bot-Network
* - Infrastructure
*
* TAGS DE MÉTHODE:
* - Désinformation-Ciblée
* - Amplification-Artificielle
* - Multi-Langues
* - Coordination-Cross-Platform
*/

Просмотреть файл

@ -1,23 +1,39 @@
{
"manifest_version": 3,
"name": "Analyseur DIMA - M82 Project",
"version": "1.1",
"description": "Plugin d'analyse de manipulation cognitive selon la matrice DIMA par M82 Project",
"permissions": ["activeTab", "storage"],
"name": "DIMA - Digital Influence Manipulation Analyzer",
"version": "2.0.0",
"description": "Plugin d'analyse de manipulation cognitive selon la matrice DIMA par M82 Project, détecte et analyse les sites suspects identifiés dans des rapports de désinformation",
"permissions": [
"activeTab",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": [
"data/databases/Copycop.js",
"data/databases/PortalKombat.js",
"data/databases/RRN.js",
"data/databases/Baybridge.js",
"data/databases/Storm1516.js",
"data/techniques.js",
"data/keywords.js",
"modules/contentExtractor.js",
"modules/suspiciousSitesManager.js",
"modules/techniqueAnalyzer.js",
"modules/uiManager.js",
"content.js"
]
],
"run_at": "document_end"
}
],
"action": {
"action": {
"default_title": "Analyse DIMA - M82 Project"
},
"icons": {

Просмотреть файл

@ -0,0 +1,540 @@
// DIMA - Gestionnaire Central de Sites Suspects
// Version 2.2 - Support COMPLET des comptes sociaux (format Storm1516 natif)
// Ce fichier charge et agrège toutes les bases de données de domaines suspects
/**
* Gestionnaire centralisé des sites suspects
* Compatible avec TOUS les formats de données existants
*/
class SuspiciousSitesManager {
constructor() {
this.sources = new Map();
this.allSites = [];
this.stats = {
totalSites: 0,
totalDomains: 0,
totalSocialAccounts: 0,
byRiskLevel: { high: 0, medium: 0, low: 0 },
bySources: {},
byTags: {},
bySocialPlatform: {}
};
this.init();
}
/**
* Initialise le gestionnaire en chargeant toutes les sources disponibles
*/
init() {
console.log('🛡️ DIMA: Initialisation du gestionnaire de sites suspects...');
// Détecter et charger les sources disponibles
this.detectAndLoadSources();
// Agréger tous les sites
this.aggregateAllSites();
// Calculer les statistiques
this.calculateStats();
console.log(`✅ DIMA: ${this.allSites.length} entrées chargées depuis ${this.sources.size} source(s)`);
console.log(` - ${this.stats.totalDomains} domaines`);
console.log(` - ${this.stats.totalSocialAccounts} comptes de réseaux sociaux`);
this.logStats();
}
/**
* Détecte et charge automatiquement toutes les sources disponibles
*/
detectAndLoadSources() {
// Source 1: CopyCop (Recorded Future)
if (typeof copycopDomains !== 'undefined' && Array.isArray(copycopDomains)) {
this.registerSource('CopyCop', copycopDomains, {
name: 'Opération CopyCop',
description: 'Réseau russe de sites fictifs et de désinformation',
organization: 'Recorded Future - Insikt Group',
reportUrl: 'https://www.recordedfuture.com/research/cta-ru-2025-0917',
reportDate: '2025-09-17'
});
console.log(` ✓ Source CopyCop chargée: ${copycopDomains.length} domaines`);
}
// Source 2: RRN (VIGINUM)
if (typeof rrnDomains !== 'undefined' && Array.isArray(rrnDomains)) {
this.registerSource('RRN', rrnDomains, {
name: 'Réseau RRN',
description: 'Réseau de faux médias et infrastructure de désinformation pro-russe',
organization: 'VIGINUM',
reportUrl: 'https://www.sgdsn.gouv.fr/files/files/20230619_NP_VIGINUM_RAPPORT-CAMPAGNE-RRN_VF_0.pdf',
reportDate: '2023-06-19'
});
console.log(` ✓ Source RRN chargée: ${rrnDomains.length} domaines`);
}
// Source 3: Portal Kombat (VIGINUM)
if (typeof portalKombatDomains !== 'undefined' && Array.isArray(portalKombatDomains)) {
this.registerSource('PortalKombat', portalKombatDomains, {
name: 'Opération Portal Kombat',
description: 'Réseau d\'influence',
organization: 'Viginum',
reportUrl: 'https://www.sgdsn.gouv.fr/files/files/20240212_NP_SGDSN_VIGINUM_RAPPORT-RESEAU-PORTAL-KOMBAT_VF.pdf',
reportDate: '2024-02-01'
});
console.log(` ✓ Source Portal Kombat chargée: ${portalKombatDomains.length} domaines`);
}
// Source 4: Baybridge (IRSEM)
if (typeof baybridgeDomains !== 'undefined' && Array.isArray(baybridgeDomains)) {
this.registerSource('Baybridge', baybridgeDomains, {
name: 'Opération Baybridge',
description: 'Vaste écosystème d\'influence informationnelle chinoise ',
organization: 'IRSEM & TadaWeb',
reportUrl: 'https://www.irsem.fr/focus',
reportDate: '2025-10-17'
});
console.log(` ✓ Source Baybridge chargée: ${baybridgeDomains.length} domaines`);
}
// Source 5: Storm 1516 - Domaines (VIGINUM)
if (typeof storm1516Domains !== 'undefined' && Array.isArray(storm1516Domains)) {
this.registerSource('Storm1516_Domains', storm1516Domains, {
name: 'Opération Storm_1516 (Domaines)',
description: 'Mode opératoire informationnel (MOI) russe actif depuis août 2023',
organization: 'VIGINUM',
reportUrl: 'https://www.defense.gouv.fr/sites/default/files/desinformation/Rapport%20Storm%201516%20-%20SGDSN.pdf',
reportDate: '2025-05-02'
});
console.log(` ✓ Source Storm 1516 (domaines) chargée: ${storm1516Domains.length} domaines`);
}
// Source 6: Storm 1516 - Comptes sociaux (VIGINUM) - FORMAT NATIF
if (typeof storm1516SocialAccounts !== 'undefined' && Array.isArray(storm1516SocialAccounts)) {
this.registerSource('Storm1516_Social', storm1516SocialAccounts, {
name: 'Opération Storm_1516 (Comptes sociaux)',
description: 'Comptes de réseaux sociaux relayant le MOI russe Storm 1516',
organization: 'VIGINUM',
reportUrl: 'https://www.defense.gouv.fr/sites/default/files/desinformation/Rapport%20Storm%201516%20-%20SGDSN.pdf',
reportDate: '2025-05-02'
});
console.log(` ✓ Source Storm 1516 (comptes sociaux) chargée: ${storm1516SocialAccounts.length} comptes`);
}
// Avertissement si aucune source n'est chargée
if (this.sources.size === 0) {
console.warn('⚠️ DIMA: Aucune base de données de sites suspects n\'a été chargée');
console.warn(' Vérifiez que les fichiers de bases de données sont correctement chargés avant ce gestionnaire');
}
}
/**
* Enregistre une nouvelle source de données
*/
registerSource(sourceName, domains, metadata) {
this.sources.set(sourceName, {
domains: domains,
metadata: metadata,
count: domains.length
});
}
/**
* Agrège tous les sites de toutes les sources
*/
aggregateAllSites() {
this.allSites = [];
for (const [sourceName, sourceData] of this.sources) {
this.allSites.push(...sourceData.domains);
}
}
/**
* Calcule les statistiques globales
*/
calculateStats() {
this.stats.totalSites = this.allSites.length;
this.stats.totalDomains = 0;
this.stats.totalSocialAccounts = 0;
// Reset stats
this.stats.byRiskLevel = { high: 0, medium: 0, low: 0 };
this.stats.bySources = {};
this.stats.byTags = {};
this.stats.bySocialPlatform = {};
// Compter par niveau de risque et tags
this.allSites.forEach(site => {
// Distinguer domaines et comptes sociaux
// Format Storm1516: {platform: "X/Twitter", handle: "@..."}
// Format standard: {domain: "...", accountType: "twitter"}
if (site.platform || site.accountType) {
this.stats.totalSocialAccounts++;
const platform = site.platform || site.accountType;
this.stats.bySocialPlatform[platform] = (this.stats.bySocialPlatform[platform] || 0) + 1;
} else {
this.stats.totalDomains++;
}
// Par niveau de risque
if (site.riskLevel) {
this.stats.byRiskLevel[site.riskLevel] = (this.stats.byRiskLevel[site.riskLevel] || 0) + 1;
}
// Par source
if (site.source) {
this.stats.bySources[site.source] = (this.stats.bySources[site.source] || 0) + 1;
}
// Par tags
if (site.tags && Array.isArray(site.tags)) {
site.tags.forEach(tag => {
this.stats.byTags[tag] = (this.stats.byTags[tag] || 0) + 1;
});
}
});
}
/**
* Affiche les statistiques dans la console
*/
logStats() {
console.log('📊 Statistiques:');
console.log(` Total: ${this.stats.totalSites} entrées`);
console.log(` - Domaines: ${this.stats.totalDomains}`);
console.log(` - Comptes sociaux: ${this.stats.totalSocialAccounts}`);
if (this.stats.totalSocialAccounts > 0) {
console.log(' Répartition par plateforme:');
for (const [platform, count] of Object.entries(this.stats.bySocialPlatform)) {
console.log(`${platform}: ${count}`);
}
}
console.log(` Risque élevé: ${this.stats.byRiskLevel.high || 0}`);
console.log(` Risque moyen: ${this.stats.byRiskLevel.medium || 0}`);
console.log(` Risque faible: ${this.stats.byRiskLevel.low || 0}`);
console.log(` Sources: ${Object.keys(this.stats.bySources).length}`);
}
/**
* Vérifie si une URL correspond à un site suspect OU un compte social suspect
* @param {string} url - L'URL à vérifier
* @returns {Object} Résultat de la vérification
*/
checkSite(url) {
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
const pathname = urlObj.pathname.toLowerCase();
for (const site of this.allSites) {
let isMatch = false;
let matchType = 'domain';
// NOUVEAU: Support du format Storm1516 natif
// Format: {platform: "X/Twitter", handle: "@JimFergusonUK", url: "..."}
if (site.platform && site.handle) {
isMatch = this.checkSocialAccountStorm1516Format(url, site, hostname, pathname);
matchType = 'social_account';
}
// Support du format standard avec accountType
else if (site.accountType) {
const extractedHandle = this.extractSocialHandle(url, site.accountType);
if (extractedHandle) {
const dbHandle = site.domain.toLowerCase().replace(/^@/, '');
isMatch = extractedHandle === dbHandle;
matchType = 'social_account';
}
}
// Vérification classique pour les domaines
else {
switch (site.matchType) {
case "exact":
isMatch = hostname === site.domain.toLowerCase() ||
hostname === `www.${site.domain.toLowerCase()}`;
break;
case "contains":
isMatch = hostname.includes(site.domain.toLowerCase());
break;
case "pattern":
try {
const regex = new RegExp(site.domain, "i");
isMatch = regex.test(hostname);
} catch (e) {
console.error(`DIMA: Pattern regex invalide pour ${site.domain}:`, e);
}
break;
}
}
if (isMatch) {
console.log(`🎯 DIMA: Match trouvé!`, {
type: matchType,
site: site.handle || site.domain,
url: url
});
return {
isSuspicious: true,
siteInfo: site,
riskConfig: this.getRiskConfig(site.riskLevel),
matchedHostname: hostname,
matchType: matchType,
matchedIdentifier: matchType === 'social_account' ? (site.handle || site.domain) : hostname
};
}
}
return { isSuspicious: false };
} catch (error) {
console.error("DIMA: Erreur lors de la vérification du site suspect:", error);
return { isSuspicious: false, error: error.message };
}
}
/**
* NOUVEAU: Vérifie un compte social au format Storm1516
* Format: {platform: "X/Twitter", handle: "@JimFergusonUK"}
*/
checkSocialAccountStorm1516Format(url, site, hostname, pathname) {
// Mapping des plateformes Storm1516 vers domaines
const platformDomains = {
'X/Twitter': ['twitter.com', 'x.com'],
'Telegram': ['t.me', 'telegram.me'],
'YouTube': ['youtube.com', 'youtu.be'],
'Facebook': ['facebook.com', 'fb.com', 'm.facebook.com'],
'Instagram': ['instagram.com'],
'TikTok': ['tiktok.com'],
'VK': ['vk.com'],
'Rumble': ['rumble.com']
};
const platform = site.platform;
const handle = site.handle.toLowerCase().replace(/^@/, ''); // Enlever @ et lowercase
// Vérifier si on est sur la bonne plateforme
const domains = platformDomains[platform];
if (!domains) {
console.warn(`DIMA: Plateforme inconnue: ${platform}`);
return false;
}
const isCorrectDomain = domains.some(domain => hostname.includes(domain));
if (!isCorrectDomain) {
return false;
}
// Extraire le handle de l'URL actuelle
let extractedHandle = null;
if (platform === 'X/Twitter') {
// twitter.com/JimFergusonUK ou x.com/JimFergusonUK
const match = pathname.match(/^\/([a-zA-Z0-9_]+)(?:\/|$|\?)/);
if (match) extractedHandle = match[1].toLowerCase();
} else if (platform === 'Telegram') {
// t.me/username ou t.me/s/channelname
const match = pathname.match(/^\/(?:s\/)?([a-zA-Z0-9_]+)(?:\/|$|\?)/);
if (match) extractedHandle = match[1].toLowerCase();
} else if (platform === 'YouTube') {
// youtube.com/@username ou youtube.com/c/username
const match = pathname.match(/^\/([@c]\/)?([a-zA-Z0-9_-]+)(?:\/|$|\?)/);
if (match) extractedHandle = match[2].toLowerCase();
} else if (platform === 'Facebook') {
// facebook.com/username
const match = pathname.match(/^\/([a-zA-Z0-9._-]+)(?:\/|$|\?)/);
if (match) extractedHandle = match[1].toLowerCase();
} else if (platform === 'Rumble') {
// rumble.com/c/username
const match = pathname.match(/^\/c\/([a-zA-Z0-9_-]+)(?:\/|$|\?)/);
if (match) extractedHandle = match[1].toLowerCase();
}
if (extractedHandle) {
console.log(`🔍 DIMA: Comparaison - URL: "${extractedHandle}" vs DB: "${handle}"`);
return extractedHandle === handle;
}
return false;
}
/**
* Extrait le handle/username d'une URL de réseau social
* @param {string} url - L'URL complète
* @param {string} accountType - Type de compte (twitter, facebook, youtube, etc.)
* @returns {string|null} Le handle extrait ou null
*/
extractSocialHandle(url, accountType) {
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
const pathname = urlObj.pathname;
// Patterns pour différents réseaux sociaux
const patterns = {
twitter: {
domains: ['twitter.com', 'x.com'],
regex: /^\/([a-zA-Z0-9_]+)(?:\/|$|\?)/
},
facebook: {
domains: ['facebook.com', 'fb.com'],
regex: /^\/([a-zA-Z0-9._]+)(?:\/|$|\?)/
},
instagram: {
domains: ['instagram.com'],
regex: /^\/([a-zA-Z0-9._]+)(?:\/|$|\?)/
},
youtube: {
domains: ['youtube.com'],
regex: /^\/([@c]\/)?([a-zA-Z0-9_-]+)(?:\/|$|\?)/
},
telegram: {
domains: ['t.me', 'telegram.me'],
regex: /^\/([a-zA-Z0-9_]+)(?:\/|$|\?)/
},
tiktok: {
domains: ['tiktok.com'],
regex: /^\/@?([a-zA-Z0-9._]+)(?:\/|$|\?)/
},
vk: {
domains: ['vk.com'],
regex: /^\/([a-zA-Z0-9._]+)(?:\/|$|\?)/
}
};
const pattern = patterns[accountType.toLowerCase()];
if (!pattern) {
console.warn(`DIMA: Type de compte non supporté: ${accountType}`);
return null;
}
// Vérifier si on est sur le bon domaine
const isCorrectDomain = pattern.domains.some(domain => hostname.includes(domain));
if (!isCorrectDomain) return null;
// Extraire le handle
const match = pathname.match(pattern.regex);
if (match) {
const handle = accountType.toLowerCase() === 'youtube' ? (match[2] || match[1]) : match[1];
console.log(`DIMA: Handle extrait de ${accountType}: ${handle}`);
return handle;
}
return null;
} catch (error) {
console.error("DIMA: Erreur lors de l'extraction du handle social:", error);
return null;
}
}
/**
* Retourne la configuration visuelle pour un niveau de risque
*/
getRiskConfig(riskLevel) {
const RISK_LEVELS = {
critical: {
color: "#8b0000",
icon: "🚨",
label: "Risque Critique",
message: "Ce site/compte a été identifié comme un acteur majeur de désinformation."
},
high: {
color: "#c0392b",
icon: "⚠️",
label: "Risque Élevé",
message: "Ce site/compte a été identifié comme diffusant de la désinformation de manière systématique."
},
medium: {
color: "#e67e22",
icon: "⚡",
label: "Vigilance Requise",
message: "Ce site/compte a été signalé pour des pratiques douteuses."
},
low: {
color: "#f39c12",
icon: "",
label: "À Surveiller",
message: "Ce site/compte présente des caractéristiques suspectes."
}
};
return RISK_LEVELS[riskLevel] || RISK_LEVELS.low;
}
/**
* Retourne les statistiques
*/
getStats() {
return this.stats;
}
/**
* Retourne les informations sur toutes les sources chargées
*/
getSourcesInfo() {
const sourcesInfo = [];
for (const [sourceName, sourceData] of this.sources) {
sourcesInfo.push({
name: sourceName,
count: sourceData.count,
...sourceData.metadata
});
}
return sourcesInfo;
}
/**
* Recherche des sites par tag
*/
searchByTag(tag) {
return this.allSites.filter(site =>
site.tags && site.tags.includes(tag)
);
}
/**
* Recherche des sites par source
*/
searchBySource(sourceName) {
return this.allSites.filter(site =>
site.source === sourceName
);
}
/**
* Recherche des comptes sociaux par plateforme
*/
searchBySocialPlatform(platform) {
return this.allSites.filter(site =>
site.platform === platform ||
(site.accountType && site.accountType.toLowerCase() === platform.toLowerCase())
);
}
}
// Initialisation automatique du gestionnaire
let suspiciousSitesManager;
// Initialiser après le chargement de toutes les bases de données
if (typeof window !== 'undefined') {
// Dans le navigateur, initialiser après un court délai pour laisser les autres fichiers se charger
setTimeout(() => {
suspiciousSitesManager = new SuspiciousSitesManager();
// Rendre disponible globalement
window.suspiciousSitesManager = suspiciousSitesManager;
// Pour compatibilité avec l'ancien code, exposer aussi checkSuspiciousSite
window.checkSuspiciousSite = (url) => suspiciousSitesManager.checkSite(url);
// Exposer aussi les statistiques et infos
window.getSuspiciousSitesStats = () => suspiciousSitesManager.getStats();
window.getSuspiciousSitesSourcesInfo = () => suspiciousSitesManager.getSourcesInfo();
}, 100);
}
// Export pour Node.js si nécessaire
if (typeof module !== 'undefined' && module.exports) {
module.exports = SuspiciousSitesManager;
}

Просмотреть файл

@ -9,6 +9,7 @@ class UIManager {
this.buttonCreated = false;
this.analysisResults = null;
this.pageType = 'general';
this.suspiciousSiteCheck = null;
}
log(message, data = null) {
@ -30,12 +31,20 @@ class UIManager {
console.error('DIMA: Aucun résultat d\'analyse disponible pour créer le bouton');
return;
}
// Vérifier si le site est suspect
this.suspiciousSiteCheck = window.checkSuspiciousSite ?
window.checkSuspiciousSite(window.location.href) :
{ isSuspicious: false };
try {
// Supprimer bouton existant
document.getElementById('dima-btn')?.remove();
document.getElementById('dima-suspicious-alert')?.remove();
if (this.buttonCreated) return;
// Créer le bouton principal
const button = document.createElement('div');
button.id = 'dima-btn';
@ -80,6 +89,12 @@ class UIManager {
});
document.body?.appendChild(button);
// Créer l'alerte de site suspect si nécessaire
if (this.suspiciousSiteCheck.isSuspicious) {
this.createSuspiciousSiteAlert();
}
this.buttonCreated = true;
this.log('Bouton créé avec succès');
@ -88,6 +103,227 @@ class UIManager {
}
}
createSuspiciousSiteAlert() {
const { siteInfo, riskConfig } = this.suspiciousSiteCheck;
const alert = document.createElement('div');
alert.id = 'dima-suspicious-alert';
alert.innerHTML = `
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 24px;">${riskConfig.icon}</span>
<div style="flex: 1;">
<div style="font-weight: bold; margin-bottom: 4px; font-size: 14px;">
${riskConfig.label}
</div>
<div style="font-size: 12px; line-height: 1.4; margin-bottom: 8px;">
Vigilance : ce site appartient à un dispositif de manipulation de l'information identifié.
</div>
<button id="dima-suspicious-details" style="
background: white;
color: ${riskConfig.color};
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 11px;
font-weight: 600;
transition: all 0.2s;
">
En savoir plus
</button>
</div>
<button id="dima-suspicious-close" style="
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 20px;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity 0.2s;
">
×
</button>
</div>
`;
alert.style.cssText = `
position: fixed !important;
top: 70px !important;
right: 20px !important;
z-index: 999998 !important;
background: linear-gradient(135deg, ${riskConfig.color}, ${this.adjustColor(riskConfig.color, -15)}) !important;
color: white !important;
padding: 16px !important;
border-radius: 12px !important;
max-width: 350px !important;
font-family: 'Segoe UI', Arial, sans-serif !important;
box-shadow: 0 6px 20px rgba(0,0,0,0.3) !important;
border: 2px solid rgba(255,255,255,0.2) !important;
animation: slideInRight 0.4s ease-out !important;
backdrop-filter: blur(10px) !important;
`;
document.body?.appendChild(alert);
// Événements
document.getElementById('dima-suspicious-details')?.addEventListener('click', () => {
this.showSuspiciousSiteDetails();
});
document.getElementById('dima-suspicious-close')?.addEventListener('click', () => {
alert.remove();
});
// Hover effects
const detailsBtn = document.getElementById('dima-suspicious-details');
if (detailsBtn) {
detailsBtn.addEventListener('mouseenter', () => {
detailsBtn.style.transform = 'translateY(-1px)';
detailsBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
});
detailsBtn.addEventListener('mouseleave', () => {
detailsBtn.style.transform = 'translateY(0)';
detailsBtn.style.boxShadow = 'none';
});
}
const closeBtn = document.getElementById('dima-suspicious-close');
if (closeBtn) {
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.opacity = '1';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.opacity = '0.7';
});
}
}
showSuspiciousSiteDetails() {
const { siteInfo, riskConfig } = this.suspiciousSiteCheck;
// Créer modal avec détails
const detailsModal = document.createElement('div');
detailsModal.id = 'dima-suspicious-details-modal';
detailsModal.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0,0,0,0.75) !important;
backdrop-filter: blur(5px) !important;
z-index: 10000000 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-family: 'Segoe UI', Arial, sans-serif !important;
animation: fadeIn 0.3s ease-out !important;
`;
const logoUrl = chrome.runtime.getURL('M82-logo-16.png');
detailsModal.innerHTML = `
<div style="background: white; padding: 30px; border-radius: 20px; max-width: 600px; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 25px 50px rgba(0,0,0,0.3); animation: slideIn 0.3s ease-out;">
<!-- En-tête -->
<div style="text-align: center; margin-bottom: 25px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0;">
<div style="display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 10px;">
<img src="${logoUrl}"
style="width: 24px; height: 24px;"
alt="M82 Project"
onerror="this.style.display='none'">
<h2 style="color: #2c3e50; margin: 0; font-size: 1.8em;">Site Suspect Identifié</h2>
</div>
<div style="display: inline-block; background: ${riskConfig.color}; color: white; padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: 600; margin-top: 10px;">
${riskConfig.icon} ${riskConfig.label}
</div>
</div>
<!-- Contenu -->
<div style="margin-bottom: 25px;">
<div style="background: linear-gradient(135deg, #fff3cd, #ffeaa7); padding: 20px; border-radius: 12px; border-left: 4px solid ${riskConfig.color}; margin-bottom: 20px;">
<h3 style="margin: 0 0 10px 0; color: #856404; font-size: 1.1em;"> Avertissement</h3>
<p style="margin: 0; color: #856404; line-height: 1.6;">
${riskConfig.message}
</p>
</div>
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 15px;">
<h4 style="margin: 0 0 12px 0; color: #2c3e50; font-size: 1em;">📋 Détails de l'identification</h4>
<div style="display: grid; gap: 12px;">
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Raison :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${siteInfo.reason}</div>
</div>
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Source du rapport :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${siteInfo.source}</div>
</div>
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Date d'identification :</strong>
<div style="color: #2c3e50; margin-top: 4px;">${new Date(siteInfo.identifiedDate).toLocaleDateString('fr-FR')}</div>
</div>
${siteInfo.tags && siteInfo.tags.length > 0 ? `
<div>
<strong style="color: #7f8c8d; font-size: 0.9em;">Catégories :</strong>
<div style="display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px;">
${siteInfo.tags.map(tag => `
<span style="background: #e9ecef; color: #495057; padding: 4px 10px; border-radius: 12px; font-size: 0.8em;">
${tag}
</span>
`).join('')}
</div>
</div>
` : ''}
</div>
</div>
<div style="background: #e8f4f8; padding: 16px; border-radius: 10px; border-left: 4px solid #17a2b8;">
<h4 style="margin: 0 0 8px 0; color: #0c5460; font-size: 0.95em;">💡 Recommandations</h4>
<ul style="margin: 0; padding-left: 20px; color: #0c5460; line-height: 1.6;">
<li>Vérifiez les informations auprès de sources fiables</li>
<li>Consultez plusieurs sources avant de partager</li>
<li>Soyez attentif aux techniques de manipulation détectées</li>
<li>Signalez le contenu suspect si nécessaire</li>
</ul>
</div>
</div>
<!-- Actions -->
<div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
<button onclick="window.open('${siteInfo.reportUrl}', '_blank')"
style="background: #3498db; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 500; transition: all 0.3s; box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);">
📄 Consulter le rapport complet
</button>
<button onclick="document.getElementById('dima-suspicious-details-modal').remove()"
style="background: #95a5a6; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 500; transition: all 0.3s;">
Fermer
</button>
</div>
<div style="text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #e9ecef; color: #7f8c8d; font-size: 0.85em;">
Base de données maintenue par
<a href="https://m82-project.org/" target="_blank"
style="color: #3498db; text-decoration: none; font-weight: 500;">M82 Project</a>
</div>
</div>
`;
detailsModal.addEventListener('click', (e) => {
if (e.target === detailsModal) detailsModal.remove();
});
document.body.appendChild(detailsModal);
}
adjustColor(color, amount) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * amount);
@ -101,116 +337,205 @@ class UIManager {
generateTooltip() {
const techniques = this.analysisResults.detectedTechniques.slice(0, 3);
return `DIMA Score: ${this.analysisResults.globalScore} (${this.analysisResults.riskLevel})
let tooltip = `DIMA Score: ${this.analysisResults.globalScore} (${this.analysisResults.riskLevel})
${this.analysisResults.detectedTechniques.length} techniques détectées
${techniques.map(t => `${t.nom}`).join('\n')}
Contenu: ${this.analysisResults.contentLength} caractères`;
}
getScoreIcon(score) {
if (score >= 75) return '🚨'; // Critique
if (score >= 50) return '⚠️'; // Élevé
if (score >= 30) return '⚡'; // Modéré
if (score >= 15) return '👀'; // Faible
return '✅'; // Très faible
}
generateExecutiveSummary() {
const score = this.analysisResults.globalScore;
const techniqueCount = this.analysisResults.detectedTechniques.length;
const topTechniques = this.analysisResults.detectedTechniques.slice(0, 3);
let summary = "";
// Évaluation générale selon le score
if (score >= 75) {
summary = `🚨 <strong>Manipulation intensive détectée</strong> : Ce contenu présente un niveau critique de techniques manipulatoires (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else if (score >= 50) {
summary = `⚠️ <strong>Manipulation significative</strong> : Ce contenu utilise plusieurs techniques suspectes (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else if (score >= 30) {
summary = `⚡ <strong>Éléments manipulatoires présents</strong> : Quelques techniques détectées nécessitent votre attention (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
} else {
summary = `👀 <strong>Faible niveau de manipulation</strong> : Peu d'éléments manipulatoires détectés (${techniqueCount} technique${techniqueCount > 1 ? 's' : ''}). `;
}
// Analyse des phases dominantes
if (this.analysisResults.phaseScores && Object.keys(this.analysisResults.phaseScores).length > 0) {
const sortedPhases = Object.entries(this.analysisResults.phaseScores)
.sort(([,a], [,b]) => b - a)
.slice(0, 2);
${techniques.map(t => `${t.nom}`).join('\n')}`;
if (sortedPhases.length > 0) {
const dominantPhase = sortedPhases[0][0];
summary += `La manipulation se concentre principalement sur la phase "<strong>${dominantPhase}</strong>" (${this.getPhaseExplanation(dominantPhase)}). `;
if (this.suspiciousSiteCheck.isSuspicious) {
tooltip += `\n\n⚠️ SITE SUSPECT IDENTIFIÉ`;
}
}
// Technique principale
if (topTechniques.length > 0) {
const mainTechnique = topTechniques[0];
summary += `La technique dominante est <strong>${mainTechnique.nom}</strong> avec ${mainTechnique.confidence}% de confiance. `;
// Conseil spécifique selon la technique
summary += this.getTechniqueAdvice(mainTechnique.index);
}
return summary;
tooltip += `\nContenu: ${this.analysisResults.contentLength} caractères`;
return tooltip;
}
getPhaseEmoji(phase) {
const emojis = {
'Detect': '👁️',
'Informer': '📢',
'Mémoriser': '🧠',
'Act': '⚡'
};
return emojis[phase] || '📍';
generatePhaseAnalysis() {
if (!this.analysisResults || !this.analysisResults.detectedTechniques || this.analysisResults.detectedTechniques.length === 0) {
return '';
}
// Analyser les techniques par phase
const phaseStats = {
'Detect': { count: 0, totalScore: 0, techniques: [], icon: '👁️', color: '#3498db' },
'Informer': { count: 0, totalScore: 0, techniques: [], icon: '📢', color: '#e67e22' },
'Mémoriser': { count: 0, totalScore: 0, techniques: [], icon: '🧠', color: '#9b59b6' },
'Agir': { count: 0, totalScore: 0, techniques: [], icon: '⚡', color: '#e74c3c' }
};
this.analysisResults.detectedTechniques.forEach(technique => {
const phase = technique.phase || 'Detect';
if (phaseStats[phase]) {
phaseStats[phase].count++;
phaseStats[phase].totalScore += technique.weightedScore || technique.score || 0;
phaseStats[phase].techniques.push(technique);
}
});
// Calculer les pourcentages
const totalTechniques = this.analysisResults.detectedTechniques.length;
const totalScore = Object.values(phaseStats).reduce((sum, phase) => sum + phase.totalScore, 0);
// Trouver la phase dominante
let dominantPhase = null;
let maxCount = 0;
Object.entries(phaseStats).forEach(([phase, stats]) => {
if (stats.count > maxCount) {
maxCount = stats.count;
dominantPhase = phase;
}
});
// Générer l'explication contextuelle
const explanation = this.generatePhaseExplanation(dominantPhase, phaseStats, totalTechniques);
// Générer le HTML
return `
<div style="background: #f8f9fa; padding: 25px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
<h3 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 1.2em; display: flex; align-items: center; gap: 10px;">
📊 Analyse par Phase DIMA
<span style="font-size: 0.7em; color: #7f8c8d; font-weight: normal; font-style: italic;">
(Detect, Informer, Mémoriser, Agir)
</span>
</h3>
<!-- Explication contextuelle -->
<div style="background: linear-gradient(135deg, #e8f4f8, #d4e8f0); padding: 16px; border-radius: 10px; margin-bottom: 20px; border-left: 4px solid ${phaseStats[dominantPhase]?.color || '#3498db'};">
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 24px;">${phaseStats[dominantPhase]?.icon || '💡'}</span>
<div>
<h4 style="margin: 0 0 8px 0; color: #0c5460; font-size: 1em;">
Analyse : Phase dominante "${dominantPhase}"
</h4>
<p style="margin: 0; color: #0c5460; font-size: 0.9em; line-height: 1.5;">
${explanation}
</p>
</div>
</div>
</div>
<!-- Répartition visuelle des phases -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 20px;">
${Object.entries(phaseStats).map(([phase, stats]) => {
const percentage = totalTechniques > 0 ? Math.round((stats.count / totalTechniques) * 100) : 0;
const scorePercentage = totalScore > 0 ? Math.round((stats.totalScore / totalScore) * 100) : 0;
const isActive = stats.count > 0;
return `
<div style="
background: ${isActive ? 'white' : '#f8f9fa'};
padding: 15px;
border-radius: 10px;
text-align: center;
border: ${isActive ? `2px solid ${stats.color}` : '1px solid #e9ecef'};
opacity: ${isActive ? '1' : '0.5'};
transition: all 0.3s;
">
<div style="font-size: 24px; margin-bottom: 8px;">${stats.icon}</div>
<div style="font-weight: bold; color: ${stats.color}; font-size: 0.85em; margin-bottom: 4px;">
${phase}
</div>
<div style="font-size: 1.8em; font-weight: bold; color: ${isActive ? stats.color : '#bdc3c7'}; margin-bottom: 4px;">
${stats.count}
</div>
<div style="font-size: 0.75em; color: #7f8c8d; margin-bottom: 8px;">
${percentage}% techniques
</div>
${isActive ? `
<div style="background: ${stats.color}20; padding: 4px 8px; border-radius: 6px; font-size: 0.7em; color: ${stats.color}; font-weight: 600;">
${scorePercentage}% du score
</div>
` : ''}
</div>
`;
}).join('')}
</div>
<!-- Graphique à barres -->
<div style="background: white; padding: 15px; border-radius: 10px; border: 1px solid #e9ecef;">
<h4 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 0.95em;">Distribution du score par phase</h4>
${Object.entries(phaseStats).map(([phase, stats]) => {
const percentage = totalScore > 0 ? (stats.totalScore / totalScore) * 100 : 0;
const displayScore = stats.totalScore.toFixed(1);
return `
<div style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<span style="font-size: 0.85em; font-weight: 600; color: #2c3e50;">
${stats.icon} ${phase}
</span>
<span style="font-size: 0.8em; color: #7f8c8d;">
${displayScore} pts (${Math.round(percentage)}%)
</span>
</div>
<div style="background: #e9ecef; height: 8px; border-radius: 4px; overflow: hidden;">
<div style="
background: linear-gradient(90deg, ${stats.color}, ${this.adjustColor(stats.color, -15)});
width: ${percentage}%;
height: 100%;
transition: width 0.6s ease-out;
border-radius: 4px;
"></div>
</div>
</div>
`;
}).join('')}
</div>
<!-- Comprendre les phases -->
<div style="margin-top: 20px; padding: 15px; background: white; border-radius: 10px; border: 1px solid #e9ecef;">
<details style="cursor: pointer;">
<summary style="font-weight: 600; color: #2c3e50; font-size: 0.9em; padding: 5px; outline: none;">
Comprendre les phases DIMA
</summary>
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e9ecef; font-size: 0.85em; line-height: 1.6; color: #555;">
<div style="margin-bottom: 10px;">
<strong style="color: #3498db;">👁 Detect (Détecter)</strong> :
Techniques visant à capter l'attention et identifier les cibles sensibles aux messages.
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #e67e22;">📢 Informer</strong> :
Techniques de transmission et cadrage de l'information pour influencer la perception.
</div>
<div style="margin-bottom: 10px;">
<strong style="color: #9b59b6;">🧠 Mémoriser</strong> :
Techniques d'ancrage mémoriel et de renforcement des messages dans la durée.
</div>
<div>
<strong style="color: #e74c3c;"> Agir</strong> :
Techniques d'incitation à l'action et de mobilisation comportementale.
</div>
</div>
</details>
</div>
</div>
`;
}
getPhaseColor(phase) {
const colors = {
'Detect': '#e3f2fd', // Bleu clair
'Informer': '#f3e5f5', // Violet clair
'Mémoriser': '#e8f5e8', // Vert clair
'Act': '#fff3e0' // Orange clair
};
return colors[phase] || '#f5f5f5';
generatePhaseExplanation(dominantPhase, phaseStats, totalTechniques) {
const explanations = {
'Detect': `Le contenu utilise principalement des techniques de <strong>détection et captation d'attention</strong> (${phaseStats['Detect'].count}/${totalTechniques} techniques). Cela suggère une stratégie axée sur l'identification des publics réceptifs et l'accroche initiale. Le contenu cherche à attirer et cibler des audiences spécifiques.`,
'Informer': `Le contenu se concentre sur des techniques de <strong>transmission et cadrage de l'information</strong> (${phaseStats['Informer'].count}/${totalTechniques} techniques). L'objectif est de contrôler la perception de l'information via le choix des faits présentés, leur contextualisation, et les biais introduits dans le message.`,
'Mémoriser': `Le contenu privilégie des techniques de <strong>mémorisation et ancrage</strong> (${phaseStats['Mémoriser'].count}/${totalTechniques} techniques). Ces méthodes visent à inscrire durablement les messages dans la mémoire du public, souvent par répétition, simplification ou associations émotionnelles fortes.`,
'Agir': `Le contenu met l'accent sur des techniques d'<strong>incitation à l'action</strong> (${phaseStats['Agir'].count}/${totalTechniques} techniques). L'objectif est de mobiliser le public vers des comportements spécifiques : partage, engagement, manifestation, ou modification d'opinions et de votes.`
};
// Si plusieurs phases sont également représentées
const topPhases = Object.entries(phaseStats)
.filter(([_, stats]) => stats.count > 0)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 2);
if (topPhases.length > 1 && topPhases[0][1].count === topPhases[1][1].count) {
return `Le contenu présente une <strong>stratégie équilibrée</strong> entre les phases "${topPhases[0][0]}" et "${topPhases[1][0]}" (${topPhases[0][1].count} techniques chacune). Cette combinaison indique une approche sophistiquée visant à la fois à attirer l'attention et à générer un impact durable.`;
}
return explanations[dominantPhase] || 'Analyse de la répartition des techniques de manipulation cognitive détectées selon le modèle DIMA.';
}
getPhaseDescription(phase) {
const descriptions = {
'Detect': 'Capter l\'attention',
'Informer': 'Influencer ou orienter la compréhension',
'Mémoriser': 'Ancrer l\'information',
'Act': 'Provoquer l\'action'
};
return descriptions[phase] || phase;
}
getPhaseExplanation(phase) {
const explanations = {
'Detect': 'techniques pour attirer et capter votre attention',
'Informer': 'méthodes pour orienter votre interprétation des faits',
'Mémoriser': 'stratégies pour ancrer certaines idées dans votre mémoire',
'Act': 'pressions pour vous pousser à agir rapidement'
};
return explanations[phase] || 'manipulation cognitive';
}
getTechniqueAdvice(techniqueIndex) {
const advices = {
'TE0500': 'Méfiez-vous des titres sensationnalistes et vérifiez les sources.',
'TE0132': 'Prenez du recul face aux messages alarmistes excessifs.',
'TE0501': 'Résistez à la pression de l\'urgence et prenez le temps de réfléchir.',
'TE0422': 'Vérifiez les qualifications réelles des "experts" cités.',
'TE0251': 'Questionnez les affirmations sur ce que "tout le monde" pense.',
'TE0221': 'Attention aux généralisations excessives sur des groupes.',
'TE0212': 'Ne tirez pas de conclusions générales à partir d\'anecdotes.',
'TE0321': 'Cherchez des sources contradictoires pour éviter le biais de confirmation.'
};
return advices[techniqueIndex] || 'Restez critique et vérifiez les informations.';
}
showModal() {
try {
this.log('Affichage du modal');
@ -235,9 +560,33 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
font-family: 'Segoe UI', Arial, sans-serif !important;
animation: fadeIn 0.3s ease-out !important;
`;
// Récupérer l'URL du logo
const logoUrl = chrome.runtime.getURL('M82-logo-16.png');
// Construire le contenu avec alerte site suspect si nécessaire
let suspiciousAlert = '';
if (this.suspiciousSiteCheck.isSuspicious) {
const { riskConfig, siteInfo } = this.suspiciousSiteCheck;
suspiciousAlert = `
<div style="background: linear-gradient(135deg, ${riskConfig.color}, ${this.adjustColor(riskConfig.color, -15)}); color: white; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 2px solid rgba(255,255,255,0.2);">
<div style="display: flex; align-items: start; gap: 12px;">
<span style="font-size: 28px;">${riskConfig.icon}</span>
<div style="flex: 1;">
<h3 style="margin: 0 0 8px 0; font-size: 1.2em;">${riskConfig.label}</h3>
<p style="margin: 0 0 12px 0; font-size: 0.95em; line-height: 1.5;">
${riskConfig.message}
</p>
<button onclick="document.getElementById('dima-suspicious-details-modal')?.remove(); document.querySelector('#dima-modal .suspicious-details-btn').click()"
class="suspicious-details-btn"
style="background: white; color: ${riskConfig.color}; border: none; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s;">
Voir les détails du rapport
</button>
</div>
</div>
</div>
`;
}
modal.innerHTML = `
<div style="background: white; padding: 30px; border-radius: 20px; max-width: 900px; max-height: 90vh; overflow-y: auto; margin: 20px; box-shadow: 0 25px 50px rgba(0,0,0,0.3); animation: slideIn 0.3s ease-out;">
@ -257,6 +606,8 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</p>
</div>
${suspiciousAlert}
<!-- Métriques principales -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 15px; margin-bottom: 25px;">
<div style="background: linear-gradient(135deg, ${this.analysisResults.riskColor}, ${this.adjustColor(this.analysisResults.riskColor, -15)}); color: white; padding: 20px; border-radius: 12px; text-align: center; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
@ -277,17 +628,7 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</div>
</div>
<!-- Résumé exécutif -->
${this.analysisResults.globalScore > 15 ? `
<div style="background: ${this.analysisResults.globalScore >= 50 ? '#ffebee' : this.analysisResults.globalScore >= 30 ? '#fff3e0' : '#e8f5e8'}; padding: 20px; border-radius: 12px; margin-bottom: 25px; border-left: 4px solid ${this.analysisResults.riskColor};">
<h4 style="margin: 0 0 10px 0; color: ${this.analysisResults.riskColor}; font-size: 1.1em;">
${this.getScoreIcon(this.analysisResults.globalScore)} Résumé de l'analyse
</h4>
<p style="margin: 0; color: #444; line-height: 1.5; font-size: 0.95em;">
${this.generateExecutiveSummary()}
</p>
</div>
` : ''}
${this.generatePhaseAnalysis()}
<!-- Informations sur la page -->
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
@ -300,38 +641,6 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
</div>
</div>
<!-- Répartition par phase DIMA -->
${this.analysisResults.phaseScores && Object.keys(this.analysisResults.phaseScores).length > 0 ? `
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; margin-bottom: 25px; border: 1px solid #e9ecef;">
<h4 style="margin: 0 0 15px 0; color: #2c3e50; font-size: 1.1em;">📊 Répartition par phase DIMA</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 15px;">
${Object.entries(this.analysisResults.phaseScores).map(([phase, score]) => {
const maxScore = Math.max(...Object.values(this.analysisResults.phaseScores));
const percentage = maxScore > 0 ? (score / maxScore) * 100 : 0;
return `
<div style="text-align: center;">
<div style="font-size: 0.85em; color: #666; margin-bottom: 8px; font-weight: 500;">
${this.getPhaseEmoji(phase)} ${phase}
</div>
<div style="background: ${this.getPhaseColor(phase)}; height: 12px; border-radius: 6px; margin-bottom: 8px; overflow: hidden;">
<div style="background: ${this.analysisResults.riskColor}; height: 100%; width: ${Math.min(percentage, 100)}%; border-radius: 6px; transition: width 0.8s ease;"></div>
</div>
<div style="font-size: 0.8em; font-weight: bold; color: #333;">
${score.toFixed(1)} pts
</div>
<div style="font-size: 0.7em; color: #888;">
${this.getPhaseDescription(phase)}
</div>
</div>
`;
}).join('')}
</div>
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e9ecef; font-size: 0.8em; color: #666; text-align: center;">
💡 La matrice DIMA analyse comment l'information traverse les 4 phases cognitives
</div>
</div>
` : ''}
<!-- Message si aucune technique -->
${this.analysisResults.detectedTechniques.length === 0 ? `
<div style="background: linear-gradient(135deg, #d4edda, #c3e6cb); color: #155724; padding: 25px; border-radius: 12px; text-align: center; border: 1px solid #c3e6cb;">
@ -370,7 +679,7 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
${technique.matchedKeywords?.length > 0 ? `
<div style="margin-top: 10px;">
<div style="font-size: 0.85em; color: #666; margin-bottom: 6px; font-weight: 500;">
🔍 Mots-clés détectés:
🔎 Mots-clés détectés:
</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
${technique.matchedKeywords.slice(0, 4).map(keyword =>
@ -414,6 +723,10 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
#dima-modal button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
@ -425,6 +738,11 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
// Ajouter l'événement pour le bouton des détails du site suspect
modal.querySelector('.suspicious-details-btn')?.addEventListener('click', () => {
this.showSuspiciousSiteDetails();
});
document.body.appendChild(modal);
this.log('Modal affiché');
@ -434,5 +752,6 @@ Contenu: ${this.analysisResults.contentLength} caractères`;
}
}
}
// Make UIManager available globally for Chrome extension
window.UIManager = UIManager;
window.UIManager = UIManager;