Watch It? Track It.

Ka otsing on staatiline indeks: umbes 7000 väikest faili, mis katavad koos iga sarja, filmi ja inimese iga nime igas keeles. Päringuks on vaja ainult ühte neist.

Failid

  • 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

Päringu otsimine

  1. Loe v1/search/manifest.json, et saada praeguse ehituse id.
  2. Loe v1/search/{build}/tree.json. See ei muutu ühe ehituse jooksul kunagi, seega laadi see alla üks kord.
  3. Normaliseeri päring: teisenda see väiketähtedeks, eemalda diakriitikud ja kirjavahemärgid. Otsi selle pikima sõna järgi, mis pole stoppsõna.
  4. Läbi selle sõnaga puu, et leida selle fail: ämber kaustas l/ või, kui sõna lõpeb täpselt jaotusprefiksil, kokkuvõte kaustas s/. Failinimed on prefiksi UTF-8 baitide kuueteistkümnendkuju.
  5. Jäta alles kirjed, kus iga päringusõna on mõne nimesõna algus. Pane ettepoole nimed, mis algavad kogu päringuga, ja seejärel sordi populaarsuse järgi.
  6. Rakenda v1/search/recent.json: selles on kirjed, mida on pärast ehitust muudetud. Eemalda nende kirjete indeksikanded ja kasuta nende asemel neid. See muutub kirjete muutmisel, seega lisa värske koopia saamiseks päringustring, mis muutub iga paari minuti järel (?w= pluss praegune 5-minutiline aken).

Kirjed

Iga kirje on massiiv:

PositsioonTüüpKirjeldus
0stringKirje tüüp.
1numberKirje id.
2stringSobinud nimi, olenemata selle keelest.
3numberKoht tüübi piires: 1 000 000 on kõige populaarsem.
4stringKirje slug.
5number | nullAasta, pealkirjade puhul.
6string | nullPisipildi tee.

Näide (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"))

Indeks ehitatakse uuesti iga päev. Ehituse failid ei muutu kunagi, seega võid neid vahemällu salvestada nii kauaks, kui soovid; muutuvad ainult manifest ja recent.json.