| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex, notranslate, none"> |
| <meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet, noimageindex, none"> |
| <meta name="bingbot" content="noindex, nofollow, noarchive, nosnippet, none"> |
| <meta name="duckduckbot" content="noindex, nofollow, none"> |
| <meta name="yandexbot" content="noindex, nofollow, none"> |
| <meta name="baiduspider" content="noindex, nofollow, none"> |
| <meta name="slurp" content="noindex, nofollow, noarchive, none"> |
| <meta name="referrer" content="no-referrer"> |
| <meta http-equiv="x-dns-prefetch-control" content="off"> |
| <meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate"> |
| <meta http-equiv="pragma" content="no-cache"> |
| <meta http-equiv="expires" content="0"> |
| <link rel="canonical" href="#"> |
| <title>CHS Backend Java — SentinelAI</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="chs-backend.css"> |
| <script> |
| tailwind.config = { |
| theme: { |
| extend: { |
| fontFamily: { |
| sans: ['Inter', 'sans-serif'], |
| mono: ['JetBrains Mono', 'monospace'], |
| } |
| } |
| } |
| } |
| </script> |
| </head> |
| <body class="bg-slate-950 text-slate-200 font-sans antialiased min-h-screen overflow-x-hidden"> |
|
|
| |
| <div class="fixed inset-0 z-0 pointer-events-none"> |
| <div class="grid-bg"></div> |
| <div class="absolute top-0 left-1/4 w-[600px] h-[600px] bg-indigo-600/5 rounded-full blur-3xl animate-pulse-slow"></div> |
| <div class="absolute bottom-0 right-1/4 w-[600px] h-[600px] bg-blue-600/5 rounded-full blur-3xl animate-pulse-slow-delayed"></div> |
| </div> |
|
|
| |
| <nav class="relative z-20 border-b border-slate-800/60 backdrop-blur-xl bg-slate-950/90 sticky top-0"> |
| <div class="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8"> |
| <div class="flex items-center justify-between h-14"> |
| <div class="flex items-center gap-2.5"> |
| <div class="w-8 h-8 bg-gradient-to-br from-indigo-400 to-violet-600 rounded-lg flex items-center justify-center shadow-lg shadow-indigo-500/20"> |
| <i data-lucide="plug" class="w-4 h-4 text-white"></i> |
| </div> |
| <div> |
| <span class="text-base font-bold text-white tracking-tight">Sentinel<span class="text-indigo-400">AI</span></span> |
| <span class="hidden sm:inline text-xs text-slate-500 ml-2 font-mono">CHS BACKEND JAVA</span> |
| </div> |
| </div> |
| <div class="hidden md:flex items-center gap-1"> |
| <a href="command-center.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="layout-dashboard" class="w-3.5 h-3.5 inline mr-1"></i>Dashboard</a> |
| <a href="index.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="search" class="w-3.5 h-3.5 inline mr-1"></i>GDorks</a> |
| <a href="threats.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="alert-triangle" class="w-3.5 h-3.5 inline mr-1"></i>Threats</a> |
| <a href="network.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="network" class="w-3.5 h-3.5 inline mr-1"></i>Network</a> |
| <a href="seo-mesh-normal.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="network" class="w-3.5 h-3.5 inline mr-1"></i>Mesh Normal</a> |
| <a href="seo-mesh-inverse.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="brain-circuit" class="w-3.5 h-3.5 inline mr-1"></i>Mesh Inversé</a> |
| <a href="cognitive-inverse-mesh.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="brain-circuit" class="w-3.5 h-3.5 inline mr-1"></i>Cognitive Inversé</a> |
| <a href="cognitive-seo-geo-mesh.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="brain" class="w-3.5 h-3.5 inline mr-1"></i>Cognitive SEO/GEO</a> |
| <a href="seo-inverse.html" class="nav-link px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="eye-off" class="w-3.5 h-3.5 inline mr-1"></i>SEO Inverse</a> |
| <a href="chs-backend.html" class="nav-link active px-3 py-1.5 rounded-lg text-xs font-medium transition-all"><i data-lucide="plug" class="w-3.5 h-3.5 inline mr-1"></i>CHS Backend</a> |
| </div> |
| <div class="flex items-center gap-3"> |
| <div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-indigo-500/10 border border-indigo-500/20"> |
| <div class="w-2 h-2 bg-indigo-400 rounded-full animate-pulse"></div> |
| <span class="text-xs font-semibold text-indigo-400 font-mono">JAVA BACKEND</span> |
| </div> |
| <button onclick="toggleMobileMenu()" class="md:hidden p-2 text-slate-400 hover:text-white"><i data-lucide="menu" class="w-5 h-5"></i></button> |
| </div> |
| </div> |
| </div> |
| <div id="mobileMenu" class="hidden md:hidden border-t border-slate-800/60 bg-slate-950/95 backdrop-blur-xl"> |
| <div class="px-4 py-3 space-y-1"> |
| <a href="command-center.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Dashboard</a> |
| <a href="index.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">GDorks</a> |
| <a href="threats.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Threats</a> |
| <a href="network.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Network</a> |
| <a href="seo-mesh-normal.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Mesh Normal</a> |
| <a href="seo-mesh-inverse.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Mesh Inversé</a> |
| <a href="cognitive-inverse-mesh.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Cognitive Inversé</a> |
| <a href="cognitive-seo-geo-mesh.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">Cognitive SEO/GEO</a> |
| <a href="seo-inverse.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-slate-400 hover:text-white hover:bg-slate-800">SEO Inverse</a> |
| <a href="chs-backend.html" class="block px-3 py-2 rounded-lg text-sm font-medium text-indigo-400 bg-indigo-500/10">CHS Backend</a> |
| </div> |
| </div> |
| </nav> |
|
|
| |
| <div class="relative z-10 max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-6"> |
|
|
| |
| <div class="mb-6 flex items-center gap-3 px-4 py-2.5 bg-slate-900/50 border border-slate-800/60 rounded-xl backdrop-blur-sm flex-wrap"> |
| <div class="flex items-center gap-2"> |
| <div class="w-2 h-2 bg-indigo-400 rounded-full animate-pulse"></div> |
| <span class="text-xs font-mono text-indigo-400">CHS BACKEND JAVA ACTIF</span> |
| </div> |
| <div class="h-4 w-px bg-slate-800"></div> |
| <span class="text-xs font-mono text-slate-500" id="systemTime">--:--:--</span> |
| <div class="h-4 w-px bg-slate-800"></div> |
| <span class="text-xs font-mono text-slate-500">STACK: <span class="text-slate-300">Java 17 + Google API Client Services</span></span> |
| <div class="ml-auto flex items-center gap-2"> |
| <span class="text-xs font-mono text-slate-500">MODULES: <span class="text-indigo-400">3</span></span> |
| <div class="h-4 w-px bg-slate-800"></div> |
| <span class="text-xs font-mono text-slate-500">APIs: <span class="text-cyan-400">Search Console · Indexing · Custom Search</span></span> |
| </div> |
| </div> |
|
|
| |
| <div class="mb-8 text-center"> |
| <h1 class="text-3xl md:text-4xl font-bold text-white mb-3 tracking-tight"> |
| CHS Backend <span class="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-violet-400">Java Integration</span> |
| </h1> |
| <p class="text-slate-400 text-base max-w-2xl mx-auto leading-relaxed"> |
| Remplacement des appels décoratifs par les vrais clients Java Google — 3 APIs, 3 modules, 1 pom.xml |
| </p> |
| </div> |
|
|
| |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"> |
| <div class="stat-card bg-slate-900/50 border border-slate-800/80 rounded-xl p-4 backdrop-blur-sm"> |
| <div class="flex items-center justify-between mb-2"> |
| <span class="text-xs font-medium text-slate-500 uppercase tracking-wider">Modules</span> |
| <i data-lucide="boxes" class="w-4 h-4 text-indigo-400"></i> |
| </div> |
| <div class="text-2xl font-bold text-indigo-400 font-mono">3</div> |
| <div class="text-xs text-slate-500 font-mono mt-1">Scanner · Indexing · SERP</div> |
| </div> |
| <div class="stat-card bg-slate-900/50 border border-slate-800/80 rounded-xl p-4 backdrop-blur-sm"> |
| <div class="flex items-center justify-between mb-2"> |
| <span class="text-xs font-medium text-slate-500 uppercase tracking-wider">Google APIs</span> |
| <i data-lucide="cloud" class="w-4 h-4 text-cyan-400"></i> |
| </div> |
| <div class="text-2xl font-bold text-cyan-400 font-mono">3</div> |
| <div class="text-xs text-slate-500 font-mono mt-1">Search Console · Indexing · CSE</div> |
| </div> |
| <div class="stat-card bg-slate-900/50 border border-slate-800/80 rounded-xl p-4 backdrop-blur-sm"> |
| <div class="flex items-center justify-between mb-2"> |
| <span class="text-xs font-medium text-slate-500 uppercase tracking-wider">Auth OAuth2</span> |
| <i data-lucide="key-round" class="w-4 h-4 text-green-400"></i> |
| </div> |
| <div class="text-2xl font-bold text-green-400 font-mono">1</div> |
| <div class="text-xs text-slate-500 font-mono mt-1">Service Account unifié</div> |
| </div> |
| <div class="stat-card bg-slate-900/50 border border-slate-800/80 rounded-xl p-4 backdrop-blur-sm"> |
| <div class="flex items-center justify-between mb-2"> |
| <span class="text-xs font-medium text-slate-500 uppercase tracking-wider">Dépendances</span> |
| <i data-lucide="package" class="w-4 h-4 text-amber-400"></i> |
| </div> |
| <div class="text-2xl font-bold text-amber-400 font-mono">5</div> |
| <div class="text-xs text-slate-500 font-mono mt-1">Maven dependencies</div> |
| </div> |
| </div> |
|
|
| |
| <div class="mb-6 flex gap-2 overflow-x-auto pb-1"> |
| <button onclick="switchTab('pom', this)" class="tab-btn active px-4 py-2 rounded-lg text-xs font-semibold whitespace-nowrap transition-all"> |
| <i data-lucide="file-code" class="w-3.5 h-3.5 inline mr-1"></i>pom.xml |
| </button> |
| <button onclick="switchTab('auth', this)" class="tab-btn px-4 py-2 rounded-lg text-xs font-semibold whitespace-nowrap transition-all"> |
| <i data-lucide="key-round" class="w-3.5 h-3.5 inline mr-1"></i>Auth Provider |
| </button> |
| <button onclick="switchTab('modules', this)" class="tab-btn px-4 py-2 rounded-lg text-xs font-semibold whitespace-nowrap transition-all"> |
| <i data-lucide="boxes" class="w-3.5 h-3.5 inline mr-1"></i>Modules 1·4·6 |
| </button> |
| <button onclick="switchTab('bootstrap', this)" class="tab-btn px-4 py-2 rounded-lg text-xs font-semibold whitespace-nowrap transition-all"> |
| <i data-lucide="play" class="w-3.5 h-3.5 inline mr-1"></i>Bootstrap |
| </button> |
| <button onclick="switchTab('recap', this)" class="tab-btn px-4 py-2 rounded-lg text-xs font-semibold whitespace-nowrap transition-all"> |
| <i data-lucide="table" class="w-3.5 h-3.5 inline mr-1"></i>Récapitulatif |
| </button> |
| </div> |
|
|
| |
| <div id="tab-pom" class="tab-content mb-6"> |
| <div class="module-card bg-slate-900/50 border border-slate-800/80 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="file-code" class="w-4 h-4 text-indigo-400"></i> |
| <h2 class="text-sm font-semibold text-white">Dépendances Maven — <code class="text-indigo-400">pom.xml</code></h2> |
| </div> |
| <button onclick="copyBlock('pomCode')" class="text-xs text-indigo-400 hover:text-indigo-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <pre class="code-block p-4 text-slate-300 max-h-[500px] overflow-y-auto" id="pomCode"><dependencies> |
| <!-- ============================================ --> |
| <!-- Google API Client Services (Java) --> |
| <!-- ============================================ --> |
|
|
| <!-- 🔍 Module 1 : Search Console (URL Inspection) --> |
| <dependency> |
| <groupId>com.google.apis</groupId> |
| <artifactId>google-api-services-searchconsole</artifactId> |
| <version>v1-rev20240501-2.0.0</version> |
| </dependency> |
|
|
| <!-- 📨 Module 4 : Indexing API --> |
| <dependency> |
| <groupId>com.google.apis</groupId> |
| <artifactId>google-api-services-indexing</artifactId> |
| <version>v3-rev20240501-2.0.0</version> |
| </dependency> |
|
|
| <!-- 📡 Module 6 : Custom Search JSON API --> |
| <dependency> |
| <groupId>com.google.apis</groupId> |
| <artifactId>google-api-services-customsearch</artifactId> |
| <version>v1-rev20240501-2.0.0</version> |
| </dependency> |
|
|
| <!-- 🔐 Google Auth Library --> |
| <dependency> |
| <groupId>com.google.auth</groupId> |
| <artifactId>google-auth-library-oauth2-http</artifactId> |
| <version>1.23.0</version> |
| </dependency> |
|
|
| <!-- 📦 Jackson (JSON parsing) --> |
| <dependency> |
| <groupId>com.google.http-client</groupId> |
| <artifactId>google-http-client-jackson2</artifactId> |
| <version>1.43.3</version> |
| </dependency> |
| </dependencies></pre> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="tab-auth" class="tab-content hidden mb-6"> |
| <div class="module-card bg-slate-900/50 border border-slate-800/80 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="key-round" class="w-4 h-4 text-green-400"></i> |
| <h2 class="text-sm font-semibold text-white">Authentification unifiée — <code class="text-green-400">GoogleAuthProvider.java</code></h2> |
| </div> |
| <button onclick="copyBlock('authCode')" class="text-xs text-green-400 hover:text-green-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <div class="mb-4 flex flex-wrap gap-2"> |
| <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-semibold bg-green-500/10 text-green-400 border border-green-500/20"> |
| <i data-lucide="shield-check" class="w-3 h-3"></i>OAuth2 Service Account |
| </span> |
| <span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-semibold bg-indigo-500/10 text-indigo-400 border border-indigo-500/20"> |
| <i data-lucide="repeat" class="w-3 h-3"></i>Réutilisable |
| </span> |
| </div> |
| <pre class="code-block p-4 text-slate-300 max-h-[500px] overflow-y-auto" id="authCode">package com.chs.auth; |
|
|
| import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; |
| import com.google.api.client.json.jackson2.JacksonFactory; |
| import com.google.auth.http.HttpCredentialsAdapter; |
| import com.google.auth.oauth2.GoogleCredentials; |
| import com.google.auth.oauth2.ServiceAccountCredentials; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
| import java.util.List; |
|
|
| public class GoogleAuthProvider { |
|
|
| private static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); |
| private static GoogleCredentials credentials; |
|
|
| /** |
| * Charge les credentials OAuth2 depuis un compte de service. |
| */ |
| public static GoogleCredentials loadCredentials(String keyPath, List<String> scopes) |
| throws IOException { |
| credentials = ServiceAccountCredentials |
| .fromStream(new FileInputStream(keyPath)) |
| .createScoped(scopes); |
| return credentials; |
| } |
|
|
| /** |
| * Retourne un HttpCredentialsAdapter réutilisable. |
| */ |
| public static HttpCredentialsAdapter getAdapter() { |
| if (credentials == null) { |
| throw new IllegalStateException("loadCredentials() doit être appelé d'abord"); |
| } |
| return new HttpCredentialsAdapter(credentials); |
| } |
|
|
| public static JacksonFactory getJsonFactory() { return JSON_FACTORY; } |
|
|
| public static GoogleNetHttpTransport getTransport() |
| throws GeneralSecurityException, IOException { |
| return GoogleNetHttpTransport.newTrustedTransport(); |
| } |
| }</pre> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="tab-modules" class="tab-content hidden mb-6"> |
| <div class="space-y-6"> |
| |
| <div class="module-card bg-slate-900/50 border border-blue-500/20 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="search" class="w-4 h-4 text-blue-400"></i> |
| <h2 class="text-sm font-semibold text-white">Module 1 — Scanner Google (URL Inspection API v1)</h2> |
| </div> |
| <button onclick="copyBlock('m1Code')" class="text-xs text-blue-400 hover:text-blue-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <div class="mb-4 overflow-x-auto"> |
| <table class="api-table w-full border-collapse"> |
| <tr> |
| <td style="width:160px" class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-500/10 text-blue-400 border border-blue-500/20">API</span></td> |
| <td class="text-slate-300">Search Console v1 — <code class="text-blue-400">urlInspection.index.inspect</code></td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-500/10 text-blue-400 border border-blue-500/20">Scope OAuth</span></td> |
| <td class="font-mono text-xs text-cyan-400"><code>https://www.googleapis.com/auth/webmasters</code></td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-500/10 text-blue-400 border border-blue-500/20">Endpoint</span></td> |
| <td class="font-mono text-xs text-cyan-400"><code>POST /v1/urlInspection/index:inspect</code></td> |
| </tr> |
| </table> |
| </div> |
| <pre class="code-block p-4 text-slate-300 max-h-[400px] overflow-y-auto" id="m1Code">package com.chs.module1; |
|
|
| import com.chs.auth.GoogleAuthProvider; |
| import com.google.api.services.searchconsole.v1.SearchConsole; |
| import com.google.api.services.searchconsole.v1.model.*; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
|
|
| public class CanonicalScanner { |
|
|
| private final SearchConsole client; |
| private final String siteUrl; |
|
|
| public CanonicalScanner(String siteUrl) throws GeneralSecurityException, IOException { |
| this.siteUrl = siteUrl; |
| this.client = new SearchConsole.Builder( |
| GoogleAuthProvider.getTransport(), |
| GoogleAuthProvider.getJsonFactory(), |
| GoogleAuthProvider.getAdapter()) |
| .setApplicationName("canonical-hijack-shield") |
| .build(); |
| } |
|
|
| /** |
| * Inspecte une URL via l'API Search Console. |
| * Retourne le verdict canonique (userCanonical vs googleCanonical). |
| */ |
| public ScanResult inspect(String targetUrl) throws IOException { |
| InspectUrlIndexRequest request = new InspectUrlIndexRequest() |
| .setInspectionUrl(targetUrl) |
| .setSiteUrl(this.siteUrl) |
| .setLanguageCode("fr"); |
|
|
| InspectUrlIndexResponse response = client |
| .urlInspection() |
| .index() |
| .inspect(request) |
| .execute(); |
|
|
| IndexStatusResult result = response.getInspectionResult().getIndexStatusResult(); |
|
|
| // 🔴 Détection du verrou canonique parasite |
| String userCanonical = result.getUserCanonical(); |
| String googleCanonical = result.getGoogleCanonical(); |
| boolean isParasited = userCanonical != null |
| && googleCanonical != null |
| && !userCanonical.equals(googleCanonical); |
|
|
| return new ScanResult( |
| result.getVerdict(), |
| result.getCoverageState(), |
| userCanonical, |
| googleCanonical, |
| isParasited ? "parameter_injection" : null, |
| isParasited, |
| result.getLastCrawlTime() |
| ); |
| } |
| }</pre> |
| </div> |
| </div> |
|
|
| |
| <div class="module-card bg-slate-900/50 border border-green-500/20 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="send" class="w-4 h-4 text-green-400"></i> |
| <h2 class="text-sm font-semibold text-white">Module 4 — Indexing API v3 (ping Google après nettoyage)</h2> |
| </div> |
| <button onclick="copyBlock('m4Code')" class="text-xs text-green-400 hover:text-green-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <div class="mb-4 overflow-x-auto"> |
| <table class="api-table w-full border-collapse"> |
| <tr> |
| <td style="width:160px" class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-green-500/10 text-green-400 border border-green-500/20">API</span></td> |
| <td class="text-slate-300">Indexing API v3 — <code class="text-green-400">urlNotifications.publish</code></td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-green-500/10 text-green-400 border border-green-500/20">Scope OAuth</span></td> |
| <td class="font-mono text-xs text-cyan-400"><code>https://www.googleapis.com/auth/indexing</code></td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-green-500/10 text-green-400 border border-green-500/20">Endpoint</span></td> |
| <td class="font-mono text-xs text-cyan-400"><code>POST /v3/urlNotifications:publish</code></td> |
| </tr> |
| </table> |
| </div> |
| <pre class="code-block p-4 text-slate-300 max-h-[400px] overflow-y-auto" id="m4Code">package com.chs.module4; |
|
|
| import com.chs.auth.GoogleAuthProvider; |
| import com.google.api.services.indexing.v3.Indexing; |
| import com.google.api.services.indexing.v3.model.*; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
|
|
| public class IndexingNotifier { |
|
|
| private final Indexing client; |
|
|
| public IndexingNotifier() throws GeneralSecurityException, IOException { |
| this.client = new Indexing.Builder( |
| GoogleAuthProvider.getTransport(), |
| GoogleAuthProvider.getJsonFactory(), |
| GoogleAuthProvider.getAdapter()) |
| .setApplicationName("canonical-hijack-shield") |
| .build(); |
| } |
|
|
| /** |
| * Notifie Google que l'URL a été mise à jour (canonical corrigé). |
| * Équivaut à : POST /v3/urlNotifications:publish |
| * { "url": "...", "type": "URL_UPDATED" } |
| */ |
| public boolean notifyUrlUpdated(String url) throws IOException { |
| UrlNotification notification = new UrlNotification() |
| .setUrl(url) |
| .setType("URL_UPDATED"); |
|
|
| UrlNotificationMetadata response = client |
| .urlNotifications() |
| .publish(notification) |
| .execute(); |
|
|
| // ✅ 200 OK → Google a bien reçu la notification |
| return response != null |
| && response.getLatestUpdate() != null |
| && response.getLatestUpdate().getUrl().equals(url); |
| } |
|
|
| /** |
| * Notifie que l'URL a été supprimée (si nécessaire). |
| */ |
| public boolean notifyUrlRemoved(String url) throws IOException { |
| UrlNotification notification = new UrlNotification() |
| .setUrl(url) |
| .setType("URL_DELETED"); |
|
|
| UrlNotificationMetadata response = client |
| .urlNotifications() |
| .publish(notification) |
| .execute(); |
|
|
| return response != null; |
| } |
| }</pre> |
| </div> |
| </div> |
|
|
| |
| <div class="module-card bg-slate-900/50 border border-red-500/20 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="radar" class="w-4 h-4 text-red-400"></i> |
| <h2 class="text-sm font-semibold text-white">Module 6 — Custom Search JSON API v1 (surveillance SERP)</h2> |
| </div> |
| <button onclick="copyBlock('m6Code')" class="text-xs text-red-400 hover:text-red-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <div class="mb-4 overflow-x-auto"> |
| <table class="api-table w-full border-collapse"> |
| <tr> |
| <td style="width:160px" class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-500/10 text-red-400 border border-red-500/20">API</span></td> |
| <td class="text-slate-300">Custom Search JSON API v1</td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-500/10 text-red-400 border border-red-500/20">Auth</span></td> |
| <td class="font-mono text-xs text-cyan-400">API Key (<code>GOOGLE_API_KEY</code>) + Search Engine ID (<code>CX</code>)</td> |
| </tr> |
| <tr> |
| <td class="font-mono text-slate-500"><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-500/10 text-red-400 border border-red-500/20">Endpoint</span></td> |
| <td class="font-mono text-xs text-cyan-400"><code>GET /customsearch/v1?key=...&cx=...&q=...</code></td> |
| </tr> |
| </table> |
| </div> |
| <pre class="code-block p-4 text-slate-300 max-h-[400px] overflow-y-auto" id="m6Code">package com.chs.module6; |
|
|
| import com.chs.auth.GoogleAuthProvider; |
| import com.google.api.services.customsearch.v1.CustomSearchAPI; |
| import com.google.api.services.customsearch.v1.model.*; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
| import java.util.ArrayList; |
| import java.util.List; |
|
|
| public class SerpMonitor { |
|
|
| private final CustomSearchAPI client; |
| private final String cx; // Search Engine ID (configuré dans cse.google.com) |
|
|
| public SerpMonitor(String apiKey, String cx) throws GeneralSecurityException, IOException { |
| this.cx = cx; |
| this.client = new CustomSearchAPI.Builder( |
| GoogleAuthProvider.getTransport(), |
| GoogleAuthProvider.getJsonFactory(), |
| request -> { /* pas d'auth OAuth ici, on utilise la clé API en paramètre */ }) |
| .setApplicationName("canonical-hijack-shield") |
| .build(); |
| } |
|
|
| /** |
| * Surveillance SERP : vérifie si des URLs parasites apparaissent |
| * pour une requête donnée sur un domaine spécifique. |
| */ |
| public MonitorResult monitor(String domain, String keyword, String apiKey) throws IOException { |
| String query = String.format("site:%s \"%s\"", domain, keyword); |
|
|
| CustomSearchAPI.Cse.List request = client.cse().list() |
| .setCx(this.cx) |
| .setKey(apiKey) |
| .setQ(query) |
| .setLr("lang_fr"); |
|
|
| Search results = request.execute(); |
|
|
| List<SerpEntry> cleanUrls = new ArrayList<>(); |
| List<SerpEntry> parasiteUrls = new ArrayList<>(); |
|
|
| if (results.getItems() != null) { |
| for (int i = 0; i < results.getItems().size(); i++) { |
| Result item = results.getItems().get(i); |
| String link = item.getLink(); |
|
|
| // 🔴 Détection d'URL parasite (paramètres suspects) |
| boolean isParasite = link.contains("?parasite=") |
| || link.contains("?utm_source=") |
| || link.contains("?gclid=") |
| || link.contains("?fbclid="); |
|
|
| SerpEntry entry = new SerpEntry(i + 1, link, isParasite); |
| if (isParasite) { |
| parasiteUrls.add(entry); |
| } else { |
| cleanUrls.add(entry); |
| } |
| } |
| } |
|
|
| boolean alertTriggered = !parasiteUrls.isEmpty(); |
|
|
| return new MonitorResult( |
| results.getSearchInformation() != null |
| ? results.getSearchInformation().getTotalResults() : 0, |
| cleanUrls, |
| parasiteUrls, |
| alertTriggered ? "degraded" : "clean", |
| alertTriggered |
| ); |
| } |
| }</pre> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="tab-bootstrap" class="tab-content hidden mb-6"> |
| <div class="module-card bg-slate-900/50 border border-slate-800/80 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="play" class="w-4 h-4 text-indigo-400"></i> |
| <h2 class="text-sm font-semibold text-white">Bootstrap — <code class="text-indigo-400">ChsApplication.java</code></h2> |
| </div> |
| <button onclick="copyBlock('bootstrapCode')" class="text-xs text-indigo-400 hover:text-indigo-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5"> |
| <pre class="code-block p-4 text-slate-300 max-h-[500px] overflow-y-auto" id="bootstrapCode">package com.chs; |
|
|
| import com.chs.auth.GoogleAuthProvider; |
| import com.chs.module1.CanonicalScanner; |
| import com.chs.module1.ScanResult; |
| import com.chs.module4.IndexingNotifier; |
| import com.chs.module6.MonitorResult; |
| import com.chs.module6.SerpMonitor; |
| import com.google.auth.oauth2.GoogleCredentials; |
| import java.util.List; |
|
|
| public class ChsApplication { |
|
|
| public static void main(String[] args) throws Exception { |
|
|
| // 🔐 1. Charger les credentials |
| String keyPath = System.getenv("CHS_GOOGLE_CREDENTIALS"); |
| GoogleAuthProvider.loadCredentials(keyPath, List.of( |
| "https://www.googleapis.com/auth/webmasters", // M1 |
| "https://www.googleapis.com/auth/indexing" // M4 |
| )); |
|
|
| // 🔍 2. Module 1 — Scanner |
| CanonicalScanner scanner = new CanonicalScanner( |
| "sc-domain:mediapart.fr"); |
| ScanResult scan = scanner.inspect( |
| "https://www.mediapart.fr/journal/france/270917/la-nouvelle-recherche-de-financement"); |
|
|
| System.out.println("📋 Verdict: " + scan.verdict()); |
| System.out.println("🔴 Parasité: " + scan.isParasited()); |
| System.out.println("📐 User Canonical: " + scan.userCanonical()); |
| System.out.println("⚠️ Google Canonical: " + scan.googleCanonical()); |
|
|
| // 🔧 3. Module 4 — Si parasité, on nettoie + ping |
| if (scan.isParasited()) { |
| IndexingNotifier notifier = new IndexingNotifier(); |
| boolean ok = notifier.notifyUrlUpdated(scan.userCanonical()); |
| System.out.println("📨 Indexing API ping → " + (ok ? "✅ OK" : "❌ Échec")); |
| } |
|
|
| // 📡 4. Module 6 — Surveillance |
| String apiKey = System.getenv("CHS_GOOGLE_API_KEY"); |
| String cx = System.getenv("CHS_CSE_ID"); |
| SerpMonitor monitor = new SerpMonitor(apiKey, cx); |
| MonitorResult mon = monitor.monitor("mediapart.fr", "fortune", apiKey); |
|
|
| System.out.println("📡 SERP Status: " + mon.status()); |
| System.out.println("🧹 Clean URLs: " + mon.cleanUrls().size()); |
| System.out.println("🦠 Parasite URLs: " + mon.parasiteUrls().size()); |
| System.out.println("🚨 Alerte: " + mon.alertTriggered()); |
| } |
| }</pre> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="tab-recap" class="tab-content hidden mb-6"> |
| <div class="module-card bg-slate-900/50 border border-slate-800/80 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="table" class="w-4 h-4 text-indigo-400"></i> |
| <h2 class="text-sm font-semibold text-white">Récapitulatif — Mapping Modules ↔ APIs Google</h2> |
| </div> |
| <button onclick="copyBlock('recapCode')" class="text-xs text-indigo-400 hover:text-indigo-300 transition-colors font-mono flex items-center gap-1"> |
| <i data-lucide="copy" class="w-3.5 h-3.5"></i>COPIER |
| </button> |
| </div> |
| <div class="p-5 overflow-x-auto"> |
| <table class="api-table w-full border-collapse"> |
| <thead> |
| <tr> |
| <th>Module CHS</th> |
| <th>Google API</th> |
| <th>Dépendance Maven</th> |
| <th>Scope OAuth</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-500/10 text-blue-400 border border-blue-500/20">M1 Scanner</span></td> |
| <td class="text-slate-300">Search Console v1</td> |
| <td class="font-mono text-xs text-cyan-400"><code>google-api-services-searchconsole</code></td> |
| <td class="font-mono text-xs text-cyan-400"><code>webmasters</code></td> |
| </tr> |
| <tr> |
| <td><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-green-500/10 text-green-400 border border-green-500/20">M4 Remediation</span></td> |
| <td class="text-slate-300">Indexing API v3</td> |
| <td class="font-mono text-xs text-cyan-400"><code>google-api-services-indexing</code></td> |
| <td class="font-mono text-xs text-cyan-400"><code>indexing</code></td> |
| </tr> |
| <tr> |
| <td><span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-500/10 text-red-400 border border-red-500/20">M6 Monitoring</span></td> |
| <td class="text-slate-300">Custom Search JSON v1</td> |
| <td class="font-mono text-xs text-cyan-400"><code>google-api-services-customsearch</code></td> |
| <td class="font-mono text-xs text-amber-400">API Key (pas OAuth)</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-6 bg-slate-900/50 border border-indigo-500/20 rounded-xl backdrop-blur-sm overflow-hidden"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="git-branch" class="w-4 h-4 text-indigo-400"></i> |
| <h2 class="text-sm font-semibold text-white">Flux d'Architecture CHS</h2> |
| </div> |
| </div> |
| <div class="p-5"> |
| <div class="flex flex-col md:flex-row items-center justify-center gap-3 text-center"> |
| <div class="bg-blue-500/10 border border-blue-500/20 rounded-xl px-5 py-4 min-w-[160px]"> |
| <i data-lucide="search" class="w-6 h-6 text-blue-400 mx-auto mb-2"></i> |
| <div class="text-sm font-bold text-blue-400">M1 Scanner</div> |
| <div class="text-[10px] text-slate-500 font-mono">URL Inspection</div> |
| </div> |
| <i data-lucide="arrow-right" class="w-5 h-5 text-slate-600 hidden md:block"></i> |
| <i data-lucide="arrow-down" class="w-5 h-5 text-slate-600 md:hidden"></i> |
| <div class="bg-amber-500/10 border border-amber-500/20 rounded-xl px-5 py-4 min-w-[160px]"> |
| <i data-lucide="alert-triangle" class="w-6 h-6 text-amber-400 mx-auto mb-2"></i> |
| <div class="text-sm font-bold text-amber-400">Verdict</div> |
| <div class="text-[10px] text-slate-500 font-mono">Parasité ?</div> |
| </div> |
| <i data-lucide="arrow-right" class="w-5 h-5 text-slate-600 hidden md:block"></i> |
| <i data-lucide="arrow-down" class="w-5 h-5 text-slate-600 md:hidden"></i> |
| <div class="bg-green-500/10 border border-green-500/20 rounded-xl px-5 py-4 min-w-[160px]"> |
| <i data-lucide="send" class="w-6 h-6 text-green-400 mx-auto mb-2"></i> |
| <div class="text-sm font-bold text-green-400">M4 Indexing</div> |
| <div class="text-[10px] text-slate-500 font-mono">Ping Google</div> |
| </div> |
| <i data-lucide="arrow-right" class="w-5 h-5 text-slate-600 hidden md:block"></i> |
| <i data-lucide="arrow-down" class="w-5 h-5 text-slate-600 md:hidden"></i> |
| <div class="bg-red-500/10 border border-red-500/20 rounded-xl px-5 py-4 min-w-[160px]"> |
| <i data-lucide="radar" class="w-6 h-6 text-red-400 mx-auto mb-2"></i> |
| <div class="text-sm font-bold text-red-400">M6 SERP</div> |
| <div class="text-[10px] text-slate-500 font-mono">Surveillance</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-slate-900/50 border border-slate-800/80 rounded-xl backdrop-blur-sm overflow-hidden mb-6"> |
| <div class="flex items-center justify-between px-5 py-3 border-b border-slate-800/60"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="terminal" class="w-4 h-4 text-indigo-400"></i> |
| <h2 class="text-sm font-semibold text-white">Journal Backend</h2> |
| </div> |
| <button onclick="clearLogs()" class="text-xs text-slate-500 hover:text-slate-300 transition-colors font-mono">CLEAR</button> |
| </div> |
| <div class="overflow-y-auto p-4 font-mono text-xs space-y-1.5" style="max-height: 220px;" id="activityLog"></div> |
| </div> |
| </div> |
|
|
| |
| <div id="chsToast" class="fixed bottom-6 right-6 z-50 transform translate-y-20 opacity-0 transition-all duration-300"> |
| <div class="bg-slate-900 border border-slate-700 rounded-xl px-4 py-3 shadow-2xl flex items-center gap-3"> |
| <div class="w-8 h-8 bg-indigo-500/20 rounded-lg flex items-center justify-center"> |
| <i data-lucide="check" class="w-4 h-4 text-indigo-400"></i> |
| </div> |
| <span class="text-sm font-medium text-slate-200" id="chsToastMsg">Done</span> |
| </div> |
| </div> |
|
|
| <script src="https://unpkg.com/lucide@latest"></script> |
| <script src="chs-backend.js"></script> |
| </body> |
| </html> |