PageSourceSearch

https://theblackholebay.lol/api

html theblackholebay.lol collected 2026-10-09 01:12:29 UTC 52,137 bytes, 830 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>API Reference - The Black Hole Bay</title>
7    <meta name="description" content="Full API reference for The Black Hole Bay - torrent search, movies, TV, games, and more.">
8    <link rel="icon" type="image/jpeg" href="https://theblackholebay.lol/images/icon-haloween.jpg">
9    <style>
10        :root{--bg:#050505;--bg2:#0a0a0a;--card:#0d0d0d;--border:#1a1a1a;--border2:#2a2a2a;--text:#e0e0e0;--text2:#888;--text3:#555;--orange:#ff6b35;--green:#00d27a;--blue:#3b82f6;--code-bg:#020202;--sidebar-w:260px;}
11        *{box-sizing:border-box;margin:0;padding:0;}
12        body{background:var(--bg);color:var(--text);font-family:'Segoe UI',-apple-system,sans-serif;line-height:1.6;background-image:url('https://www.transparenttextures.com/patterns/stardust.png');background-attachment:fixed;}
13        a{color:var(--orange);text-decoration:none;transition:color .15s;}
14        a:hover{text-decoration:underline;}
15        ::selection{background:var(--orange);color:#fff;}
16        ::-webkit-scrollbar{width:8px;height:8px;}
17        ::-webkit-scrollbar-track{background:var(--bg2);}
18        ::-webkit-scrollbar-thumb{background:var(--border2);border-radius:4px;}
19        ::-webkit-scrollbar-thumb:hover{background:var(--text3);}
20        code,pre{font-family:'SF Mono','Fira Code','Courier New',monospace;font-size:13px;}
21
22        .nav{position:sticky;top:0;z-index:200;background:rgba(5,5,5,.92);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:10px 20px;}
23        .nav-brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:15px;color:var(--text);}
24        .nav-brand img{width:24px;height:24px;border-radius:6px;}
25        .nav-links{display:flex;gap:20px;align-items:center;}
26        .nav-links a{color:var(--text2);font-size:14px;font-weight:600;}
27        .nav-links a:hover{color:var(--orange);text-decoration:none;}
28        .menu-btn{display:none;background:var(--card);border:1px solid var(--border2);color:var(--text);padding:6px 10px;border-radius:6px;cursor:pointer;font-size:14px;}
29
30        .layout{display:flex;max-width:1100px;margin:0 auto;padding:20px;gap:24px;}
31
32        /* SIDEBAR */
33        .sidebar{width:var(--sidebar-w);flex-shrink:0;position:sticky;top:60px;align-self:flex-start;max-height:calc(100vh - 80px);overflow-y:auto;padding-right:8px;}
34        .sidebar-group{margin-bottom:24px;}
35        .sidebar-h{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text3);margin-bottom:8px;padding-left:12px;}
36        .sidebar a{display:block;color:var(--text2);font-size:14px;padding:5px 12px;border-radius:6px;transition:all .12s;border-left:2px solid transparent;}
37        .sidebar a:hover{color:var(--text);background:rgba(255,255,255,.03);text-decoration:none;border-left-color:var(--border2);}
38        .sidebar a.active{color:var(--orange);background:rgba(255,107,53,.06);border-left-color:var(--orange);font-weight:600;}
39
40        /* MAIN */
41        .main{flex:1;min-width:0;}
42
43        /* HERO */
44        .api-hero{text-align:center;margin-bottom:40px;padding:30px 0;}
45        .api-hero img{width:56px;height:56px;border-radius:12px;margin-bottom:10px;}
46        .api-hero h1{font-size:28px;margin-bottom:6px;}
47        .api-hero p{color:var(--text2);font-size:15px;}
48
49        /* SECTIONS */
50        .section{margin-bottom:48px;scroll-margin-top:70px;}
51        .section-h{font-size:22px;font-weight:700;margin-bottom:8px;display:flex;align-items:center;gap:8px;padding-bottom:8px;border-bottom:1px solid var(--border);}
52        .section-h .bar{width:4px;height:22px;background:var(--orange);border-radius:2px;}
53        .section-desc{color:var(--text2);font-size:14px;margin-bottom:20px;}
54
55        /* ENDPOINT CARD */
56        .ep{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:16px;scroll-margin-top:70px;transition:border-color .2s;}
57        .ep:hover{border-color:var(--border2);}
58        .ep-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;flex-wrap:wrap;}
59        .method{font-size:11px;font-weight:800;padding:3px 10px;border-radius:5px;letter-spacing:.5px;}
60        .get{background:rgba(0,210,122,.12);color:var(--green);border:1px solid rgba(0,210,122,.25);}
61        .post{background:rgba(59,130,246,.12);color:var(--blue);border:1px solid rgba(59,130,246,.25);}
62        .path{font-family:monospace;font-size:14px;color:var(--text);word-break:break-all;}
63        .ep h3{font-size:17px;margin-bottom:6px;font-weight:600;}
64        .ep>p{color:var(--text2);font-size:15px;margin-bottom:12px;}
65
66        /* TABLES */
67        .tbl{width:100%;border-collapse:collapse;margin:10px 0;font-size:14px;}
68        .tbl th{text-align:left;color:var(--text2);font-size:11px;text-transform:uppercase;letter-spacing:.5px;padding:7px 10px;border-bottom:1px solid var(--border2);font-weight:700;}
69        .tbl td{padding:7px 10px;border-bottom:1px solid var(--border);color:var(--text2);font-size:14px;vertical-align:top;}
70        .tbl td:first-child{color:var(--text);font-family:monospace;font-size:13px;}
71        .tbl td:nth-child(2){font-family:monospace;font-size:13px;}
72        .req{color:var(--orange)!important;font-weight:700;}
73        .opt{color:var(--text3)!important;}
74
75        /* CODE BLOCKS */
76        .code-block{background:var(--code-bg);border:1px solid var(--border);border-radius:10px;overflow:hidden;margin:12px 0;}
77        .code-head{background:var(--bg2);border-bottom:1px solid var(--border);padding:7px 12px;display:flex;align-items:center;justify-content:space-between;}
78        .code-lang{font-size:11px;color:var(--text3);font-family:monospace;text-transform:uppercase;letter-spacing:.5px;}
79        .copy-btn{background:transparent;border:1px solid var(--border2);color:var(--text2);font-size:12px;padding:3px 10px;border-radius:5px;cursor:pointer;transition:all .15s;}
80        .copy-btn:hover{background:var(--card);color:var(--orange);border-color:var(--orange);}
81        .copy-btn.copied{color:var(--green);border-color:var(--green);}
82        .code-body{padding:14px;overflow-x:auto;}
83        .code-body code{color:#c8c8c8;line-height:1.5;}
84        .code-body .k{color:var(--orange);}
85        .code-body .s{color:var(--green);}
86        .code-body .n{color:var(--blue);}
87
88        /* NOTE */
89        .note{background:rgba(59,130,246,.06);border:1px solid rgba(59,130,246,.2);border-radius:8px;padding:12px 16px;margin:12px 0;font-size:14px;color:var(--text2);}
90        .note b{color:var(--blue);}
91        .warn{background:rgba(255,107,53,.06);border:1px solid rgba(255,107,53,.2);border-radius:8px;padding:12px 16px;margin:12px 0;font-size:14px;color:var(--text2);}
92        .warn b{color:var(--orange);}
93
94        .footer{text-align:center;padding:30px 20px;color:var(--text3);font-size:13px;border-top:1px solid var(--border);margin-top:40px;}
95
96        /* MOBILE SIDEBAR */
97        .sidebar-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:150;}
98        .sidebar-overlay.show{display:block;}
99        body.sidebar-open .sidebar{position:fixed;top:0;left:0;bottom:0;max-height:100vh;z-index:160;width:280px;background:var(--bg2);padding:20px;border-right:1px solid var(--border2);overflow-y:auto;transform:none;}
100        body.sidebar-open .sidebar-overlay{display:block;}
101
102        @media(max-width:768px){
103            .menu-btn{display:block;}
104            .sidebar{display:none;}
105            .layout{padding:14px;gap:0;}
106            .api-hero h1{font-size:22px;}
107            .section-h{font-size:18px;}
108            .ep{padding:14px;}
109            .tbl{font-size:12px;}
110            .nav-links{gap:12px;}
111        }
112    </style>
113<link rel="stylesheet" href="/css/site-layout.css"></head>
114<body>
115    <nav class="nav">
116        <a href="/" class="nav-brand"><img src="https://theblackholebay.lol/images/icon-haloween.jpg" alt="Logo"> The Black Hole Bay</a>
117        <div class="nav-links"><a href="/">Docs</a><a href="/api">API</a><a href="https://theblackholebay.lol/">Main Site</a></div>
118    </nav>
119
120    <div class="layout">
121        <!-- SIDEBAR -->
122        <div class="sidebar-overlay" onclick="document.body.classList.remove('sidebar-open')"></div>
123        <aside class="sidebar" id="sidebar">
124            <div class="sidebar-group">
125                <div class="sidebar-h">Getting Started</div>
126                <a href="#base-url">Base URL</a>
127                <a href="#auth">Authentication</a>
128                <a href="#rate-limit">Rate Limiting</a>
129                <a href="#response-format">Response Format</a>
130            </div>
131            <div class="sidebar-group">
132                <div class="sidebar-h">Torrents</div>
133                <a href="#search">Search Torrents</a>
134                <a href="#torrent-details">Torrent Details</a>
135                <a href="#torrent-files">Torrent Files</a>
136                <a href="#track-download">Track Download</a>
137            </div>
138            <div class="sidebar-group">
139                <div class="sidebar-h">User Accounts</div>
140                <a href="#user-register">Register</a>
141                <a href="#user-login">Login</a>
142                <a href="#user-me">Get Current User</a>
143                <a href="#user-profile">Get Public Profile</a>
144                <a href="#user-upload">Upload Torrent</a>
145                <a href="#user-rate">Rate User</a>
146            </div>
147            <div class="sidebar-group">
148                <div class="sidebar-h">Movies &amp; TV</div>
149                <a href="#trending-movies">Trending Movies</a>
150                    <a href="#movie-lists">Movie Lists</a>
151                    <a href="#tv-lists">TV Lists</a>
152                    <a href="#discover">Discover</a>
153                    <a href="#tmdb-search">TMDB Search</a>
154                    <a href="#tv-season">Season Episodes</a>
155                    <a href="#suggestions-endpoint">Suggestions</a>
156                    <a href="#applications-endpoint">Applications</a>
157                    <a href="#music-artwork">Music Artwork</a>
158                    <a href="#music-import">Playlist Import</a>
159                <a href="#trending-tv">Trending TV</a>
160                <a href="#now-playing">Now Playing</a>
161                <a href="#popular-tv">Popular TV</a>
162                <a href="#movie-details">Movie Details</a>
163                <a href="#tv-details">TV Details</a>
164            </div>
165            <div class="sidebar-group">
166                <div class="sidebar-h">Games</div>
167                <a href="#trending-games">Trending Games</a>
168                <a href="#desktop-updates">Desktop App Updates</a>
169            </div>
170            <div class="sidebar-group">
171                <div class="sidebar-h">Reference</div>
172                <a href="#images">Images</a>
173                <a href="#errors">Errors</a>
174                <a href="#code-examples">Code Examples</a>
175                <a href="#best-practices">Best Practices</a>
176            </div>
177        </aside>
178
179        <!-- MAIN -->
180        <main class="main">
181            <div class="api-hero">
182                <img src="https://theblackholebay.lol/images/icon-haloween.jpg" alt="The Black Hole Bay">
183                <h1>API Reference</h1>
184                <p>Full endpoint documentation with parameters, responses, and code examples</p>
185            </div>
186
187            <!-- GETTING STARTED -->
188            <div class="section" id="base-url">
189                <div class="section-h"><span class="bar"></span> Getting Started</div>
190                <p class="section-desc">Everything you need to start using the API.</p>
191                <div class="ep">
192                    <h3>Base URL</h3>
193                    <p>All API requests should be made to:</p>
194                    <div class="code-block"><div class="code-head"><span class="code-lang">text</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>https://theblackholebay.lol</code></div></div>
195                    <div class="note"><b>Note:</b> All endpoints are prefixed with <code>/api</code>. Example: <code>https://theblackholebay.lol/api/search?q=dune</code></div>
196                </div>
197                <div class="ep" id="auth">
198                    <h3>Authentication</h3>
199                    <p>No authentication is required for public search and metadata endpoints.</p>
200                    <table class="tbl"><tr><th>Header</th><th>Required</th><th>Description</th></tr>
201                    <tr><td>Authorization</td><td class="opt">No</td><td>Not required for public endpoints</td></tr>
202                    <tr><td>x-session-token</td><td class="req">Yes</td><td>Required for User endpoints (Upload, Rate, Profile Update)</td></tr></table>
203                    <div class="note"><b>Note:</b> All third-party API keys (TMDB, RAWG) are kept server-side and are never exposed in client responses.</div>
204                </div>
205                <div class="ep" id="rate-limit">
206                    <h3>Rate Limiting</h3>
207                    <p>The API uses IP-based rate limiting.</p>
208                    <table class="tbl"><tr><th>Setting</th><th>Value</th></tr>
209                    <tr><td>Requests</td><td>120 / minute / IP</td></tr>
210                    <tr><td>Auth Limit</td><td>5 / minute / IP (Login/Register)</td></tr>
211                    <tr><td>Enforcement</td><td>Automatic</td></tr>
212                    <tr><td>Exceeded</td><td><code>429 Too Many Requests</code></td></tr></table>
213                </div>
214                <div class="ep" id="response-format">
215                    <h3>Response Format</h3>
216                    <p>All responses are JSON with the following headers:</p>
217                    <table class="tbl"><tr><th>Header</th><th>Value</th></tr>
218                    <tr><td>Content-Type</td><td>application/json; charset=utf-8</td></tr>
219                    <tr><td>Cache-Control</td><td>no-store</td></tr></table>
220                </div>
221            </div>
222
223            <!-- TORRENTS -->
224            <div class="section" id="torrents">
225                <div class="section-h"><span class="bar"></span> Torrent API</div>
226                <p class="section-desc">Search torrents, get metadata, list files, and track downloads.</p>
227
228                <div class="ep" id="search">
229                    <div class="ep-head"><span class="method get">
229GET</span><span class="path">/api/search</span></div>
230                    <p>Queries configured torrent indexers concurrently and merges their results with the local index, including DHT and user uploads. Results are filtered and ranked for relevance before seed count. Season queries such as <code>the expanse S01</code> search the show title and return matching-season releases, including season packs.</p>
231                    <table class="tbl"><tr><th>Parameter</th><th>Required</th><th>Default</th><th>Description</th></tr>
232                    <tr><td>q</td><td class="opt">No</td><td>""</td><td>Search query. Empty = browse latest uploads</td></tr>
233                    <tr><td>category</td><td class="opt">No</td><td>0</td><td>Category code (see below)</td></tr>
234                    </table>
235                    <p style="margin-top:8px;color:var(--text2);font-size:13px;">Category codes: <code>0</code>=All, <code>100</code>=Audio, <code>200</code>=Video, <code>300</code>=Apps, <code>400</code>=Games, <code>500</code>=Adult, <code>600</code>=Other</p>
236                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/search?q=breaking+bad&category=200"</code></div></div>
237                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
238  "query": "breaking bad",
239  "source": "remote_api+local+multi_indexer",
240  "results": [{
241    "id": "4034523",
242    "name": "Breaking.Bad.S01E01.1080p.BluRay.x265",
243    "info_hash": "abc123def456...",
244    "magnet": "magnet:?xt=urn:btih:abc123def456...",
245    "seeders": 245,
246    "leechers": 12,
247    "size": 1073741824,
248    "sizeLabel": "1.00 GiB",
249    "category": 200,
250    "added": 1700000000,
251    "username": "uploader123",
252    "status": "trusted",
253    "source": "TPB"
254  }]
255}</code></div></div>
256                    <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr>
257                    <tr><td>id</td><td>string</td><td>Unique torrent ID</td></tr>
258                    <tr><td>name</td><td>string</td><td>Torrent title</td></tr>
259                    <tr><td>info_hash</td><td>string</td><td>BitTorrent info hash</td></tr>
260                    <tr><td>magnet</td><td>string</td><td>Full magnet URI with trackers</td></tr>
261                    <tr><td>seeders</td><td>number</td><td>Active seeders</td></tr>
262                    <tr><td>leechers</td><td>number</td><td>Active leechers</td></tr>
263                    <tr><td>size</td><td>number</td><td>Size in bytes</td></tr>
264                    <tr><td>sizeLabel</td><td>string</td><td>Human-readable size</td></tr>
265                    <tr><td>added</td><td>number</td><td>Unix timestamp</td></tr>
266                    <tr><td>status</td><td>string</td><td>Uploader status (trusted/vip)</td></tr>
267                    <tr><td>source</td><td>string</td><td>Origin, such as TPBMirror, 1337x, YTS, DHT, or User_Upload</td></tr></table>
268                    <div class="warn"><b>Fallback:</b> If the live search fails, the endpoint returns matching results from the local index with response <code>source: "local_fallback"</code>.</div>
269                </div>
270
271                <div class="ep" id="torrent-details">
272                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/torrent/{id}</span></div>
273                    <p>Returns full metadata for a single torrent.</p>
274                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/torrent/4034523"</code></div></div>
275                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>
275{ "torrent": { "id":"4034523", "name":"...", "magnet":"magnet:?...", "seeders":245, "sizeLabel":"1.00 GiB" } }</code></div></div>
276                </div>
277
278                <div class="ep" id="torrent-files">
279                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/files/{id}</span></div>
280                    <p>Returns the list of files inside a torrent. For user-uploaded torrents, this is fetched dynamically via WebTorrent.</p>
281                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
282  "files": [
283    { "name": "video.mkv", "size": 1073741824, "sizeLabel": "1.00 GiB" },
284    { "name": "subs.srt", "size": 45200, "sizeLabel": "44.14 KiB" }
285  ]
286}</code></div></div>
287                </div>
288
289                <div class="ep" id="track-download">
290                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/track/download</span></div>
291                    <p>Increments the download counter. Call this when a user clicks a magnet link.</p>
292                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{ "ok": true }</code></div></div>
293                </div>
294            </div>
295
296            <!-- USERS -->
297            <div class="section" id="users">
298                <div class="section-h"><span class="bar"></span> User API</div>
299                <p class="section-desc">Register, login, upload torrents, and rate users.</p>
300
301                <div class="ep" id="user-register">
302                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/auth/register</span></div>
303                    <p>Register a new user account. Returns a session token. <code>acceptToS: true</code> is required - accounts are only created when the Terms of Service and Privacy Policy are accepted.</p>
304                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body
305{
306  "username": "newuser",
307  "password": "supersecretpassword",
308  "acceptToS": true
309}
310
311// Response 200
312{
313  "success": true,
314  "token": "SESSION_TOKEN_HERE",
315  "user": { "id": "...", "username": "newuser", "rank": 1, "xp": 0 }
316}</code></div></div>
317                </div>
318
319                <div class="ep" id="user-login">
320                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/auth/login</span></div>
321                    <p>Login to an existing account. Returns a session token.</p>
322                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body
323{
324  "username": "existinguser",
325  "password": "supersecretpassword"
326}
327
328// Response 200
329{
330  "success": true,
331  "token": "SESSION_TOKEN_HERE",
332  "user": { "id": "...", "username": "existinguser", "rank": 5, "xp": 1200 }
333}</code></div></div>
334                </div>
335
336                <div class="ep" id="user-me">
337                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/user/me</span></div>
338                    <p>Returns the currently logged-in user's private profile data. Requires <code>x-session-token</code> header.</p>
339                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl -H "x-session-token: YOUR_TOKEN" "https://theblackholebay.lol/api/user/me"</code></div></div>
340                </div>
341
342                <div class="ep" id="user-profile">
343                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/user/profile/{username}</span></div>
344                    <p>Returns public profile data, uploaded torrents, and rating info for a specific user.</p>
345                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Response 200
346{
347  "user": {
348    "id": "abc123",
349    "username": "Cybotage",
350    "avatarPath": "/uploads/avatars/abc123.jpg",
351    "bio": "Official uploader",
352    "rank": 10,
353    "badges": ["5 Uploads", "10 Uploads"]
354  },
355  "torrents": [ { "id": "idx_123", "name": "Movie.mkv" } ],
356  "avgRating": "4.8"
357}</code></div></div>
358                </div>
359
360                <div class="ep" id="user-upload">
361                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/user/upload</span></div>
362                    <p>Submit a new torrent to the index. Auto-approved. Requires <code>x-session-token</code> header.</p>
363                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body
364{
365  "name": "My.Custom.Torrent.1080p",
366  "info_hash": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b", // 40 hex chars
367  "category": 200,
368  "description": "Optional description here"
369}
370
371// Response 200
372{
373  "success": true,
374  "message": "Torrent uploaded successfully! (+50 XP)"
375}</code></div></div>
376                    <div class="warn"><b>Note:</b> Duplicate info hashes are rejected with <code>409 Conflict</code>.</div>
377                </div>
378
379                <div class="ep" id="user-rate">
380                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/user/rate</span></div>
381                    <p>Rate another user (1-5 stars). You can rate a user only once per IP. Requires <code>x-session-token</code> header.</p>
382                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>// Request Body
383{
384  "toUserId": "abc123",
385  "score": 5
386}
387
388// Response 200
389{
390  "success": true,
391  "newRank": 9
392}</code></div></div>
393                </div>
394            </div>
395
396            <!-- MOVIES & TV -->
397            <div class="section" id="movies-tv">
398                <div class="section-h"><span class="bar"></span> Movies &amp; TV</div>
399                <p class="section-desc">Movie and TV metadata powered by TMDB.</p>
400
401                <div class="ep" id="trending-movies">
402                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending</span></div>
403                    <p>Trending movies this week.</p>
404                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
405  "results": [{
406    "id": 872585,
407    "title": "Oppenheimer",
408    "overview": "...",
409    "poster_path": "/abc.jpg",
410    "backdrop_path": "/def.jpg",
411    "vote_average": 8.4,
412    "release_date": "2024-01-15",
413    "original_language": "en"
414  }]
415}</code></div></div>
416                </div>
417
418                <div class="ep" id="trending-tv">
419                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending_tv</span></div>
420                    <p>Trending TV shows this week. Uses <code>name</code> instead of <code>title</code> and <code>first_air_date</code> instead of <code>release_date</code>.</p>
421                </div>
422
423                <div class="ep" id="now-playing">
424                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/upcoming?page=1</span></div>
425                    <p>Movies currently in theaters. Posters cached locally.</p>
426                    <table class="tbl"><tr><th>Parameter</th><th>Default</th><th>Description</th></tr>
427                    <tr><td>page</td><td>1</td><td>Page number (20 per page)</td></tr></table>
428                    <div class="note"><b>Note:</b> <code>poster_path</code> returns a local path: <code>/upcoming/images/{id}.jpg</code> - use directly as <code>https://theblackholebay.lol/upcoming/images/{id}.jpg</code>. Movies older than 90 days are filtered out.</div>
429                </div>
430
431                <div class="ep" id="popular-tv">
432                    <div class="ep-head"><span class="method get">
432GET</span><span class="path">/api/tv_airing?page=1</span></div>
433                    <p>Currently popular TV shows, filtered to the last 5 years.</p>
434                    <table class="tbl"><tr><th>Parameter</th><th>Default</th><th>Description</th></tr>
435                    <tr><td>page</td><td>1</td><td>Page number</td></tr></table>
436                </div>
437
438                <div class="ep" id="movie-lists">
439                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/movies?list={list}&page={n}</span></div>
440                    <p>Cached movie lists. <code>list</code>: <code>now_playing</code>, <code>popular</code>, <code>top_rated</code>, <code>upcoming</code>. Poster art is downloaded once and served locally from <code>/upcoming/images/</code>.</p>
441</div>
442<div class="ep" id="tv-lists">
443                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tv?list={list}&page={n}</span></div>
444                    <p>Cached TV lists. <code>list</code>: <code>popular</code>, <code>top_rated</code>, <code>on_the_air</code>, <code>airing_today</code>.</p>
445</div>
446<div class="ep" id="discover">
447                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/discover?type={movie|tv}&genre={id}&page={n}</span></div>
448                    <p>Browse movies or TV by TMDB genre id (cached 6 hours). Without a genre id this returns the popular list.</p>
449</div>
450<div class="ep" id="tmdb-search">
451                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tmdb/search?type={movie|tv}&q={query}&page={n}</span></div>
452                    <p>Proxied movie/TV search - the TMDB key never leaves the server. Results are cached for 30 minutes.</p>
453</div>
454<div class="ep" id="tv-season">
455                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/tv/{id}/season/{n}</span></div>
456                    <p>Episode list for a TV season (cached 24 hours).</p>
457</div>
458<div class="ep" id="suggestions-endpoint">
459                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/suggestions</span></div>
460                    <p>Submit a feature suggestion. Body: <code>{ title, category, description }</code>. Rate limited (5/min per IP), 20KB body cap, honeypot field.</p>
461</div>
462<div class="ep" id="applications-endpoint">
463                    <div class="ep-head"><span class="method post">POST</span><span class="path">/api/applications</span></div>
464                    <p>Submit a staff application from <code>/apply</code>. Body: <code>{ role, username, contact, timezone, availability, skills, experience, motivation, links }</code>. Rate limited (3/min per IP).</p>
465</div>
466<div class="ep" id="music-artwork">
467                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/music/artwork?q={artist title}</span></div>
468                    <p>Album artwork + artist lookup for the desktop music player (keyless iTunes-search proxy, 24h server-side cache, never errors - returns <code>{ artwork: null }</code> on miss).</p>
469</div>
470<div class="ep" id="music-import">
471                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/music/import?url={playlist link}</span></div>
472                    <p>Import a YouTube playlist or a public Spotify playlist. The server parses the embedded page data and returns the track list so clients can match each track to the site's audio index.</p>
473</div>
474                <div class="ep" id="movie-details">
475                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/movie/{id}</span></div>
476                    <p>Full movie details including cast, runtime, genres, and IMDb link.</p>
477                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
478  "id": 872585,
479  "title": "Oppenheimer",
480  "runtime": 180,
481  "imdb_id": "tt15398776",
482  "genres": [{ "name": "Drama" }],
483  "credits": {
484    "cast": [{ "name": "Cillian Murphy", "character": "Oppenheimer" }]
485  }
486}</code></div></div>
487                </div>
488
489                <div class="ep" id="tv-details">
490                    <div class="ep-head"><span class="method get">
490GET</span><span class="path">/api/tv/{id}</span></div>
491                    <p>Full TV show details including cast, seasons, and episodes.</p>
492                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
493  "name": "Show Name",
494  "number_of_seasons": 3,
495  "number_of_episodes": 24,
496  "episode_run_time": [55],
497  "status": "Returning Series"
498}</code></div></div>
499                </div>
500            </div>
501
502            <!-- GAMES -->
503            <div class="section" id="games">
504                <div class="section-h"><span class="bar"></span> Games</div>
505                <p class="section-desc">Game metadata powered by RAWG.</p>
506
507                <div class="ep" id="trending-games">
508                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/trending_games</span></div>
509                    <p>Recently released games from the last 6 months, sorted by release date. Filtered to modern platforms (PC, PS4, PS5, Xbox One, Xbox Series, Switch). Up to 24 results.</p>
510                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
511  "results": [{
512    "name": "Black Myth: Wukong",
513    "background_image": "https://media.rawg.io/media/games/xxx.jpg",
514    "released": "2024-08-20",
515    "rating": "85",
516    "genres": "Action, RPG",
517    "platforms": "PC, PlayStation 5"
518  }]
519}</code></div></div>
520                    <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr>
521                    <tr><td>name</td><td>string</td><td>Game name</td></tr>
522                    <tr><td>background_image</td><td>string</td><td>Direct image URL - use as-is</td></tr>
523                    <tr><td>released</td><td>string</td><td>Release date (YYYY-MM-DD)</td></tr>
524                    <tr><td>rating</td><td>string?</td><td>Metacritic score or RAWG rating</td></tr>
525                    <tr><td>genres</td><td>string</td><td>Comma-separated genre names</td></tr>
526                    <tr><td>platforms</td><td>string</td><td>Comma-separated platform names</td></tr></table>
527                </div>
528            </div>
529
530            <!-- DESKTOP APP UPDATES -->
531            <div class="section" id="desktop-updates">
532                <div class="section-h"><span class="bar"></span> Desktop App Updates</div>
533                <p class="section-desc">Release feed and auto-update support for the BlackHole Bay desktop app. Releases are published from the admin panel under <b>App Updates</b>. These endpoints are public and stay reachable even while the site is in maintenance mode.</p>
534
535                <div class="ep" id="update-check">
536                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/update</span></div>
537                    <p>Checks whether a newer build than the caller's is published. This is the endpoint the desktop app calls on launch to decide whether to prompt for an update.</p>
538                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/update?current=1.0.0&amp;channel=stable"</code></div></div>
539                    <table class="tbl"><tr><th>Parameter</th><th>Required</th><th>Description</th></tr>
540                    <tr><td>current</td><td class="opt">No</td><td>Version the client is currently running. Omit to just fetch the latest release.</td></tr>
541                    <tr><td>channel</td><td class="opt">No</td><td><code>stable</code> (default) or <code>beta</code>. Beta clients also receive stable builds; stable clients never see betas.</td></tr></table>
542                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
543  "updateAvailable": true,
544  "current": "1.0.0",
545  "latest": {
546    "id": "6b6e95cf579526b6",
547    "version": "1.1.0",
548    "name": "Stability &amp; Performance Patch",
549    "notes": [
550      "Fixed stalled downloads on resume",
551      "Improved streaming buffer handling"
552    ],
553    "channel": "stable",
554    "size": 2097152,
555    "sha256": "6ea73b45c3b229e3...",
556    "publishedAt": 1790594645585,
557    "fileName": "BlackHoleBaySetup1.1.0.exe",
558    "available": true,
559    "downloadUrl": "/api/releases/6b6e95cf579526b6/download"
560  }
561}</code></div></div>
562                    <table class="tbl"><tr><th>Field</th><th>Type</th><th>Description</th></tr>
563                    <tr><td>updateAvailable</td><td>boolean</td><td>True when latest is newer than current. Always <code>false</code> when current is omitted.</td></tr>
564                    <tr><td>current</td><td>string</td><td>Echo of the supplied current version.</td></tr>
565                    <tr><td>latest</td><td>object?</td><td>The newest available release, or <code>null</code> if nothing is published.</td></tr></table>
566                    <div class="note"><b>
566Always HTTP 200.</b> This endpoint never returns an error status for "no update" - check the <code>updateAvailable</code> field instead.</div>
567                </div>
568                <div class="ep" id="releases-list">
569                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/releases</span></div>
570                    <p>Returns every published release, newest first, plus the current stable build. Powers the version history list on the download page.</p>
571                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>curl "https://theblackholebay.lol/api/releases"</code></div></div>
572                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
573  "releases": [
574    {
575      "id": "6b6e95cf579526b6",
576      "version": "1.1.0",
577      "name": "Stability &amp; Performance Patch",
578      "notes": ["Fixed stalled downloads on resume"],
579      "channel": "stable",
580      "size": 2097152,
581      "sha256": "6ea73b45c3b229e3...",
582      "publishedAt": 1790594645585,
583      "fileName": "BlackHoleBaySetup1.1.0.exe",
584      "available": true,
585      "downloadUrl": "/api/releases/6b6e95cf579526b6/download"
586    }
587  ],
588  "latest": { "version": "1.1.0", "...": "..." }
589}</code></div></div>
590                    <div class="note"><b>Beta releases</b> appear in <code>releases</code> for every client. Only <code>latest</code> respects the channel split, since this endpoint is for display.</div>
591                </div>
592
593                <div class="ep" id="release-download">
594                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/releases/{id}/download</span></div>
595                    <p>Streams the installer binary for a release. Supports <code>Range</code> requests, so interrupted downloads resume instead of restarting.</p>
596                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code># Full download
597curl -L "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe
598
599# Resume an interrupted download
600curl -L -C - "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download" -o app.exe</code></div></div>
601                    <table class="tbl"><tr><th>Header</th><th>Description</th></tr>
602                    <tr><td>Content-Disposition</td><td>Forces a save using the original installer filename.</td></tr>
603                    <tr><td>Accept-Ranges</td><td>Always <code>bytes</code>.</td></tr>
604                    <tr><td>X-Release-Version</td><td>Version of the file being served.</td></tr></table>
605                    <div class="note"><b>Status codes:</b> <code>200</code> full body, <code>206</code> partial (Range), <code>404</code> unknown id, <code>410</code> the record exists but the binary is gone from disk.</div>
606                    <div class="warn"><b>Verify the checksum.</b> Each release carries a <code>sha256</code> computed server-side while the file was uploaded. Compare it against your downloaded file before executing the installer.</div>
607                </div>
608                <div class="ep" id="desktop-version">
609                    <div class="ep-head"><span class="method get">GET</span><span class="path">/api/desktop-version</span></div>
610                    <p>Simple latest-version lookup kept for older clients. Prefer <code>/api/update</code> for new integrations, since it reports whether an update is actually needed.</p>
611                    <div class="code-block"><div class="code-head"><span class="code-lang">json</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>{
612  "version": "1.1.0",
613  "name": "Stability &amp; Performance Patch",
614  "url": "https://theblackholebay.lol/api/releases/6b6e95cf579526b6/download",
615  "notes": ["Fixed stalled downloads on resume"],
616  "sha256": "6ea73b45c3b229e3...",
617  "size": 2097152
618}</code></div></div>
619                    <div class="warn"><b>Note:</b> <code>version</code> is <code>null</code> when no release has been published yet.</div>
620                </div>
621
622                <div class="ep" id="client-integration">
623                    <div class="section-h" style="font-size:17px;"><span class="bar"></span> Client Integration</div>
624                    <p>Minimal update-check flow for the desktop app. Poll on launch, compare the returned version, then download and verify.</p>
625                    <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const crypto = require('crypto');
626
627async function checkForUpdate(currentVersion, channel = 'stable') {
628  const res = await fetch(
629    'https://theblackholebay.lol/api/update?current=' +
630      encodeURIComponent(currentVersion) + '&amp;channel=' + channel
631  );
632  const data = await res.json();   // always HTTP 200
633
634  if (!data.updateAvailable || !data.latest) return { update: false };
635
636  return {
637    update: true,
638    version: data.latest.version,
639    notes: data.latest.notes,       // show these in the update prompt
640    url: 'https://theblackholebay.lol' + data.latest.downloadUrl,
641    sha256: data.latest.sha256,     // verify after download
642    size: data.latest.size
643  };
644}</code></div></div>
645                    <div class="warn"><b>Verify before running.</b> Compute the SHA-256 of the downloaded file and compare it to <code>latest.sha256</code> before launching the installer. If they differ, discard the file and report a failed update.</div>
646                </div>
647            </div>
648
649            <!-- REFERENCE -->
650            <div class="section" id="images">
651                <div class="section-h"><span class="bar"></span> Image URLs</div>
652                <table class="tbl"><tr><th>Source</th><th>Format</th></tr>
653                <tr><td>TMDB</td><td><code>https://image.tmdb.org/t/p/{size}{path}</code>
653 - sizes: w342, w500, w780, original</td></tr>
654                <tr><td>Local cache</td><td><code>https://theblackholebay.lol/upcoming/images/{id}.jpg</code></td></tr>
655                <tr><td>RAWG</td><td>Use <code>background_image</code> URL directly</td></tr>
656                <tr><td>User Avatars</td><td><code>https://theblackholebay.lol/uploads/avatars/{userId}.png</code></td></tr></table>
657            </div>
658
659            <div class="section" id="errors">
660                <div class="section-h"><span class="bar"></span> Errors</div>
661                <table class="tbl"><tr><th>Status</th><th>Meaning</th><th>Response</th></tr>
662                <tr><td>400</td><td>Bad Request</td><td><code>{ "error": "Invalid request" }</code></td></tr>
663                <tr><td>401</td><td>Unauthorized</td><td><code>{ "error": "Unauthorized" }</code></td></tr>
664                <tr><td>403</td><td>IP blocked</td><td><code>Forbidden: IP banned.</code></td></tr>
665                <tr><td>404</td><td>Not found</td><td><code>{ "error": "Failed." }</code></td></tr>
666                <tr><td>409</td><td>Conflict</td><td><code>{ "error": "This torrent hash has already been uploaded." }</code></td></tr>
667                <tr><td>429</td><td>Rate limit exceeded</td><td><code>Too Many Requests.</code></td></tr>
668                <tr><td>500</td><td>Server error</td><td><code>{ "error": "Internal Server Error" }</code></td></tr></table>
669            </div>
670
671            <div class="section" id="code-examples">
672                <div class="section-h"><span class="bar"></span> Code Examples</div>
673                <div class="ep">
674                    <h3>JavaScript</h3>
675                    <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const API = "https://theblackholebay.lol";
676
677// Search torrents
678const res = await fetch(`${API}/api/search?q=oppenheimer&category=200`);
679const data = await res.json();
680
681data.results.forEach(t => {
682  console.log(`${t.name} | ${t.sizeLabel} | S:${t.seeders} L:${t.leechers}`);
683});
684
685// Login and upload
686const loginRes = await fetch(`${API}/api/auth/login`, {
687  method: 'POST',
688  headers: { 'Content-Type': 'application/json' },
689  body: JSON.stringify({ username: 'user', password: 'pass' })
690});
691const { token } = await loginRes.json();
692
693await fetch(`${API}/api/user/upload`, {
694  method: 'POST',
695  headers: { 'Content-Type': 'application/json', 'x-session-token': token },
696  body: JSON.stringify({ name: 'My.Torrent', info_hash: 'abc123...', category: 200 })
697});</code></div></div>
698                </div>
699                <div class="ep">
700                    <h3>Python</h3>
701                    <div class="code-block"><div class="code-head"><span class="code-lang">python</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>import requests
702
703API = "https://theblackholebay.lol"
704
705response = requests.get(f"{API}/api/search", params={
706    "q": "breaking bad", "category": 200, "page": 0
707}, timeout=10)
708
709for torrent in response.json()["results"]:
710    print(f"{torrent['name']} | {torrent['sizeLabel']}")</code></div></div>
711                </div>
712                <div class="ep">
713                    <h3>cURL</h3>
714                    <div class="code-block"><div class="code-head"><span class="code-lang">bash</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code># Search
715curl "https://theblackholebay.lol/api/search?q=dune&category=200"
716
717# Trending movies
718curl "https://theblackholebay.lol/api/trending"
719
720# Trending games
721curl "https://theblackholebay.lol/api/trending_games"
722
723# Movie details
724curl "https://theblackholebay.lol/api/movie/872585"</code></div></div>
725                </div>
726                <div class="ep">
727                    <h3>Minimal API Client</h3>
728                    <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>const API = "https://theblackholebay.lol";
729
730const client = {
731  search: (q, cat = 0) =>
732    fetch(`${API}/api/search?q=${q}&category=${cat}`).then(r => r.json()),
733
734  torrent: (id) =>
735    fetch(`${API}/api/torrent/${id}`).then(r => r.json()),
736
737  files: (id) =>
738    fetch(`${API}/api/files/${id}`).then(r => r.json()),
739
740  trendingMovies: () =>
741    fetch(`${API}/api/trending`).then(r => r.json()),
742
743  trendingTV: () =>
744    fetch(`${API}/api/trending_tv`).then(r => r.json()),
745
746  trendingGames: () =>
747    fetch(`${API}/api/trending_games`).then(r => r.json()),
748
749  movie: (id) =>
750    fetch(`${API}/api/movie/${id}`).then(r => r.json()),
751
752  tv: (id) =>
753    fetch(`${API}/api/tv/${id}`).then(r => r.json())
754};</code></div></div>
755                </div>
756            </div>
757
758            <div class="section" id="best-practices">
759                <div class="section-h"><span class="bar"></span> Best Practices</div>
760                <div class="ep">
761                    <h3>Caching</h3>
762                    <p>API responses use <code>Cache-Control: no-store</code>. For frequently-requested data (like trending movies), implement your own short-lived cache (30-60 seconds) to avoid hitting rate limits.</p>
763                </div>
764                <div class="ep">
765                    <h3>Error Handling</h3>
766                    <div class="code-block"><div class="code-head"><span class="code-lang">javascript</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>switch (response.status) {
767  case 200: break;           // Success
768  case 400: break;           // Bad request
769  case 401: break;           // Unauthorized
770  case 403: break;           // IP blocked
771  case 404: break;           // Not found
772  case 409: break;           // Conflict (duplicate)
773  case 429: break;           // Back off and retry
774  case 500: break;           // Temporary failure
775}</code></div></div>
776                </div>
777                <div class="ep">
778                    <h3>Security</h3>
779                    <p>Do not expose provider API keys in frontend applications. All keys are kept server-side. The recommended architecture is:</p>
780                    <div class="code-block"><div class="code-head"><span class="code-lang">text</span><button class="copy-btn" onclick="copyCode(this)">Copy</button></div><div class="code-body"><code>Frontend ? HTTPS ? Your App ? Server-side request ? The Black Hole Bay API</code></div></div>
781                </div>
782            </div>
783        </main>
784    </div>
785
786    <div class="footer">
787        <p>The Black Hole Bay - API Reference</p>
788        <p style="margin-top:4px;">Last updated: October 7, 2026</p>
789        <p style="margin-top:8px;"><a href="/">Docs</a> - <a href="https://theblackholebay.lol/">Main Site</a> - <a href="https://theblackholebay.lol/#donations">Donations</a> - <a href="https://discord.gg/Qb2GQqW3Uq" target="_blank" rel="noopener noreferrer">Discord</a></p>
790    </div>
791
792    
792<script>
793    function copyCode(btn){
794        var code=btn.closest('.code-block').querySelector('code').textContent;
795        navigator.clipboard.writeText(code).then(function(){
796            btn.textContent='Copied!';btn.classList.add('copied');
797            setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);
798        });
799    }
800
801    // Scrollspy - highlight active sidebar link
802    var sections=document.querySelectorAll('section[id], .ep[id]');
803    var navLinks=document.querySelectorAll('.sidebar a');
804    var observer=new IntersectionObserver(function(entries){
805        entries.forEach(function(entry){
806            if(entry.isIntersecting){
807                var id=entry.target.id;
808                navLinks.forEach(function(link){
809                    link.classList.toggle('active',link.getAttribute('href')==='#'+id);
810                });
811            }
812        });
813    },{rootMargin:'-80px 0px -70% 0px'});
814    sections.forEach(function(s){observer.observe(s);});
815
816    // Smooth scroll
817    navLinks.forEach(function(link){
818        link.addEventListener('click',function(e){
819            var href=this.getAttribute('href');
820            if(href&&href.startsWith('#')){
821                e.preventDefault();
822                var target=document.querySelector(href);
823                if(target){target.scrollIntoView({behavior:'smooth'});document.body.classList.remove('sidebar-open');}
824            }
825        });
826    });
827    </script>
vendor: 1 bytes, line 827
827
828<script defer src="/js/site-footer.js"></script>
828<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"fed1a331b87d4dc7b361c56f56c914c2","r":1,"spa":2}' crossorigin="anonymous"></script>
828
829</body>
830</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.