Emano209 commited on
Commit
07724ba
Β·
verified Β·
1 Parent(s): 41479f3

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +9 -0
  2. README.md +26 -0
  3. index.js +122 -0
  4. package.json +12 -0
  5. showbox.js +200 -0
Dockerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+ WORKDIR /app
3
+ COPY package*.json ./
4
+ RUN npm install --production
5
+ COPY . .
6
+ RUN chown -R node:node /app
7
+ USER node
8
+ EXPOSE 7860
9
+ CMD ["node", "index.js"]
README.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ShowBox Stremio Addon
3
+ emoji: 🎬
4
+ colorFrom: purple
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # ShowBox Stremio Addon v3
11
+
12
+ Direct HTTP streams via ShowBox + FebBox.
13
+
14
+ ## Required Secrets (Space Settings β†’ Variables and Secrets)
15
+
16
+ | Secret | How to get it |
17
+ |--------|--------------|
18
+ | `FEBBOX_UI_COOKIE` | [febbox.com](https://www.febbox.com) β†’ login β†’ F12 β†’ Application β†’ Cookies β†’ copy `ui` value |
19
+ | `TMDB_API_KEY` | [themoviedb.org/signup](https://www.themoviedb.org/signup) β†’ free account β†’ Settings β†’ API β†’ v3 key |
20
+
21
+ ## Install in Stremio
22
+
23
+ Once the Space is running, add this URL in Stremio:
24
+ ```
25
+ https://YOUR_USERNAME-YOUR_SPACE_NAME.hf.space/manifest.json
26
+ ```
index.js ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const { getShowboxStreams } = require('./showbox');
4
+
5
+ const app = express();
6
+ app.use(cors());
7
+
8
+ const PORT = process.env.PORT || 7860;
9
+
10
+ const FEBBOX_UI_COOKIE = process.env.FEBBOX_UI_COOKIE || '';
11
+ const TMDB_API_KEY = process.env.TMDB_API_KEY || '';
12
+
13
+ const manifest = {
14
+ id: 'community.showbox-febbox-streams',
15
+ version: '3.0.0',
16
+ name: 'ShowBox Streams',
17
+ description: 'Direct HTTP streams via ShowBox + FebBox.',
18
+ logo: 'https://www.febbox.com/favicon.ico',
19
+ resources: ['stream'],
20
+ types: ['movie', 'series'],
21
+ idPrefixes: ['tt'],
22
+ catalogs: [],
23
+ behaviorHints: { adult: false, p2p: false },
24
+ };
25
+
26
+ app.get('/', (req, res) => {
27
+ const hasCookie = !!FEBBOX_UI_COOKIE;
28
+ const hasKey = !!TMDB_API_KEY;
29
+ const host = process.env.SPACE_HOST
30
+ ? `https://${process.env.SPACE_HOST}`
31
+ : `http://localhost:${PORT}`;
32
+
33
+ res.send(`<!DOCTYPE html><html>
34
+ <head><title>ShowBox Streams</title>
35
+ <style>
36
+ *{box-sizing:border-box}body{font-family:-apple-system,sans-serif;max-width:640px;
37
+ margin:60px auto;color:#e0e0e0;background:#0f0f0f;padding:24px}h1{color:#fff}
38
+ .badge{display:inline-block;padding:3px 12px;border-radius:20px;font-size:12px;margin:2px}
39
+ .ok{background:#1e4d1e}.no{background:#4d1e1e}
40
+ .btn{display:inline-block;padding:12px 26px;background:#7b5ea7;color:#fff;
41
+ text-decoration:none;border-radius:8px;font-size:15px;margin-top:8px}
42
+ code{background:#1e1e1e;padding:2px 7px;border-radius:4px;font-size:13px}
43
+ .box{background:#1a1a1a;border:1px solid #2a2a2a;border-radius:10px;padding:16px;margin:18px 0}
44
+ .box h3{margin-top:0;color:#888;font-size:12px;text-transform:uppercase;letter-spacing:1px}
45
+ ol{margin:0;padding-left:18px;line-height:2;font-size:14px}a{color:#9b79d4}
46
+ </style></head>
47
+ <body>
48
+ <h1>🎬 ShowBox Streams</h1>
49
+ <span class="badge ${hasCookie?'ok':'no'}">${hasCookie?'βœ… FebBox cookie set':'⚠️ No FebBox cookie'}</span>
50
+ <span class="badge ${hasKey?'ok':'no'}">${hasKey?'βœ… TMDB key set':'⚠️ No TMDB key'}</span>
51
+
52
+ <p>Stremio addon for direct HTTP streams via ShowBox + FebBox.</p>
53
+ <a class="btn" href="stremio://${host.replace(/^https?:\/\//,'')}/manifest.json">βž• Install in Stremio</a>
54
+ <p style="font-size:13px;color:#666;margin-top:8px">Or add manually:<br>
55
+ <code>${host}/manifest.json</code></p>
56
+
57
+ <div class="box">
58
+ <h3>Required: FebBox ui cookie</h3>
59
+ <ol>
60
+ <li>Go to <a href="https://www.febbox.com" target="_blank">febbox.com</a> β†’ log in with Google</li>
61
+ <li>Press F12 β†’ Application β†’ Cookies β†’ <code>febbox.com</code></li>
62
+ <li>Copy the value of the <strong>ui</strong> cookie</li>
63
+ <li>Add as secret <code>FEBBOX_UI_COOKIE</code> in HuggingFace Space settings</li>
64
+ </ol>
65
+ </div>
66
+
67
+ <div class="box">
68
+ <h3>Required: TMDB API key</h3>
69
+ <ol>
70
+ <li>Go to <a href="https://www.themoviedb.org/signup" target="_blank">themoviedb.org</a> β†’ sign up free</li>
71
+ <li>Go to Settings β†’ API β†’ Request a v3 API Key (takes ~1 min)</li>
72
+ <li>Add as secret <code>TMDB_API_KEY</code> in HuggingFace Space settings</li>
73
+ </ol>
74
+ </div>
75
+ </body></html>`);
76
+ });
77
+
78
+ app.get('/manifest.json', (_req, res) => res.json(manifest));
79
+
80
+ app.get('/stream/:type/:id.json', async (req, res) => {
81
+ const { type, id } = req.params;
82
+ let imdbId, season, episode;
83
+
84
+ if (type === 'series') {
85
+ const p = id.split(':');
86
+ imdbId = p[0]; season = +p[1]; episode = +p[2];
87
+ } else {
88
+ imdbId = id;
89
+ }
90
+
91
+ console.log(`\n[req] ${type} ${imdbId}${season != null ? ` S${season}E${episode}` : ''}`);
92
+
93
+ if (!TMDB_API_KEY) {
94
+ console.error('[err] TMDB_API_KEY not set!');
95
+ return res.json({ streams: [] });
96
+ }
97
+ if (!FEBBOX_UI_COOKIE) {
98
+ console.warn('[warn] FEBBOX_UI_COOKIE not set β€” streams may fail');
99
+ }
100
+
101
+ try {
102
+ const streams = await getShowboxStreams({
103
+ imdbId,
104
+ type,
105
+ season: season ?? null,
106
+ episode: episode ?? null,
107
+ uiCookie: FEBBOX_UI_COOKIE,
108
+ tmdbApiKey: TMDB_API_KEY,
109
+ });
110
+ res.json({ streams });
111
+ } catch (err) {
112
+ console.error('[err]', err.message);
113
+ res.json({ streams: [] });
114
+ }
115
+ });
116
+
117
+ app.listen(PORT, () => {
118
+ console.log(`\n🎬 ShowBox Stremio Addon β€” port ${PORT}`);
119
+ if (!TMDB_API_KEY) console.log('⚠ TMDB_API_KEY not set β€” addon will not work');
120
+ if (!FEBBOX_UI_COOKIE) console.log('⚠ FEBBOX_UI_COOKIE not set β€” streams may fail');
121
+ if (TMDB_API_KEY && FEBBOX_UI_COOKIE) console.log('βœ… All secrets loaded');
122
+ });
package.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "stremio-showbox-febbox",
3
+ "version": "3.0.0",
4
+ "main": "index.js",
5
+ "scripts": { "start": "node index.js" },
6
+ "dependencies": {
7
+ "axios": "^1.6.0",
8
+ "cors": "^2.8.5",
9
+ "express": "^4.18.2"
10
+ },
11
+ "engines": { "node": ">=16.0.0" }
12
+ }
showbox.js ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // showbox.js β€” ShowBox/FebBox stream resolver
2
+ // Flow: IMDB ID β†’ TMDB ID β†’ superstream ID (dmdb.network) β†’ FebBox share key β†’ direct links
3
+
4
+ const axios = require('axios');
5
+
6
+ const FEBBOX_BASE = 'https://www.febbox.com';
7
+
8
+ const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
9
+
10
+ // ─── Step 1: IMDB β†’ TMDB ID ───────────────────────────────────────────────────
11
+ // Uses TMDB's free /find endpoint. Requires a free TMDB API key.
12
+ async function getTmdbId(imdbId, tmdbApiKey) {
13
+ const url = `https://api.themoviedb.org/3/find/${imdbId}?external_source=imdb_id&api_key=${tmdbApiKey}`;
14
+ const res = await axios.get(url, { timeout: 10000, headers: { 'User-Agent': UA } });
15
+ const data = res.data;
16
+
17
+ // Check movies first, then TV
18
+ if (data.movie_results?.length) return { tmdbId: data.movie_results[0].id, type: 'movie' };
19
+ if (data.tv_results?.length) return { tmdbId: data.tv_results[0].id, type: 'tv' };
20
+ return null;
21
+ }
22
+
23
+ // ─── Step 2: TMDB ID β†’ superstream/showbox ID (dmdb.network) ─────────────────
24
+ // api.dmdb.network is a free community API β€” no key required
25
+ async function getSuperstreamId(tmdbId, mediaType) {
26
+ const prefix = mediaType === 'tv' ? 'S' : 'M';
27
+ const url = `https://api.dmdb.network/v1/gmid/${prefix}.${tmdbId}`;
28
+ const res = await axios.get(url, { timeout: 10000, headers: { 'User-Agent': UA } });
29
+ const data = res.data;
30
+ if (!data?.ids?.superstream) return null;
31
+ return data.ids.superstream;
32
+ }
33
+
34
+ // ─── Step 3: superstream ID β†’ FebBox share key ───────────────────────────────
35
+ // showbox.media is the public website β€” less likely to block cloud IPs vs shegu.net
36
+ async function getFebboxShareKey(superstreamId, mediaType, season, episode) {
37
+ const type = mediaType === 'tv' ? 2 : 1;
38
+ let url = `https://www.showbox.media/index/share_link?id=${superstreamId}&type=${type}`;
39
+ if (mediaType === 'tv' && season != null) {
40
+ url += `&season=${season}&episode=${episode}`;
41
+ }
42
+
43
+ const res = await axios.get(url, {
44
+ timeout: 10000,
45
+ headers: {
46
+ 'User-Agent': UA,
47
+ Referer: 'https://www.showbox.media/',
48
+ },
49
+ });
50
+
51
+ const data = res.data;
52
+ const link = data?.data?.link;
53
+ if (!link) return null;
54
+
55
+ // Link looks like: https://www.febbox.com/share/XXXXXXX
56
+ const parts = link.split('/');
57
+ return parts[parts.length - 1] || null;
58
+ }
59
+
60
+ // ─── Step 4: FebBox file list ─────────────────────────────────────────────────
61
+ async function getFebboxFiles(shareKey, uiCookie, parentId = 0) {
62
+ const url = `${FEBBOX_BASE}/file/file_share_list?share_key=${shareKey}&parent_id=${parentId}&pwd=&is_html=0`;
63
+ const res = await axios.get(url, {
64
+ timeout: 15000,
65
+ headers: {
66
+ 'User-Agent': UA,
67
+ Cookie: `ui=${uiCookie}`,
68
+ Referer: `${FEBBOX_BASE}/`,
69
+ },
70
+ });
71
+ return res.data?.data?.file_list || [];
72
+ }
73
+
74
+ // ─── Step 5: FebBox direct links per file ─────────────────────────────────────
75
+ async function getFebboxLinks(shareKey, fid, uiCookie) {
76
+ const url = `${FEBBOX_BASE}/file/player?share_key=${shareKey}&fid=${fid}`;
77
+ const res = await axios.get(url, {
78
+ timeout: 15000,
79
+ headers: {
80
+ 'User-Agent': UA,
81
+ Cookie: `ui=${uiCookie}`,
82
+ Referer: `${FEBBOX_BASE}/`,
83
+ },
84
+ });
85
+ return res.data?.data?.sources || [];
86
+ }
87
+
88
+ // ─── TV episode resolver ──────────────────────────────────────────────────────
89
+ // FebBox stores TV shows as folders: Season X β†’ episodes
90
+ async function findEpisodeFile(shareKey, uiCookie, season, episode) {
91
+ const files = await getFebboxFiles(shareKey, uiCookie, 0);
92
+
93
+ // Find the season folder
94
+ const seasonFolder = files.find(f =>
95
+ f.file_icon === 'dir_icon' &&
96
+ f.file_name?.toLowerCase().includes('eason') &&
97
+ f.file_name?.match(/\d+/)?.at(0) === String(season)
98
+ );
99
+
100
+ if (!seasonFolder) {
101
+ // Flat structure β€” try to match episode directly
102
+ return files.find(f => {
103
+ const m = f.file_name?.match(/[Ss](\d+)[Ee](\d+)/);
104
+ return m && +m[1] === season && +m[2] === episode;
105
+ }) || files[0];
106
+ }
107
+
108
+ // Browse inside the season folder
109
+ const episodeFiles = await getFebboxFiles(shareKey, uiCookie, seasonFolder.fid);
110
+ return episodeFiles.find(f => {
111
+ const m = f.file_name?.match(/[Ss](\d+)[Ee](\d+)/) ||
112
+ f.file_name?.match(/[Ee](\d+)/);
113
+ if (!m) return false;
114
+ const ep = m[2] != null ? +m[2] : +m[1];
115
+ return ep === episode;
116
+ }) || episodeFiles[0];
117
+ }
118
+
119
+ function quality(filename) {
120
+ const f = (filename || '').toUpperCase();
121
+ if (f.includes('2160') || f.includes('4K') || f.includes('UHD')) return '4K';
122
+ if (f.includes('1080')) return '1080p';
123
+ if (f.includes('720')) return '720p';
124
+ if (f.includes('480')) return '480p';
125
+ return 'HD';
126
+ }
127
+
128
+ // ─── Main ─────────────────────────────────────────────────────────────────────
129
+ async function getShowboxStreams({ imdbId, type, season, episode, uiCookie, tmdbApiKey }) {
130
+ const log = (msg) => console.log(`[ShowBox] ${msg}`);
131
+
132
+ // Step 1
133
+ log(`Looking up TMDB ID for ${imdbId}...`);
134
+ const tmdbInfo = await getTmdbId(imdbId, tmdbApiKey);
135
+ if (!tmdbInfo) { log('Not found on TMDB'); return []; }
136
+ log(`TMDB ID: ${tmdbInfo.tmdbId} (${tmdbInfo.type})`);
137
+
138
+ // Step 2
139
+ log(`Getting superstream ID from dmdb.network...`);
140
+ const superstreamId = await getSuperstreamId(tmdbInfo.tmdbId, tmdbInfo.type);
141
+ if (!superstreamId) { log('No superstream ID found'); return []; }
142
+ log(`Superstream ID: ${superstreamId}`);
143
+
144
+ // Step 3
145
+ log(`Getting FebBox share key from showbox.media...`);
146
+ const shareKey = await getFebboxShareKey(superstreamId, tmdbInfo.type, season, episode);
147
+ if (!shareKey) { log('No FebBox share key returned'); return []; }
148
+ log(`FebBox share key: ${shareKey}`);
149
+
150
+ // Step 4 + 5
151
+ const streams = [];
152
+
153
+ if (type === 'series') {
154
+ log(`Looking for S${season}E${episode}...`);
155
+ const file = await findEpisodeFile(shareKey, uiCookie, season, episode);
156
+ if (!file) { log('Episode not found in FebBox'); return []; }
157
+ log(`Found file: ${file.file_name}`);
158
+ const sources = await getFebboxLinks(shareKey, file.fid, uiCookie);
159
+ for (const src of sources) {
160
+ const url = src.file || src.url;
161
+ if (!url) continue;
162
+ const label = src.label || quality(file.file_name);
163
+ streams.push({
164
+ url,
165
+ name: `ShowBox\n${label}`,
166
+ description: `S${season}E${episode} Β· ${label}`,
167
+ behaviorHints: { notWebReady: false, filename: file.file_name },
168
+ });
169
+ }
170
+ } else {
171
+ log(`Getting file list...`);
172
+ const files = await getFebboxFiles(shareKey, uiCookie, 0);
173
+ log(`${files.length} file(s) found`);
174
+
175
+ for (const file of files.slice(0, 4)) {
176
+ const q = quality(file.file_name);
177
+ try {
178
+ const sources = await getFebboxLinks(shareKey, file.fid, uiCookie);
179
+ for (const src of sources) {
180
+ const url = src.file || src.url;
181
+ if (!url) continue;
182
+ const label = src.label || q;
183
+ streams.push({
184
+ url,
185
+ name: `ShowBox\n${label}`,
186
+ description: label,
187
+ behaviorHints: { notWebReady: false, filename: file.file_name },
188
+ });
189
+ }
190
+ } catch (e) {
191
+ log(`fid=${file.fid} failed: ${e.message}`);
192
+ }
193
+ }
194
+ }
195
+
196
+ log(`Returning ${streams.length} stream(s)`);
197
+ return streams;
198
+ }
199
+
200
+ module.exports = { getShowboxStreams };