← كل الأدلة

ابنِ جدول ترتيب البطولة الحي

اعرض بطولة السائقين في MotoGP أو Moto2 أو Moto3 على موقعك عبر وسيط صغير بتخزين مؤقت يُبقي مفتاح RapidAPI بعيدًا عن المتصفح.

10 دقائق قراءة

جدول الترتيب هو أكثر أدوات MotoGP شيوعًا في مواقع الأخبار ومدونات المشجعين وصفحات الأندية. يبني هذا الدليل جدولًا في جزأين:

  1. وسيط (proxy): نقطة نهاية صغيرة على الخادم تحمل مفتاحك وتخزّن الاستجابات مؤقتًا.
  2. أداة العرض: HTML وJavaScript عاديان يعرضان الجدول من الوسيط.

1. الوسيط

يجب ألا يرى المتصفح مفتاح RapidAPI أبدًا، لذلك تتحدث الصفحة مع نقطة النهاية الخاصة بك. يستخدم هذا المثال Node.js 18+ وExpress؛ وتعمل الأسطر الثلاثون نفسها كدالة على Netlify أو Vercel أو Cloudflare.

// server.js — npm install express
import express from "express";

const HOST = "motogp-api-championship-data-statistics.p.rapidapi.com";
const KEY = process.env.RAPIDAPI_KEY;
if (!KEY) throw new Error("RAPIDAPI_KEY is not set");

const CATEGORIES = new Set(["MOTOGP", "MOTO2", "MOTO3"]);
const TTL_MS = 60 * 60 * 1000; // 1 hour
const cache = new Map();

async function cached(path) {
  const hit = cache.get(path);
  if (hit && Date.now() - hit.at < TTL_MS) return hit.body;
  const res = await fetch(`https://${HOST}${path}`, {
    headers: { "X-RapidAPI-Key": KEY, "X-RapidAPI-Host": HOST },
  });
  if (!res.ok) throw new Error(`Upstream ${res.status}`);
  const body = await res.json();
  cache.set(path, { at: Date.now(), body });
  return body;
}

const app = express();

app.get("/api/standings", async (req, res) => {
  const category = String(req.query.category ?? "MOTOGP").toUpperCase();
  if (!CATEGORIES.has(category)) return res.status(400).json({ error: "Unknown category" });
  try {
    const { data: season } = await cached("/seasons/latest");
    const { data } = await cached(`/standings/seasons/${season.year}/riders?category=${category}`);
    res.set("Cache-Control", "public, max-age=300").json({ season: season.year, category, rows: data });
  } catch (err) {
    res.status(502).json({ error: "Standings are unavailable right now" });
  }
});

app.use(express.static("public"));
app.listen(3000, () => console.log("http://localhost:3000"));

هناك تفصيلان مهمان:

  • تحقق من المدخلات. تُمرَّر الفئات الثلاث المعروفة فقط، فلا يستطيع أحد استخدام وسيطك لاستهلاك حصتك على نقاط نهاية عشوائية.
  • خزّن مؤقتًا. استدعاء واحد إلى الخدمة الأصلية في الساعة لكل فئة يخدم جميع الزوار.

2. أداة العرض

<!-- public/index.html -->
<div class="standings">
  <div role="tablist">
    <button data-category="MOTOGP" aria-selected="true">MotoGP</button>
    <button data-category="MOTO2">Moto2</button>
    <button data-category="MOTO3">Moto3</button>
  </div>
  <table>
    <caption></caption>
    <thead>
      <tr><th>Pos</th><th>Rider</th><th>Team</th><th>Wins</th><th>Pts</th></tr>
    </thead>
    <tbody></tbody>
  </table>
</div>

<script type="module">
  const tbody = document.querySelector(".standings tbody");
  const caption = document.querySelector(".standings caption");

  function cell(text) {
    const td = document.createElement("td");
    td.textContent = text; // textContent, never innerHTML, for data from an API
    return td;
  }

  async function load(category) {
    caption.textContent = "Loading…";
    const res = await fetch(`/api/standings?category=${category}`);
    if (!res.ok) {
      caption.textContent = "Standings are unavailable right now.";
      tbody.replaceChildren();
      return;
    }
    const { season, rows } = await res.json();
    caption.textContent = `${season} ${category} riders' championship`;
    tbody.replaceChildren(
      ...rows.map((r) => {
        const tr = document.createElement("tr");
        tr.append(
          cell(r.position),
          cell(`#${r.rider_number} ${r.rider_name}`),
          cell(r.team_name),
          cell(r.wins),
          cell(r.total_points),
        );
        return tr;
      }),
    );
  }

  document.querySelectorAll("[data-category]").forEach((button) => {
    button.addEventListener("click", () => {
      document.querySelectorAll("[data-category]").forEach((b) => b.setAttribute("aria-selected", b === button));
      load(button.dataset.category);
    });
  });

  load("MOTOGP");
</script>

شغّل RAPIDAPI_KEY=... node server.js ثم افتح http://localhost:3000.

تنويعات

  • الفارق عن المتصدر. أضف عمودًا بالقيمة rows[0].total_points - r.total_points.
  • بطولة الفرق. وجّه مسارًا ثانيًا إلى /standings/seasons/{year}/teams؛ تحمل الصفوف team_name وtotal_points.
  • المصنّعون. يعيد /standings/seasons/{year}/manufacturers صفوفًا فيها manufacturer_name.
  • المواقع الثابتة. اجلب الترتيب في خطوة البناء (Astro أو Next.js أو Hugo) وأعد البناء بعد كل نهاية أسبوع سباق، فلا حاجة إلى وسيط إطلاقًا.
الدليل التالي → صفحات ملفات السائقين مع الإحصاءات المهنية