DIMA/plugin/plugin_chrome/releases/Plugin-dima/modules/Suspicioussitesmanager.js

540 строки
18 KiB
JavaScript
Исходник Ответственный История

Этот файл содержит невидимые символы Юникода

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}