# نسخة من الأرشيف عندك

> القائمة كاملة بلا سحوبات، والسجل حين تحتاجه.

المصدر: https://new.wasmenia.com/developers/docs/guides/sync

## الخطة

القائمة لا تُحسب سحبًا، والسجل يُحسب. لذلك:

1. امشِ على صفحات القائمة كلها، واحفظ بطاقة كل جهة: `slug` والاسم والدولة والصورة المصغرة. هذا يكفي لقائمة وبحث عندك.
2. اقرأ سجل الجهة حين يفتحها مستخدم أول مرة، واحفظه.
3. مرة في اليوم، امشِ على القائمة بـ `sort=updated` (الأحدث تعديلًا أولًا)، وأعد قراءة السجلات التي حفظتها، حتى تصل إلى سجل `updatedAt` فيه أقدم من آخر مرة، ثم توقف.

هكذا لا تُصرف السحوبات إلا على جهات فتحها مستخدموك، أو تغيّرت بعد أن حفظتها.

## المرور على القائمة

`walk.js`

```javascript
// Every card of the archive, page by page. Lists are not pulls.
const base = 'https://new.wasmenia.com';
const headers = { Authorization: `Bearer ${process.env.WASMENIA_API_KEY}` };
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));

export async function* allBrands(filters = {}) {
  for (let page = 1; ; page++) {
    const url = new URL('/api/v1/brands', base);
    for (const [k, v] of Object.entries({ ...filters, limit: 50, page }))
      url.searchParams.set(k, String(v));
    const res = await fetch(url, { headers });
    if (res.status === 429) {
      await sleep(Number(res.headers.get('Retry-After') ?? 60));
      page--; // the same page again
      continue;
    }
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const { items, pageCount } = await res.json();
    yield* items; // { slug, name, country, category, verified, versions, thumbnailUrl, url }
    if (page >= pageCount || page >= 500) return;
  }
}

for await (const brand of allBrands({ country: 'SA' })) {
  await db.cards.upsert(brand.slug, brand);
}
```

`walk.py`

```python
import os
import time

import requests

BASE = 'https://new.wasmenia.com'
HEADERS = {'Authorization': f"Bearer {os.environ['WASMENIA_API_KEY']}"}

def all_brands(**filters):
    page = 1
    while True:
        res = requests.get(
            f'{BASE}/api/v1/brands',
            params={**filters, 'limit': 50, 'page': page},
            headers=HEADERS,
            timeout=10,
        )
        if res.status_code == 429:
            time.sleep(int(res.headers.get('Retry-After', 60)))
            continue
        res.raise_for_status()
        data = res.json()
        yield from data['items']
        if page >= data['pageCount'] or page >= 500:
            return
        page += 1
```

## حدود المرور

`limit` حتى 50، و`page` حتى 500، فأقصى ما تمر عليه القائمة بفلاتر واحدة 25,000 جهة. إن كان `total` أكبر من ذلك، قسّم المرور بفلتر: دولة بعد دولة (`country`) أو قطاعًا بعد قطاع (`category`).

حد القائمة 60 طلبًا في الدقيقة بلا مفتاح، وحد باقتك بمفتاح. الكود أعلاه ينتظر `Retry-After` حين يُرد 429 ثم يعيد الصفحة نفسها.

## التحديث اليومي

`refresh.js`

```javascript
// Once a day: re-read what changed since the last run, newest change first.
const base = 'https://new.wasmenia.com';
const headers = { Authorization: `Bearer ${process.env.WASMENIA_API_KEY}` };

export async function refresh(since /* Date of the last run */) {
  for (let page = 1; page <= 500; page++) {
    const list = await fetch(`${base}/api/v1/brands?sort=updated&limit=50&page=${page}`, { headers });
    if (!list.ok) throw new Error(`HTTP ${list.status}`);
    const { items, pageCount } = await list.json();
    for (const card of items) {
      if (!(await db.records.has(card.slug))) continue; // never opened: nothing to refresh
      const res = await fetch(`${base}/api/v1/brands/${card.slug}`, { headers }); // a pull
      if (res.status === 402) return; // the month's pulls are used: try after X-Quota-Reset
      if (res.status === 404) { await db.records.delete(card.slug); continue; }
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const brand = await res.json();
      // A renamed brand answers under its new slug (308, followed by fetch).
      if (brand.slug !== card.slug) await db.records.rename(card.slug, brand.slug);
      await db.records.put(brand.slug, brand);
      if (new Date(brand.updatedAt) < since) return; // older than the last run: done
    }
    if (page >= pageCount) return;
  }
}
```

## حين يتغيّر المعرّف

إن غيّرت جهة `slug` في الأرشيف، يحوّل العنوان القديم إلى الجديد بـ 308، ويتبعه `fetch` و`requests` دون أن تفعل شيئًا. قارن `slug` في السجل بما طلبته: إن اختلفا، انقل ما حفظته إلى المعرّف الجديد. والجهة التي يُرد عليها 404 لم تعد منشورة: احذفها من نسختك.

## ما تعرضه من نسختك

اعرض من نسختك ما حفظته كما هو، ومعه تاريخ آخر قراءة إن احتاج القارئ أن يعرف. واجعل الصور من `thumbnailUrl` و`url` كما جاءت، فهي ثابتة على دومين الملفات ولا تُحسب تحميلًا. تفاصيل الملفات في [الملفات والتحميل](https://new.wasmenia.com/developers/docs/concepts/assets.md).
