Watch It? Track It.

جستجو هم یک نمایه ایستاست: حدود 7,000 فایل کوچک که روی هم همه نام‌های هر سریال، فیلم و فرد را به همه زبان‌ها پوشش می‌دهند. هر پرس‌وجو فقط به یکی از آن‌ها نیاز دارد.

فایل‌ها

  • https://api.watchittrackit.com/v1/search/manifest.json
  • https://api.watchittrackit.com/v1/search/{build}/tree.json
  • https://api.watchittrackit.com/v1/search/{build}/l/{hex}.json
  • https://api.watchittrackit.com/v1/search/{build}/s/{hex}.json
  • https://api.watchittrackit.com/v1/search/recent.json

جستجوی یک پرس‌وجو

  1. v1/search/manifest.json را بخوانید تا شناسه build فعلی را بگیرید.
  2. v1/search/{build}/tree.json را بخوانید. این فایل در یک build هرگز تغییر نمی‌کند، پس یک بار دریافتش کنید.
  3. پرس‌وجو را نرمال کنید: آن را به حروف کوچک تبدیل کنید و اعراب و نشانه‌های نگارشی را حذف کنید. آن را با بلندترین واژه‌اش که ایست‌واژه نیست جستجو کنید.
  4. با آن واژه درخت را پیمایش کنید تا فایلش را پیدا کنید: یک bucket در l/ یا، اگر واژه دقیقاً روی یک پیشوند تقسیم تمام شود، یک خلاصه در s/. نام فایل‌ها هگزادسیمال بایت‌های UTF-8 پیشوند است.
  5. مواردی را نگه دارید که هر واژه پرس‌وجو آغاز یکی از واژه‌های نام باشد. نام‌هایی را که با کل پرس‌وجو شروع می‌شوند اول بگذارید، سپس بر اساس محبوبیت مرتب کنید.
  6. v1/search/recent.json را اعمال کنید: این فایل رکوردهایی را که از زمان build تغییر کرده‌اند فهرست می‌کند. موارد نمایه برای آن رکوردها را کنار بگذارید و به‌جایشان از این‌ها استفاده کنید. این فایل با ویرایش رکوردها تغییر می‌کند، پس برای گرفتن نسخه تازه یک رشته پرس‌وجو اضافه کنید که هر چند دقیقه تغییر کند (?w= به‌علاوه بازه 5 دقیقه‌ای فعلی).

موارد

هر مورد یک آرایه است:

جایگاهنوعتوضیح
0stringنوع رکورد.
1numberشناسه رکورد.
2stringنامی که مطابقت داشت، به هر زبانی که باشد.
3numberرتبه در نوع خود: 1,000,000 محبوب‌ترین است.
4stringslug رکورد.
5number | nullسال، برای آثار.
6string | nullمسیر تصویر بندانگشتی.

نمونه (JavaScript)

const BASE = "https://api.watchittrackit.com/v1/search"
const STOPWORDS = new Set(["a", "an", "and", "the", "of", "in", "on", "to", "for", "at", "by", "with", "or",
  "de", "del", "la", "le", "les", "el", "los", "las", "lo", "un", "une", "et", "du", "des", "da", "do", "dos",
  "der", "die", "das", "und", "ein", "eine", "den", "dem", "von", "zu", "il", "i", "e", "di", "y", "en", "het", "een", "van"])

const normalize = (s) => s.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase()
  .replace(/[^\p{L}\p{N}]+/gu, " ").trim()
const hex = (s) => [...new TextEncoder().encode(s)].map((b) => b.toString(16).padStart(2, "0")).join("")

async function search(query) {
  const { build } = await fetch(`${BASE}/manifest.json`).then((r) => r.json())
  const tree = await fetch(`${BASE}/${build}/tree.json`).then((r) => r.json())

  const q = normalize(query)
  const words = q.split(" ").filter(Boolean)
  const real = words.filter((w) => !STOPWORDS.has(w))
  const word = (real.length ? real : words).sort((a, b) => [...b].length - [...a].length)[0]

  // Walk the tree: descend while the prefix is split; stop at a bucket or a summary.
  let key = "", file
  for (const c of word) {
    if (tree[key + c]) { key += c; continue }
    const starts = tree[key] ?? []
    const start = starts.filter((s) => s <= c).pop() ?? starts[0] ?? c
    file = `l/${hex(key + start)}.json`
    break
  }
  file ??= `s/${hex(key)}.json`

  const res = await fetch(`${BASE}/${build}/${file}`)
  let entries = res.ok ? await res.json() : []

  // Records changed since the build replace the index's entries for them.
  // The query string changes every 5 minutes, so caches don't serve an old copy.
  const recent = await fetch(`${BASE}/recent.json?w=${Math.floor(Date.now() / 300000)}`).then((r) => r.json())
  entries = entries.filter(([type, id]) => !(`${type}:${id}` in recent.records))
  entries.push(...Object.values(recent.records).flat())

  const seen = new Set()
  return entries
    .filter((e) => { const n = normalize(e[2]).split(" "); return words.every((w) => n.some((x) => x.startsWith(w))) })
    .sort((a, b) => Number(normalize(b[2]).startsWith(q)) - Number(normalize(a[2]).startsWith(q)) || b[3] - a[3])
    .filter(([type, id]) => !seen.has(`${type}:${id}`) && seen.add(`${type}:${id}`))
    .slice(0, 30)
}

// [["tv", 81189, "Breaking Bad", 999998, "breaking-bad", 2008, "/images/tv/81189/….t.jpg"], …]
console.log(await search("breaking bad"))

نمایه هر روز از نو ساخته می‌شود. فایل‌های یک build هرگز تغییر نمی‌کنند، پس هر قدر که بخواهید آن‌ها را کش کنید؛ فقط manifest و recent.json تغییر می‌کنند.