API
Ook zoeken is een statische index: zo'n 7.000 kleine bestanden die samen elke naam van elke serie, film en persoon in elke taal dekken. Een zoekopdracht heeft er maar één nodig.
Bestanden
https://api.watchittrackit.com/v1/search/manifest.jsonhttps://api.watchittrackit.com/v1/search/{build}/tree.jsonhttps://api.watchittrackit.com/v1/search/{build}/l/{hex}.jsonhttps://api.watchittrackit.com/v1/search/{build}/s/{hex}.jsonhttps://api.watchittrackit.com/v1/search/recent.json
Een zoekopdracht opzoeken
- Lees
v1/search/manifest.jsonvoor het id van de huidige build. - Lees
v1/search/{build}/tree.json. Dit bestand verandert nooit binnen een build, dus haal het maar één keer op. - Normaliseer de zoekopdracht: zet hem in kleine letters en verwijder accenten en leestekens. Zoek hem op via het langste woord dat geen stopwoord is.
- Doorloop de boom met dat woord om het bijbehorende bestand te vinden: een bucket in
l/of, als het woord precies op een splitsingsprefix eindigt, een samenvatting ins/. Bestandsnamen zijn de hexadecimale weergave van de UTF-8-bytes van de prefix. - Houd items over waarbij elk woord van de zoekopdracht het begin is van een woord in de naam. Zet namen die met de volledige zoekopdracht beginnen bovenaan en sorteer daarna op populariteit.
- Pas
v1/search/recent.jsontoe: dit bestand bevat de records die sinds de build zijn gewijzigd. Laat de items van de index voor die records vallen en gebruik in plaats daarvan deze. Het verandert zodra records worden bewerkt, dus voeg een querystring toe die om de paar minuten verandert (?w=plus het huidige venster van 5 minuten) om een verse kopie te krijgen.
Items
Elk item is een array:
| Positie | Type | Beschrijving |
|---|---|---|
| 0 | string | Recordtype. |
| 1 | number | Record-id. |
| 2 | string | De naam die overeenkwam, in welke taal dan ook. |
| 3 | number | Rang binnen het type: 1.000.000 is het populairst. |
| 4 | string | Record-slug. |
| 5 | number | null | Jaar, bij titels. |
| 6 | string | null | Pad naar de miniatuur. |
Voorbeeld (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"))De index wordt dagelijks opnieuw opgebouwd. De bestanden van een build veranderen nooit, dus je kunt ze zo lang cachen als je wilt; alleen het manifest en recent.json veranderen.