API
Auch die Suche ist ein statischer Index: etwa 7.000 kleine Dateien, die zusammen jeden Namen jeder Serie, jedes Films und jeder Person in jeder Sprache abdecken. Eine Suchanfrage braucht nur eine davon.
Dateien
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
Eine Suchanfrage nachschlagen
- Lies
v1/search/manifest.json, um die aktuelle Build-ID zu erhalten. - Lies
v1/search/{build}/tree.json. Die Datei ändert sich innerhalb eines Builds nie, also lade sie nur einmal. - Normalisiere die Suchanfrage: in Kleinbuchstaben umwandeln, Akzente und Satzzeichen entfernen. Schlage sie über ihr längstes Wort nach, das kein Stoppwort ist.
- Durchlaufe den Baum mit diesem Wort, um seine Datei zu finden: einen Bucket in
l/oder, wenn das Wort genau an einem Teilungspräfix endet, eine Zusammenfassung ins/. Dateinamen sind die Hex-Darstellung der UTF-8-Bytes des Präfixes. - Behalte Einträge, bei denen jedes Wort der Suchanfrage den Anfang eines Wortes im Namen bildet. Stelle Namen, die mit der ganzen Suchanfrage beginnen, an den Anfang und sortiere dann nach Beliebtheit.
- Wende
v1/search/recent.jsonan: Sie listet Datensätze auf, die sich seit dem Build geändert haben. Verwirf die Einträge des Index für diese Datensätze und verwende stattdessen diese. Die Datei ändert sich, sobald Datensätze bearbeitet werden, also hänge einen Query-String an, der sich alle paar Minuten ändert (?w=plus das aktuelle 5-Minuten-Fenster), um eine frische Kopie zu erhalten.
Einträge
Jeder Eintrag ist ein Array:
| Position | Typ | Beschreibung |
|---|---|---|
| 0 | string | Datensatztyp. |
| 1 | number | Datensatz-ID. |
| 2 | string | Der passende Name, in welcher Sprache auch immer. |
| 3 | number | Rang innerhalb des Typs: 1.000.000 ist der beliebteste. |
| 4 | string | Datensatz-Slug. |
| 5 | number | null | Jahr, bei Titeln. |
| 6 | string | null | Pfad zum Vorschaubild. |
Beispiel (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"))Der Index wird täglich neu erstellt. Die Dateien eines Builds ändern sich nie, du kannst sie also beliebig lange cachen; nur das Manifest und recent.json ändern sich.