Watch It? Track It.

Søket er også en statisk indeks: rundt 7 000 små filer som til sammen dekker alle navn på alle serier, filmer og personer, på alle språk. Et søk trenger bare én av dem.

Filer

  • 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

Slå opp et søk

  1. Les v1/search/manifest.json for å få id-en til gjeldende bygg.
  2. Les v1/search/{build}/tree.json. Den endres aldri innenfor et bygg, så hent den én gang.
  3. Normaliser søket: gjør det om til små bokstaver og fjern aksenter og tegnsetting. Slå det opp med det lengste ordet som ikke er et stoppord.
  4. Gå gjennom treet med dette ordet for å finne filen: en bucket i l/ eller, hvis ordet slutter nøyaktig på et delingsprefiks, et sammendrag i s/. Filnavnene er prefiksets UTF-8-byte i heksadesimal form.
  5. Behold oppføringene der hvert ord i søket innleder et ord i navnet. Sett navn som begynner med hele søket først, og sorter deretter etter popularitet.
  6. Bruk v1/search/recent.json: den viser poster som er endret siden bygget. Fjern indeksens oppføringer for disse postene og bruk disse i stedet. Filen endres når poster redigeres, så legg til en query string som endres med noen minutters mellomrom (?w= pluss gjeldende 5-minuttersvindu) for å få en fersk kopi.

Oppføringer

Hver oppføring er en array:

PosisjonTypeBeskrivelse
0stringPosttype.
1numberPostens id.
2stringNavnet som ga treff, på det språket det er.
3numberRangering innenfor typen: 1 000 000 er mest populær.
4stringPostens slug.
5number | nullÅr, for titler.
6string | nullSti til miniatyrbildet.

Eksempel (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"))

Indeksen bygges på nytt daglig. Filene i et bygg endres aldri, så du kan mellomlagre dem så lenge du vil; bare manifestet og recent.json endres.