API
גם החיפוש הוא אינדקס סטטי: כ-7,000 קבצים קטנים שיחד מכסים כל שם של כל סדרה, סרט ואדם, בכל שפה. שאילתה צריכה רק אחד מהם.
קבצים
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
חיפוש שאילתה
- קראו את
v1/search/manifest.jsonכדי לקבל את מזהה ה-build הנוכחי. - קראו את
v1/search/{build}/tree.json. הוא לעולם אינו משתנה בתוך build, אז הביאו אותו פעם אחת. - נרמלו את השאילתה: המירו לאותיות קטנות, הסירו סימני הטעמה ופיסוק. חפשו לפי המילה הארוכה ביותר בה שאינה מילת עצירה.
- עברו על העץ עם המילה הזו כדי למצוא את הקובץ שלה: bucket ב-
l/או, אם המילה מסתיימת בדיוק בקידומת פיצול, סיכום ב-s/. שמות הקבצים הם הייצוג ההקסדצימלי של בתי ה-UTF-8 של הקידומת. - השאירו רשומות שבהן כל מילה בשאילתה היא תחילתה של מילה כלשהי בשם. הציבו ראשונים שמות שמתחילים בשאילתה כולה, ואז מיינו לפי פופולריות.
- החילו את
v1/search/recent.json: הוא מפרט רשומות שהשתנו מאז ה-build. השמיטו את רשומות האינדקס של אותן רשומות והשתמשו באלה במקומן. הוא משתנה כשרשומות נערכות, אז הוסיפו מחרוזת שאילתה שמשתנה כל כמה דקות (?w=ואחריו חלון 5 הדקות הנוכחי) כדי לקבל עותק עדכני.
רשומות
כל רשומה היא מערך:
| מיקום | סוג | תיאור |
|---|---|---|
| 0 | string | סוג הרשומה. |
| 1 | number | מזהה הרשומה. |
| 2 | string | השם שהתאים, בכל שפה שהיא. |
| 3 | number | דירוג בתוך הסוג: 1,000,000 הוא הפופולרי ביותר. |
| 4 | string | ה-slug של הרשומה. |
| 5 | number | null | שנה, ליצירות. |
| 6 | string | null | נתיב התמונה הממוזערת. |
דוגמה (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"))האינדקס נבנה מחדש מדי יום. הקבצים של build לעולם אינם משתנים, אז אפשר לשמור אותם במטמון כמה שרוצים; רק ה-manifest ו-recent.json משתנים.