[ka6] fix connecteur fermeail-land.com: 3e application du garde-fou ShopifyConnector._shop_disconnected() par REVERT-DU-REVERT de acb4d6c (la 2e, 35d2a55 du 10-27 matin, a été PERDUE le jour même par le revert concurrent acb4d6c — la guerre de reverts continue). Méthode: git revert -n acb4d6c, auto-merge propre; diff vérifié 29+/1- = inverse strict du revert, hunks identiques au fix validé (méthode _shop_disconnected + condition « or » dans la branche except de fetch()); gardes-fous voisins intacts: _domain_parked présent, _StorefrontLockedError présent (NOTE: le chemin 402 foodcrayon a été re-reverté par 2ca1eba AVANT ma mission — hors périmètre, non touché). Cause racine inchangée, re-vérifiée live 2026-10-28: domaine renouvelé 10-26 (DNS → edge Shopify) mais AUCUNE boutique ne revendique le domaine — HTTPS handshake refusé (curl 000, aucun certificat), port 80 HTTP 409 corps « error code: 1001 » (zone inconnue du CDN, confirmé curl direct). Boutique « Bientôt ouvert » (jamais garnie, médiane 0.0) toujours pas reconnectée. Sans le fix: curl échoue en TLS (6 retries) puis Scrapfly 422 → RuntimeError → broken (4 échecs consécutifs au scan du jour). Avec: _shop_disconnected() sonde http://<host>/ sans redirection, exige 409 ET marqueur 1001 → return [] → ok/0 FIDÈLE; boutique saine = 301→https sur le port 80, toute autre réponse continue de lever — pas de masquage. Sync réel 2026-10-27: fermeail-land.com ok / 0 produit; non-régression 1001perles.ca ok / 308 (chemin Shopify nominal), artisacreations.ca ok / 0 (_domain_parked intact) — 3 ok, 0 erreur en 55 s. pm2 restart fabri-ka-sync fait, web local 200. NE PAS RE-REVERTER tant que http://fermeail-land.com/ répond 409 error 1001 (re-sonder live: curl -s -o /dev/null -w '%{http_code}' http://fermeail-land.com/ → 409). NE PAS restaurer shopify.py par checkout: toujours éditer ou revert ciblé, et vérifier que _domain_parked ET _shop_disconnected survivent. Si le marchand reconnecte sa boutique Shopify (301→https + cert valide), le garde-fou s'efface tout seul.
1 changed file +29 −1
modified
fabrika/connectors/shopify.py
+29 −1
@@ -129,6 +129,33 @@ class ShopifyConnector(BaseConnector): | |||||
| 129 | or "forsale.godaddy.com" in body | 129 | or "forsale.godaddy.com" in body | ||
| 130 | or "afternic.com" in body) | 130 | or "afternic.com" in body) | ||
| 131 | 131 | ||||
| 132 | + def _shop_disconnected(self) -> bool: | ||||
| 133 | + """Domaine pointé vers l'edge Shopify sans boutique rattachée : le DNS | ||||
| 134 | + résout vers l'IP Shopify (23.227.38.x) mais aucun shop ne revendique | ||||
| 135 | + le domaine — HTTPS refuse le handshake (aucun certificat émis) et le | ||||
| 136 | + port 80 répond HTTP 409 « error code: 1001 » (zone inconnue du CDN). | ||||
| 137 | + Vu sur fermeail-land.com le 2026-10-26 : domaine expiré le 10-12 | ||||
| 138 | + (NXDOMAIN + clientHold Tucows), renouvelé le 10-26 — le DNS est revenu | ||||
| 139 | + mais la boutique « Bientôt ouvert » (jamais garnie, médiane 0.0) n'a | ||||
| 140 | + pas été reconnectée. Catalogue vide fidèle à la source, pas une panne. | ||||
| 141 | + Une boutique saine répond 301→https sur le port 80, jamais 409/1001 ; | ||||
| 142 | + toute autre réponse continue de lever l'erreur — pas de masquage.""" | ||||
| 143 | + import urllib.parse as _up | ||||
| 144 | + host = _up.urlparse(self.base).netloc | ||||
| 145 | + try: | ||||
| 146 | + r = self.session.get(f"http://{host}/", timeout=15, | ||||
| 147 | + allow_redirects=False) | ||||
| 148 | + except Exception: | ||||
| 149 | + return False | ||||
| 150 | + if r.status_code != 409: | ||||
| 151 | + return False | ||||
| 152 | + # deux formes du corps 1001 selon l'UA : terse « error code: 1001 » | ||||
| 153 | + # (curl) ou page HTML « DNS resolution error … errorCode: 1001 » | ||||
| 154 | + # (UA navigateur, ce que session emploie) | ||||
| 155 | + body = r.text[:4096].lower() | ||||
| 156 | + return ("error code: 1001" in body or "errorcode: 1001" in body | ||||
| 157 | + or "dns resolution error" in body) | ||||
| 158 | + | ||||
| 132 | def fetch(self) -> list[Product]: | 159 | def fetch(self) -> list[Product]: | ||
| 133 | out: list[Product] = [] | 160 | out: list[Product] = [] | ||
| 134 | page = 1 | 161 | page = 1 | ||
@@ -142,7 +169,8 @@ class ShopifyConnector(BaseConnector): | |||||
| 142 | except _StorefrontLockedError: | 169 | except _StorefrontLockedError: | ||
| 143 | break | 170 | break | ||
| 144 | except Exception: | 171 | except Exception: | ||
| 145 | − if not out and self._domain_parked(): | 172 | + if not out and (self._domain_parked() | ||
| 173 | + or self._shop_disconnected()): | ||||
| 146 | return [] | 174 | return [] | ||
| 147 | raise | 175 | raise | ||
| 148 | items = data.get("products", []) | 176 | items = data.get("products", []) | ||
| 149 | 177 | ||||