SPB Git forge

spb/fabri-ka

Public

Agrégateur de produits québécois — www.fabri-ka.com

217commits 1branches 0releases
66.1 MBsize
maindefault branch
4 h agolast push
Python 38.4% HTML 30.3% TypeScript 17.2% CSS 11% JavaScript 3.2%
5.9 KB · 136 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Fabri-Ka — Agrégateur de produits québécois3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/square.py : connecteur générique Square Online (Weebly).5#   Les sites Square Online exposent une API JSON publique côté storefront :6#     /app/store/api/v13/editor/users/<user_id>/sites/<site_id>/products7#   Les identifiants user_id / site_id sont imprimés dans le HTML de chaque8#   page (config JS `user_id: '…'` / `site_id: '…'`). Pagination via9#   meta.pagination.total_pages, 100 produits/page.10# -----------------------------------------------------------------------------11from __future__ import annotations1213import re1415from ..schema import Product, parse_price16from .base import BaseConnector1718# le HTML expose les IDs sous deux formes (config JS et JSON embarqué)19_USER_RES = [re.compile(r"user_id: '(\d+)'"), re.compile(r'"user_id":"?(\d+)')]20_SITE_RES = [re.compile(r"site_id: '(\d+)'"), re.compile(r'"site_id":"?(\d+)')]2122API_PATH = "/app/store/api/v13"232425def extract_ids(html: str) -> tuple[str | None, str | None]:26    """(user_id, site_id) depuis le HTML d'une page Square Online."""27    user = site = None28    for rx in _USER_RES:29        m = rx.search(html)30        if m:31            user = m.group(1)32            break33    for rx in _SITE_RES:34        m = rx.search(html)35        if m:36            site = m.group(1)37            break38    return user, site394041class SquareConnector(BaseConnector):42    platform = "square"43    request_delay = 0.54445    def _ids(self) -> tuple[str, str]:46        r = self.get(self.base + "/")47        user, site = extract_ids(r.text)48        if not (user and site):49            raise RuntimeError("IDs Square Online (user_id/site_id) introuvables dans le HTML")50        return user, site5152    def fetch(self) -> list[Product]:53        user, site = self._ids()54        out: list[Product] = []55        page = 156        while page <= self.max_pages:57            # include=images,options : galerie complète + variantes dans la même58            # requête paginée (levée du « mono-image » sans requête supplémentaire)59            url = (f"{self.base}{API_PATH}/editor/users/{user}/sites/{site}"60                   f"/products?page={page}&per_page=100&include=images,options")61            data = self.get(url).json()62            items = data.get("data") or []63            for it in items:64                if (it.get("visibility") or "visible") != "visible":65                    continue66                price = it.get("price") or {}67                low = parse_price(price.get("low"))68                high = parse_price(price.get("high"))69                reg_high = parse_price(price.get("regular_high"))70                on_sale = bool(it.get("on_sale"))71                badges = it.get("badges") or {}72                inv = it.get("inventory") or {}73                available = not (badges.get("out_of_stock")74                                 or inv.get("all_variations_sold_out"))75                thumb = ((it.get("thumbnail") or {}).get("data") or {})76                img = thumb.get("absolute_url")77                images: list[str] = []78                for rec in ((it.get("images") or {}).get("data") or []):79                    u = rec.get("absolute_url") or ""80                    if u and not u.startswith("http"):81                        u = "https://" + u.lstrip("/")82                    if u and u not in images:83                        images.append(u)84                if not images and img:85                    images = [img if img.startswith("http") else "https://" + img.lstrip("/")]86                det: dict = {}87                opts = []88                for o in ((it.get("options") or {}).get("data") or []):89                    name = o.get("name") or ""90                    if name:91                        opts.append({"name": name,92                                     "values": [str(c) for c in (o.get("choice_order") or [])]})93                if opts:94                    det["options"] = opts95                try:96                    if float(it.get("avg_rating") or 0) > 0:97                        det["average_rating"] = float(it["avg_rating"])98                except (TypeError, ValueError):99                    pass100                if on_sale:101                    det["on_sale"] = True102                if it.get("is_alcoholic"):103                    det["is_alcoholic"] = True104                if it.get("sku"):105                    det["sku"] = str(it["sku"])106                if it.get("rating_count"):107                    try:108                        det["review_count"] = int(it["rating_count"])109                    except (TypeError, ValueError):110                        pass111                for k in ("created_date", "updated_date"):112                    if it.get(k):113                        det[k] = it[k]114                site_link = (it.get("site_link") or "").lstrip("/")115                out.append(Product(116                    store_id=self.store_id,117                    external_id=str(it.get("id") or it.get("square_id") or ""),118                    url=f"{self.base}/{site_link}" if site_link else self.base,119                    title=it.get("name", "") or "",120                    description=(it.get("short_description")121                                 or it.get("seo_page_description") or ""),122                    price=low,123                    price_max=high if high and low and high > low else low,124                    compare_at_price=(reg_high if on_sale and reg_high125                                      and reg_high > (high or 0) else None),126                    images=images,127                    product_type=it.get("product_type", "") or "",128                    available=available,129                    details=det,130                ))131            pag = (data.get("meta") or {}).get("pagination") or {}132            if page >= int(pag.get("total_pages") or page):133                break134            page += 1135        return out136