Ajout Storm1516 et détection des handles réseaux sociaux

Этот коммит содержится в:
BartM82 2025-10-20 19:05:48 +00:00
родитель 07f11cc250
Коммит a715246f0b
3 изменённых файлов: 6383 добавлений и 28 удалений

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

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

@ -20,6 +20,7 @@
"data/databases/PortalKombat.js", "data/databases/PortalKombat.js",
"data/databases/RRN.js", "data/databases/RRN.js",
"data/databases/Baybridge.js", "data/databases/Baybridge.js",
"data/databases/Storm1516.js",
"data/techniques.js", "data/techniques.js",
"data/keywords.js", "data/keywords.js",
"modules/contentExtractor.js", "modules/contentExtractor.js",

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

@ -1,10 +1,10 @@
// DIMA - Gestionnaire Central de Sites Suspects // 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 // Ce fichier charge et agrège toutes les bases de données de domaines suspects
/** /**
* Gestionnaire centralisé des sites suspects * Gestionnaire centralisé des sites suspects
* Charge automatiquement toutes les bases de données disponibles * Compatible avec TOUS les formats de données existants
* et fournit une API unifiée pour vérifier les sites
*/ */
class SuspiciousSitesManager { class SuspiciousSitesManager {
constructor() { constructor() {
@ -12,9 +12,12 @@ class SuspiciousSitesManager {
this.allSites = []; this.allSites = [];
this.stats = { this.stats = {
totalSites: 0, totalSites: 0,
totalDomains: 0,
totalSocialAccounts: 0,
byRiskLevel: { high: 0, medium: 0, low: 0 }, byRiskLevel: { high: 0, medium: 0, low: 0 },
bySources: {}, bySources: {},
byTags: {} byTags: {},
bySocialPlatform: {}
}; };
this.init(); this.init();
@ -35,7 +38,9 @@ class SuspiciousSitesManager {
// Calculer les statistiques // Calculer les statistiques
this.calculateStats(); this.calculateStats();
console.log(`✅ DIMA: ${this.allSites.length} sites suspects chargés depuis ${this.sources.size} source(s)`); 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(); this.logStats();
} }
@ -90,6 +95,31 @@ class SuspiciousSitesManager {
}); });
console.log(` ✓ Source Baybridge chargée: ${baybridgeDomains.length} domaines`); 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 // Avertissement si aucune source n'est chargée
if (this.sources.size === 0) { if (this.sources.size === 0) {
@ -125,14 +155,28 @@ class SuspiciousSitesManager {
*/ */
calculateStats() { calculateStats() {
this.stats.totalSites = this.allSites.length; this.stats.totalSites = this.allSites.length;
this.stats.totalDomains = 0;
this.stats.totalSocialAccounts = 0;
// Reset stats // Reset stats
this.stats.byRiskLevel = { high: 0, medium: 0, low: 0 }; this.stats.byRiskLevel = { high: 0, medium: 0, low: 0 };
this.stats.bySources = {}; this.stats.bySources = {};
this.stats.byTags = {}; this.stats.byTags = {};
this.stats.bySocialPlatform = {};
// Compter par niveau de risque et tags // Compter par niveau de risque et tags
this.allSites.forEach(site => { 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 // Par niveau de risque
if (site.riskLevel) { if (site.riskLevel) {
this.stats.byRiskLevel[site.riskLevel] = (this.stats.byRiskLevel[site.riskLevel] || 0) + 1; this.stats.byRiskLevel[site.riskLevel] = (this.stats.byRiskLevel[site.riskLevel] || 0) + 1;
@ -157,7 +201,15 @@ class SuspiciousSitesManager {
*/ */
logStats() { logStats() {
console.log('📊 Statistiques:'); console.log('📊 Statistiques:');
console.log(` Total: ${this.stats.totalSites} sites`); 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 élevé: ${this.stats.byRiskLevel.high || 0}`);
console.log(` Risque moyen: ${this.stats.byRiskLevel.medium || 0}`); console.log(` Risque moyen: ${this.stats.byRiskLevel.medium || 0}`);
console.log(` Risque faible: ${this.stats.byRiskLevel.low || 0}`); console.log(` Risque faible: ${this.stats.byRiskLevel.low || 0}`);
@ -165,7 +217,7 @@ class SuspiciousSitesManager {
} }
/** /**
* Vérifie si une URL correspond à un site suspect * Vérifie si une URL correspond à un site suspect OU un compte social suspect
* @param {string} url - L'URL à vérifier * @param {string} url - L'URL à vérifier
* @returns {Object} Résultat de la vérification * @returns {Object} Résultat de la vérification
*/ */
@ -173,36 +225,64 @@ class SuspiciousSitesManager {
try { try {
const urlObj = new URL(url); const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase(); const hostname = urlObj.hostname.toLowerCase();
const pathname = urlObj.pathname.toLowerCase();
for (const site of this.allSites) { for (const site of this.allSites) {
let isMatch = false; let isMatch = false;
let matchType = 'domain';
switch (site.matchType) { // NOUVEAU: Support du format Storm1516 natif
case "exact": // Format: {platform: "X/Twitter", handle: "@JimFergusonUK", url: "..."}
isMatch = hostname === site.domain.toLowerCase() || if (site.platform && site.handle) {
hostname === `www.${site.domain.toLowerCase()}`; isMatch = this.checkSocialAccountStorm1516Format(url, site, hostname, pathname);
break; matchType = 'social_account';
}
case "contains": // Support du format standard avec accountType
isMatch = hostname.includes(site.domain.toLowerCase()); else if (site.accountType) {
break; const extractedHandle = this.extractSocialHandle(url, site.accountType);
if (extractedHandle) {
case "pattern": const dbHandle = site.domain.toLowerCase().replace(/^@/, '');
try { isMatch = extractedHandle === dbHandle;
const regex = new RegExp(site.domain, "i"); matchType = 'social_account';
isMatch = regex.test(hostname); }
} catch (e) { }
console.error(`DIMA: Pattern regex invalide pour ${site.domain}:`, e); // Vérification classique pour les domaines
} else {
break; 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) { if (isMatch) {
console.log(`🎯 DIMA: Match trouvé!`, {
type: matchType,
site: site.handle || site.domain,
url: url
});
return { return {
isSuspicious: true, isSuspicious: true,
siteInfo: site, siteInfo: site,
riskConfig: this.getRiskConfig(site.riskLevel), riskConfig: this.getRiskConfig(site.riskLevel),
matchedHostname: hostname matchedHostname: hostname,
matchType: matchType,
matchedIdentifier: matchType === 'social_account' ? (site.handle || site.domain) : hostname
}; };
} }
} }
@ -214,28 +294,168 @@ class SuspiciousSitesManager {
} }
} }
/**
* 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 * Retourne la configuration visuelle pour un niveau de risque
*/ */
getRiskConfig(riskLevel) { getRiskConfig(riskLevel) {
const RISK_LEVELS = { 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: { high: {
color: "#c0392b", color: "#c0392b",
icon: "⚠️", icon: "⚠️",
label: "Risque Élevé", label: "Risque Élevé",
message: "Ce site a été identifié comme diffusant de la désinformation de manière systématique." message: "Ce site/compte a été identifié comme diffusant de la désinformation de manière systématique."
}, },
medium: { medium: {
color: "#e67e22", color: "#e67e22",
icon: "⚡", icon: "⚡",
label: "Vigilance Requise", label: "Vigilance Requise",
message: "Ce site a été signalé pour des pratiques douteuses." message: "Ce site/compte a été signalé pour des pratiques douteuses."
}, },
low: { low: {
color: "#f39c12", color: "#f39c12",
icon: "", icon: "",
label: "À Surveiller", label: "À Surveiller",
message: "Ce site présente des caractéristiques suspectes." message: "Ce site/compte présente des caractéristiques suspectes."
} }
}; };
@ -281,6 +501,16 @@ class SuspiciousSitesManager {
site.source === sourceName 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 // Initialisation automatique du gestionnaire