# מדריך API לסרטים ב-JavaScript

> כתבו סקריפט Node.js שמקבל רשימת מזהי סרטים ומדפיס כרטיס לכל אחד: כותרת, שנה, במאי, שחקנים עיקריים וכל הדירוגים הידועים. הוא משתמש ב-fetch המובנה, ולכן אין חבילות npm. קרדיטים ודירוגים מגיעים באותה בקשה אחת דרך append_to_response, בלי קריאות נוספות. הסקריפט מוסיף מטמון TTL וניסיון חוזר ב-429.

כותבים את `cards.mjs`, סקריפט Node.js. נותנים לו מזהי סרטים והוא מדפיס כרטיס לכל סרט עם כותרת, שנה, במאי, שחקנים עיקריים וכל הדירוגים הידועים. הוא משתמש ב-`fetch` המובנה, דורש Node 20 ומעלה ולוקח כ-15 דקות.

## שלב 1: קבלת מפתח API

המסלולים בתשלום מראש ונבדלים רק במהירות: 20, 40 או 120 בקשות לדקה, עם כל הנתונים בכל מסלול. בחרו אחד בעמוד [תמחור](https://amodb.dev/he/pricing/), צרו מפתח בלוח הבקרה של החשבון, ואז שימו את המפתח בסביבה:

```bash
export AMODB_KEY="YOUR_API_KEY"
```

הסקריפט קורא את `process.env.AMODB_KEY`, ולכן המפתח אף פעם לא נוחת בקוד המקור או בבקרת גרסאות.

## שלב 2: יצירת הפרויקט

אין מה להתקין. צרו תיקייה וקובץ `cards.mjs` ריק:

```bash
mkdir movie-cards && cd movie-cards
node --version
```

הגרסה חייבת להדפיס `v20` ומעלה. הסיומת `.mjs` מפעילה מודולי ES בלי `package.json`.

## שלב 3: שליחת הבקשה הראשונה

קראו ל-`GET /3/movie/{movie_id}` עם המפתח בכותרת `Authorization` ופסק זמן, ואז בדקו את הסטטוס לפני שמפענחים את הגוף.

```js
const res = await fetch('https://api.amodb.dev/3/movie/550?language=en-US', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
  signal: AbortSignal.timeout(10_000),
});
console.log(res.status, (await res.json()).title);
```

## שלב 4: הוספת קרדיטים ודירוגים

הוספת `credits,ratings` לאותה קריאה מחזירה את רשימת השחקנים, רשימת הצוות וחמשת מקורות הדירוג תחת מפתחות משלהם. זו בקשה אחת לסרט במקום שלוש, והקריאה כולה נספרת פעם אחת מתוך 20, 40 או 120 הבקשות לדקה. הבמאי הוא איש הצוות ש-`job` שלו הוא `Director`. השדות מתוארים בעמודים [קרדיטים](https://amodb.dev/he/credits/) ו[דירוגים](https://amodb.dev/he/ratings/).

ב-`ratings` כל מקור הוא `{ value, votes }` או `null`. מעצב הכרטיס שלמטה מדלג על ערכי null ומחבר את השאר:

```js
const scores = Object.entries(movie.ratings ?? {})
  .filter(([, entry]) => entry !== null)
  .map(([source, entry]) => `${source} ${entry.value}`)
  .join(', ');
```

## שלב 5: טיפול בשגיאות ובמגבלות קצב

כל כשל מתורגם להחלטה:

| סטטוס              | החלטה                                                                |
| ------------------ | -------------------------------------------------------------------- |
| 401                | עצרו את כל הריצה. מפתח שגוי או מושעה מכשיל כל מזהה.                  |
| 404                | דווחו על המזהה והמשיכו לבא.                                          |
| 429                | המתינו `Retry-After` שניות (עד 30) ונסו שוב, עד שני ניסיונות חוזרים. |
| 504                | נסו שוב באותו לוח זמנים, ואז דווחו.                                  |
| פסק זמן או אין רשת | המתינו שנייה כפול מספר הניסיון, ודווחו בכשל השלישי.                  |

הכותרת `X-RateLimit-Remaining` מראה כמה קריאות נותרו בחלון הנוכחי של 60 שניות. רשמו אותה ביומן אם רוצים להאט לפני ש-429 קורה. את כללי החלון ראו בעמוד [מגבלות קצב](https://amodb.dev/he/rate-limits/), ואת הרשימה המלאה בעמוד [שגיאות](https://amodb.dev/he/errors/).

## שלב 6: הוספת מטמון קטן

הפרטים נשמרים בקצה במטמון 6 שעות, ולכן בקשה חוזרת בזמן הזה מחזירה את אותם נתונים. שמרו מטמון בצד שלכם כדי לדלג על הקריאה לגמרי. הסקריפט מחזיק `Map` של רשומות `{ expires, data }`, טוען אותו מ-`.amodb-cache.json` בהתחלה וכותב אותו בחזרה בסוף. הרצה שנייה על אותם מזהים לא שולחת בקשות.

## שלב 7: התוכנית המלאה

שמרו את זה כ-`cards.mjs` והריצו `node cards.mjs 550 603 27205`.

```js
import { readFile, writeFile } from 'node:fs/promises';

const BASE = 'https://api.amodb.dev/3';
const KEY = process.env.AMODB_KEY;
const CACHE_FILE = '.amodb-cache.json';
const DETAILS_TTL_MS = 6 * 60 * 60 * 1000;

if (!KEY) {
  console.error('Set AMODB_KEY first.');
  process.exit(1);
}

class ApiError extends Error {
  constructor(status, message) {
    super(`${status}: ${message}`);
    this.status = status;
  }
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function request(path, params = {}) {
  const url = new URL(BASE + path);
  for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);

  for (let attempt = 1; attempt <= 3; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        headers: { Authorization: `Bearer ${KEY}` },
        signal: AbortSignal.timeout(10_000),
      });
    } catch (err) {
      if (attempt === 3) throw new Error(`network error: ${err.message}`);
      await sleep(1000 * attempt);
      continue;
    }
    if (res.status === 401) {
      throw new ApiError(401, 'the key is invalid or suspended. Check AMODB_KEY.');
    }
    if ((res.status === 429 || res.status === 504) && attempt < 3) {
      const seconds = Math.min(Number(res.headers.get('Retry-After')) || 1, 30);
      await sleep(seconds * 1000);
      continue;
    }
    if (!res.ok) {
      const body = await res.json().catch(() => ({}));
      throw new ApiError(res.status, body.status_message ?? res.statusText);
    }
    return res.json();
  }
}

async function loadCache() {
  try {
    return new Map(Object.entries(JSON.parse(await readFile(CACHE_FILE, 'utf8'))));
  } catch {
    return new Map();
  }
}

async function cachedGet(cache, path, params, ttlMs) {
  const key = `${path}?${new URLSearchParams(params)}`;
  const hit = cache.get(key);
  if (hit && hit.expires > Date.now()) return hit.data;
  const data = await request(path, params);
  cache.set(key, { expires: Date.now() + ttlMs, data });
  return data;
}

function formatCard(movie) {
  const year = (movie.release_date ?? '').slice(0, 4) || 'n/a';
  const director = movie.credits.crew.find((p) => p.job === 'Director')?.name ?? 'unknown';
  const cast = movie.credits.cast.slice(0, 3).map((p) => p.name).join(', ');
  const scores = Object.entries(movie.ratings ?? {})
    .filter(([, entry]) => entry !== null)
    .map(([source, entry]) => `${source} ${entry.value}`)
    .join(', ');
  return [
    `${movie.title} (${year}), ${movie.runtime ?? '?'} min, directed by ${director}`,
    `  Cast: ${cast || 'none listed'}`,
    `  Ratings: ${scores || 'none known'}`,
  ].join('\n');
}

const ids = process.argv.slice(2);
if (ids.length === 0) {
  console.error('Usage: node cards.mjs 550 603 27205');
  process.exit(1);
}

const cache = await loadCache();
let failed = false;
for (const id of ids) {
  try {
    const params = { language: 'en-US', append_to_response: 'credits,ratings' };
    console.log(formatCard(await cachedGet(cache, `/movie/${id}`, params, DETAILS_TTL_MS)));
  } catch (err) {
    console.error(`id ${id}: ${err.message}`);
    failed = true;
    if (err.status === 401) break;
  }
}
await writeFile(CACHE_FILE, JSON.stringify(Object.fromEntries(cache)));
process.exitCode = failed ? 1 : 0;
```

הריצו פעמיים. הריצה הראשונה שולחת בקשה אחת לכל מזהה, ועוד ניסיון חוזר אם חזר 429, וכותבת את `.amodb-cache.json`. הריצה השנייה מדפיסה את אותם כרטיסים ולא שולחת דבר. מחקו את הקובץ כדי לכפות רענון. מזהה שלא קיים מדפיס `404: The resource you requested could not be found.` וקובע את קוד היציאה ל-1, מה שעוזר כשהסקריפט רץ בתוך משימה מתוזמנת.

הלולאה רציפה בכוונה. הרצת כל המזהים בבת אחת הייתה שורפת תקציב לדקה בהתפרצות אחת ומפעילה את נתיב 429 ברובם. אם צריך מהירות, עבדו על מזהים בקבוצות של חמישה ושמרו את לוגיקת הניסיון החוזר ב-`request()`.

## הצעדים הבאים

- הפכו את הלולאה לפונקציה קטנה שמחזירה כרטיסים, ואז קראו לה ממטפל HTTP בשרת שלכם. השאירו את המפתח בשרת.
- החליפו את רשימת המזהים בתוצאות [טרנדים](https://amodb.dev/he/trending-lists/) והדפיסו כרטיס לכל אחד.
- הוסיפו `moods` או `soundtracks` ל-`append_to_response`. מדדי מצב רוח מגיעים כ-`moods.results[]` עם `name`, `avg` ו-`values` בסולם 0 עד 10, ו-`moods.source` הוא `measured` או `estimated`. פסקולים מגיעים כ-`soundtracks.results[]`, ריק כשאין אלבום ידוע. ראו [מדדי מצב רוח](https://amodb.dev/he/moods/) ו[פסקולים](https://amodb.dev/he/soundtracks/).
- השוו את אותה משימה ב-[Python](https://amodb.dev/he/python-movie-api/) או קראו את [המדריך למתחילים](https://amodb.dev/he/quickstart/).

## שאלות ותשובות

### האם הסקריפט צריך חבילות npm?

לא. Node 20 ומעלה כולל fetch, URL ו-AbortSignal.timeout, ולכן לסקריפט אין תלויות. הסיומת .mjs מאפשרת להשתמש בהצהרות import וב-await ברמה העליונה. מריצים אותו עם node cards.mjs ואחריו מזהים.

### אפשר לקרוא ל-amodb ישירות מ-JavaScript בדפדפן?

הימנעו מכך. מפתח בקוד צד לקוח גלוי לכל מבקר ואפשר להעתיק אותו ולנצל אותו. קראו ל-amodb מ-Node או מהשרת שלכם ושלחו לעמוד רק את השדות שהוא צריך. תמונות לא דורשות מפתח וטוענים אותן ישירות.

### כמה מזהים אפשר לעבד בדקה?

בקשה אחת לכל מזהה שאינו במטמון, כי קרדיטים ודירוגים מגיעים דרך append_to_response. מסלול 20 הבקשות לדקה מכסה כ-20 מזהים חדשים בדקה, ומסלולי 40 ו-120 גדלים בהתאם. הניסיון החוזר ב-429 סופג התפרצויות קצרות.

### למה חלק מהדירוגים חסרים בכרטיס?

כל אחד מחמשת מקורות הדירוג הוא אובייקט עם value ו-votes, או null כשהמקור לא ידוע עבור הכותר. הסקריפט משמיט ערכי null, ולכן כותר אחד יכול להציג ארבעה ציונים ואחר אף לא אחד.

### איפה המטמון צריך לחיות בשירות שרץ זמן רב?

השאירו את ה-Map בזיכרון ודלגו על הקובץ. השתמשו ב-TTL של 6 שעות לפרטים כדי להתאים למטמון הקצה, ומחקו רשומות שפג תוקפן בקריאה כדי שה-Map לא יגדל בלי גבול.

## עמודים קשורים

- [API פרטי סרט](https://amodb.dev/he/movies/): API פרטי סרט עם אותם נתיבים ו-JSON כמו TMDB v3. קבלו סרט לפי מזהה, הוסיפו קרדיטים, דירוגים, מצבי רוח ופסקולים עם append_to_response, ב-14 שפות.
- [API קרדיטים](https://amodb.dev/he/credits/): API קרדיטים עם אותם נתיבים ו-JSON כמו TMDB v3. קבלו שחקנים וצוות של סרטים, סדרות, עונות ופרקים, עם credit_id, order, department ו-job ועם guest_stars בפרקים.
- [דירוגי סרטים וסדרות](https://amodb.dev/he/ratings/): API דירוגי סרטים וסדרות שמחזיר ציוני IMDb, Kinopoisk, Metacritic, Rotten Tomatoes ו-TMDB לכותר בקריאה אחת, עם value ו-votes ו-null לכל מקור שאינו ידוע.
- [אימות](https://amodb.dev/he/authentication/): איך שולחים את מפתח amodb: api_key בשאילתה, כותרת Authorization Bearer או apikey בקריאות OMDb. פורמט המפתח, שמירה, החלפה ושגיאות 401 שכדאי להכיר.
- [מגבלות קצב](https://amodb.dev/he/rate-limits/): amodb מגביל כל מפתח לפי בקשות לדקה בחלון נע של 60 שניות: 20, 40 או 120 בהתאם למסלול שלכם. קראו את כותרות מגבלת הקצב וטפלו ב-429 עם Retry-After.
- [מדריך API לסרטים ב-Python](https://amodb.dev/he/python-movie-api/): בנו כלי שורת פקודה ב-Python שמחפש כותר סרט ומדפיס שנה, שחקנים, דירוג IMDb ופוסטר, עם ניסיון חוזר ב-429 ומטמון קבצים. משתמש ב-requests, Python 3.10 ומעלה.

עודכן: 2026-10-18. Source: https://amodb.dev/he/javascript-movie-api/
