API
La recherche est elle aussi un index statique : environ 7 000 petits fichiers qui couvrent ensemble tous les noms de toutes les séries, de tous les films et de toutes les personnes, dans toutes les langues. Une requête n'a besoin que de l'un d'entre eux.
Fichiers
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
Traiter une requête
- Lisez
v1/search/manifest.jsonpour obtenir l'identifiant du build actuel. - Lisez
v1/search/{build}/tree.json. Il ne change jamais pour un même build : récupérez-le une seule fois. - Normalisez la requête : mettez-la en minuscules, supprimez les accents et la ponctuation. Recherchez-la par son mot le plus long qui n'est pas un mot vide.
- Parcourez l'arbre avec ce mot pour trouver son fichier : un bucket dans
l/ou, si le mot se termine exactement sur un préfixe de découpage, un résumé danss/. Les noms de fichiers sont la représentation hexadécimale des octets UTF-8 du préfixe. - Conservez les entrées dont le nom contient, pour chaque mot de la requête, un mot qui commence par celui-ci. Placez en premier les noms qui commencent par la requête entière, puis triez par popularité.
- Appliquez
v1/search/recent.json: il liste les enregistrements modifiés depuis le build. Supprimez les entrées de l'index pour ces enregistrements et utilisez celles-ci à la place. Ce fichier change à mesure que les enregistrements sont modifiés : ajoutez une chaîne de requête qui change toutes les quelques minutes (?w=suivi de la fenêtre de 5 minutes en cours) pour obtenir une copie à jour.
Entrées
Chaque entrée est un tableau :
| Position | Type | Description |
|---|---|---|
| 0 | string | Type d'enregistrement. |
| 1 | number | Identifiant de l'enregistrement. |
| 2 | string | Le nom correspondant, quelle que soit sa langue. |
| 3 | number | Rang au sein de son type : 1 000 000 est le plus populaire. |
| 4 | string | Slug de l'enregistrement. |
| 5 | number | null | Année, pour les titres. |
| 6 | string | null | Chemin de la miniature. |
Exemple (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"))L'index est reconstruit chaque jour. Les fichiers d'un build ne changent jamais : vous pouvez les mettre en cache aussi longtemps que vous le souhaitez ; seuls le manifeste et recent.json changent.