🔌 CHS Backend — Intégration Google API Client Services Java

Remplacement des appels décoratifs par les vrais clients Java Google — 3 APIs, 3 modules, 1 pom.xml

1

Dépendances Maven — pom.xml

<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>
2

Authentification unifiée — GoogleAuthProvider.java

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();
    }
}
3

Module 1 — Scanner Google URL Inspection API v1

API Search Console v1 — urlInspection.index.inspect
Scope OAuth https://www.googleapis.com/auth/webmasters
Endpoint POST /v1/urlInspection/index:inspect
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()
        );
    }
}
4

Module 4 — Indexing API v3 Ping Google après nettoyage

API Indexing API v3 — urlNotifications.publish
Scope OAuth https://www.googleapis.com/auth/indexing
Endpoint POST /v3/urlNotifications:publish
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;
    }
}
5

Module 6 — Custom Search JSON API v1 Surveillance SERP

API Custom Search JSON API v1
Auth API Key (GOOGLE_API_KEY) + Search Engine ID (CX)
Endpoint GET /customsearch/v1?key=...&cx=...&q=...
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
        );
    }
}
6

Bootstrap — ChsApplication.java

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());
    }
}
7

Récapitulatif — Mapping Modules ↔ APIs Google

Module CHS Google API Dépendance Maven Scope OAuth
M1 Scanner Search Console v1 google-api-services-searchconsole webmasters
M4 Remediation Indexing API v3 google-api-services-indexing indexing
M6 Monitoring Custom Search JSON v1 google-api-services-customsearch API Key (pas OAuth)