feat: add proxy support to bypass TMDB regional blocks (India)

- Add HTTP client with proxy support for TMDB API calls
- Create TMDBClient wrapper extending moviedb-promise with proxy integration
- Update all TMDB API modules to use new proxy-enabled client
- Add proxy configuration via environment variables:
  * TMDB_PROXY_ENABLED - Enable/disable proxy
  * TMDB_PROXY_HOST - Proxy host address
  * TMDB_PROXY_PORT - Proxy port
  * TMDB_PROXY_PROTOCOL - Proxy protocol (http, https, socks4, socks5)
  * TMDB_PROXY_AUTH - Enable proxy authentication
  * TMDB_PROXY_USERNAME - Proxy username
  * TMDB_PROXY_PASSWORD - Proxy password

- Add proxy status endpoint (/api/proxy/status) for monitoring
- Create test script (npm run test:proxy) for proxy verification
- Add comprehensive documentation:
  * PROXY_SETUP.md - User guide for proxy configuration
  * docs/proxy-implementation.md - Technical implementation details (English)
  * env.example - Environment variables template (English)
  * docker-compose.proxy.yml - Docker setup with Cloudflare WARP

- Update README.md with proxy support section
- Add logging when proxy is used for TMDB requests
- Support selective proxy usage (only TMDB domains use proxy)
- Maintain backward compatibility with existing configurations
- Translate all Portuguese text to English for international consistency

This resolves the issue where TMDB is blocked at network level in India,
allowing users to host the addon on Indian VPS providers while accessing
TMDB through a proxy. Based on similar implementation in AIOStreams.

Closes: Optional proxy for TMDB API calls (workaround for Indian VPS block) #1208
This commit is contained in:
mrcanelas
2025-08-13 15:38:30 -03:00
parent 87e2481760
commit f1c1071d09
26 changed files with 641 additions and 65 deletions
+18 -4
View File
@@ -5,10 +5,24 @@ FANART_API=your_fanart_api_key_here
TMDB_API=your_tmdb_api_key_here
HOST_NAME=http://localhost:1337
# Optional
PORT=3000
NODE_ENV=development
# Server settings
PORT=1337
NODE_ENV=production
# Analytics Configuration
METRICS_USER=admin
METRICS_PASSWORD=your_secure_password_here
METRICS_PASSWORD=your_secure_password_here
# Proxy Settings (optional)
# Enable proxy to bypass regional blocks
TMDB_PROXY_ENABLED=false
# Proxy configuration
TMDB_PROXY_HOST=127.0.0.1
TMDB_PROXY_PORT=1080
TMDB_PROXY_PROTOCOL=http
# Proxy authentication (optional)
TMDB_PROXY_AUTH=false
TMDB_PROXY_USERNAME=proxy_username
TMDB_PROXY_PASSWORD=proxy_password
+32 -1
View File
@@ -13,6 +13,21 @@
- **Integrations**: Watchlist Sync, Rating Support, Custom Lists
- **Modern UI**: Beautiful and intuitive configuration interface
- **IMDb Support**: Full compatibility with IMDb-based addons
- **Proxy Support**: Optional proxy configuration to bypass regional blocks (e.g., India)
## 🌐 Proxy Support
This addon now supports optional proxy configuration to bypass regional blocks where TMDB is blocked (such as in India). The proxy is only used for TMDB API calls, keeping all other requests direct.
### Quick Proxy Setup
```bash
# Enable proxy
TMDB_PROXY_ENABLED=true
TMDB_PROXY_HOST=127.0.0.1
TMDB_PROXY_PORT=40000
TMDB_PROXY_PROTOCOL=socks5
```
## 📥 Installation
@@ -63,12 +78,28 @@ docker run -d \
mrcanelas/tmdb-addon:latest
```
### Docker with Proxy Support
```bash
docker run -d \
--name tmdb-addon \
-p 1337:1337 \
-e TMDB_API=your_tmdb_key \
-e TMDB_PROXY_ENABLED=true \
-e TMDB_PROXY_HOST=127.0.0.1 \
-e TMDB_PROXY_PORT=40000 \
-e TMDB_PROXY_PROTOCOL=socks5 \
mrcanelas/tmdb-addon:latest
```
For complete proxy setup with Cloudflare WARP, see [docker-compose.proxy.yml](docker-compose.proxy.yml).
## 📚 Documentation
- [Self-Hosting Guide](docs/self-hosting.md) - Complete instructions for hosting your own instance
- [Development Guide](docs/development.md) - Guide for developers and contributors
- [Contributing Guide](docs/contributing.md) - How to contribute to the project
- [API Documentation](docs/api.md) - Complete API reference
- [Proxy Implementation](docs/proxy-implementation.md) - Guide for proxy configuration
## 🤝 Contributing
@@ -105,7 +136,7 @@ The metadata is provided by [TMDB](https://themoviedb.org/) and is subject to ch
---
<p align="center">
Made with ❤️ by the TMDB Addon community
Made with ❤️ by the Stremio community
</p>
+26 -4
View File
@@ -14,6 +14,7 @@ const { parseConfig, getRpdbPoster, checkIfExists } = require("./utils/parseProp
const { getRequestToken, getSessionId } = require("./lib/getSession");
const { getFavorites, getWatchList } = require("./lib/getPersonalLists");
const { blurImage } = require('./utils/imageProcessor');
const { testProxy, PROXY_CONFIG } = require('./utils/httpClient');
addon.use(analytics.middleware);
addon.use(favicon(path.join(__dirname, '../public/favicon.png')));
@@ -196,18 +197,39 @@ addon.get("/:catalogChoices?/meta/:type/:id.json", async function (req, res) {
}
});
addon.get("/api/proxy/status", async function (req, res) {
try {
const proxyStatus = {
enabled: PROXY_CONFIG.enabled,
host: PROXY_CONFIG.host,
port: PROXY_CONFIG.port,
protocol: PROXY_CONFIG.protocol,
working: false
};
if (PROXY_CONFIG.enabled) {
proxyStatus.working = await testProxy();
}
respond(res, proxyStatus);
} catch (error) {
console.error('Error checking proxy status:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
addon.get("/api/image/blur", async function (req, res) {
const imageUrl = req.query.url;
if (!imageUrl) {
return res.status(400).json({ error: 'URL da imagem não fornecida' });
return res.status(400).json({ error: 'Image URL not provided' });
}
try {
const blurredImageBuffer = await blurImage(imageUrl);
if (!blurredImageBuffer) {
return res.status(500).json({ error: 'Erro ao processar imagem' });
return res.status(500).json({ error: 'Error processing image' });
}
res.setHeader('Content-Type', 'image/jpeg');
@@ -215,8 +237,8 @@ addon.get("/api/image/blur", async function (req, res) {
res.send(blurredImageBuffer);
} catch (error) {
console.error('Erro na rota de blur:', error);
res.status(500).json({ error: 'Erro interno do servidor' });
console.error('Error in blur route:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
+3 -3
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
const { getGenreList } = require("./getGenreList");
const { getLanguages } = require("./getLanguages");
const { parseMedia } = require("../utils/parseProps");
@@ -30,7 +30,7 @@ async function getCatalog(type, language, page, id, genre, config) {
getMeta(type, language, item.id, config.rpdbkey)
.then(result => result.meta)
.catch(err => {
console.error(`Erro ao buscar metadados para ${item.id}:`, err.message);
console.error(`Error fetching metadata for ${item.id}:`, err.message);
return null;
})
);
+2 -2
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
const diferentOrder = require("../static/diferentOrder.json");
const diferentImdbId = require("../static/diferentImdbId.json");
+2 -2
View File
@@ -1,6 +1,6 @@
require('dotenv').config()
const { MovieDb } = require('moviedb-promise')
const moviedb = new MovieDb(process.env.TMDB_API)
const { TMDBClient } = require('../utils/tmdbClient')
const moviedb = new TMDBClient(process.env.TMDB_API)
async function getGenreList(language, type) {
if (type === "movie") {
+2 -2
View File
@@ -6,8 +6,8 @@ async function getImdbRating(imdbId, type) {
const data = response.data.meta;
return data?.imdbRating || undefined
} catch (error) {
console.error('Erro ao obter dados do Cinemeta:', error);
throw error;
console.error('Error fetching data from Cinemeta:', error);
return null;
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
async function getLanguages() {
const [primaryTranslations, languages] = await Promise.all([
+2 -2
View File
@@ -4,8 +4,8 @@ const apiKey = process.env.FANART_API;
const baseUrl = "http://webservice.fanart.tv/v3/";
const fanart = new FanartTvApi({ apiKey, baseUrl });
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
function pickLogo(logos, language, originalLanguage) {
const lang = language.split("-")[0];
+8 -8
View File
@@ -1,7 +1,7 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const { TMDBClient } = require("../utils/tmdbClient");
const Utils = require("../utils/parseProps");
const moviedb = new MovieDb(process.env.TMDB_API);
const moviedb = new TMDBClient(process.env.TMDB_API);
const { getEpisodes } = require("./getEpisodes");
const { getLogo, getTvLogo } = require("./getLogo");
const { getImdbRating } = require("./getImdbRating");
@@ -22,7 +22,7 @@ async function getCachedImdbRating(imdbId, type) {
imdbCache.set(imdbId, rating);
return rating;
} catch (err) {
console.error(`Erro ao buscar IMDb rating para ${imdbId}:`, err.message);
console.error(`Error fetching IMDb rating for ${imdbId}:`, err.message);
return null;
}
}
@@ -56,7 +56,7 @@ const buildMovieResponse = async (res, type, language, tmdbId, rpdbkey, config =
const [poster, logo, imdbRatingRaw] = await Promise.all([
Utils.parsePoster(type, tmdbId, res.poster_path, language, rpdbkey),
getLogo(tmdbId, language, res.original_language).catch(e => {
console.warn(`Erro ao buscar logo para filme ${tmdbId}:`, e.message);
console.warn(`Error fetching logo for movie ${tmdbId}:`, e.message);
return null;
}),
getCachedImdbRating(res.external_ids?.imdb_id, type),
@@ -116,14 +116,14 @@ const buildTvResponse = async (res, type, language, tmdbId, rpdbkey, config = {}
const [poster, logo, imdbRatingRaw, episodes] = await Promise.all([
Utils.parsePoster(type, tmdbId, res.poster_path, language, rpdbkey),
getTvLogo(res.external_ids?.tvdb_id, res.id, language, res.original_language).catch(e => {
console.warn(`Erro ao buscar logo para série ${tmdbId}:`, e.message);
console.warn(`Error fetching logo for series ${tmdbId}:`, e.message);
return null;
}),
getCachedImdbRating(res.external_ids?.imdb_id, type),
getEpisodes(language, tmdbId, res.external_ids?.imdb_id, res.seasons, {
hideEpisodeThumbnails: config.hideEpisodeThumbnails
}).catch(e => {
console.warn(`Erro ao buscar episódios da série ${tmdbId}:`, e.message);
console.warn(`Error fetching episodes for series ${tmdbId}:`, e.message);
return [];
})
]);
@@ -166,9 +166,9 @@ const buildTvResponse = async (res, type, language, tmdbId, rpdbkey, config = {}
};
if (hideInCinemaTag) delete response.imdb_id;
// Checagem de seasons (sem abrir issue)
// Season check (without opening issue)
if (response.imdb_id && response.videos && response.name) {
// Chama a checagem, mas comenta a parte do Issue dentro da função
// Call the check, but comment out the Issue part inside the function
checkSeasonsAndReport(
tmdbId,
response.imdb_id,
+2 -2
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
const { getGenreList } = require("./getGenreList");
const { parseMedia } = require("../utils/parseProps");
const translations = require("../static/translations.json");
+4 -4
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
const geminiService = require("../utils/gemini-service");
const { transliterate } = require("transliteration");
const { parseMedia } = require("../utils/parseProps");
@@ -48,7 +48,7 @@ async function getSearch(id, type, language, query, config) {
}
return null;
} catch (error) {
console.error(`Erro ao buscar detalhes para título "${title}":`, error);
console.error(`Error fetching details for title "${title}":`, error);
return null;
}
});
@@ -57,7 +57,7 @@ async function getSearch(id, type, language, query, config) {
searchResults = results.filter(result => result !== null);
} catch (error) {
console.error('Erro ao processar busca com IA:', error);
console.error('Error processing AI search:', error);
}
}
+17 -15
View File
@@ -1,22 +1,24 @@
require("dotenv").config();
const axios = require("axios")
require('dotenv').config()
const { get } = require('../utils/httpClient')
async function getRequestToken() {
return axios.get(`https://api.themoviedb.org/3/authentication/token/new?api_key=${process.env.TMDB_API}`)
.then(response => {
if (response.data.success) {
return response.data.request_token
}
})
return get(`https://api.themoviedb.org/3/authentication/token/new?api_key=${process.env.TMDB_API}`)
.then((res) => {
return res.data
})
.catch(err => {
return { success: false, status_message: err.message }
})
}
async function getSessionId(requestToken) {
return axios.get(`https://api.themoviedb.org/3/authentication/session/new?api_key=${process.env.TMDB_API}&request_token=${requestToken}`)
.then(response => {
if (response.data.success) {
return response.data.session_id
}
})
return get(`https://api.themoviedb.org/3/authentication/session/new?api_key=${process.env.TMDB_API}&request_token=${requestToken}`)
.then((res) => {
return res.data
})
.catch(err => {
return { success: false, status_message: err.message }
})
}
module.exports = { getRequestToken, getSessionId }
module.exports = { getRequestToken, getSessionId };
+2 -2
View File
@@ -1,6 +1,6 @@
require('dotenv').config()
const { MovieDb } = require('moviedb-promise')
const moviedb = new MovieDb(process.env.TMDB_API)
const { TMDBClient } = require('../utils/tmdbClient')
const moviedb = new TMDBClient(process.env.TMDB_API)
async function getTmdb(type, imdbId) {
if (type === "movie") {
+3 -3
View File
@@ -1,6 +1,6 @@
require("dotenv").config();
const { MovieDb } = require("moviedb-promise");
const moviedb = new MovieDb(process.env.TMDB_API);
const { TMDBClient } = require("../utils/tmdbClient");
const moviedb = new TMDBClient(process.env.TMDB_API);
const { getMeta } = require("./getMeta");
async function getTrending(type, language, page, genre, config) {
@@ -19,7 +19,7 @@ async function getTrending(type, language, page, genre, config) {
getMeta(type, language, item.id, config.rpdbkey)
.then(result => result.meta)
.catch(err => {
console.error(`Erro ao buscar metadados para ${item.id}:`, err.message);
console.error(`Error fetching metadata for ${item.id}:`, err.message);
return null;
})
);
+2 -2
View File
@@ -39,7 +39,7 @@ async function issueExistsOnGithub(title) {
}
async function checkSeasonsAndReport(tmdbId, imdbId, resp, name) {
// Se não cache disponível, pula a verificação
// If no cache is available, skip the check
if (!cache) {
console.log("Cache not available, skipping season check");
return;
@@ -96,7 +96,7 @@ async function checkSeasonsAndReport(tmdbId, imdbId, resp, name) {
`\n` +
`- [TMDB page](${tmdbLink})\n` +
`- [Stremio page](${stremioLink})`;
// Verifica se já existe issue aberta para esse título
// Check if issue already exists for this title
const exists = await issueExistsOnGithub(issueTitle);
console.log("Exists:", exists)
if (!exists) {
+2 -2
View File
@@ -14,7 +14,7 @@ class GeminiService {
this.model = this.genAI.getGenerativeModel({ model: "gemini-2.0-flash" });
return true;
} catch (error) {
console.error("Erro ao inicializar Gemini:", error);
console.error("Error initializing Gemini:", error);
return false;
}
}
@@ -70,7 +70,7 @@ class GeminiService {
return titles;
} catch (error) {
console.error("Error processing AI search:", error);
return []; // Retorna array vazio em caso de erro
return []; // Return empty array in case of error
}
}
}
+134
View File
@@ -0,0 +1,134 @@
const axios = require('axios');
const https = require('https');
const http = require('http');
// Proxy configuration
const PROXY_CONFIG = {
enabled: process.env.TMDB_PROXY_ENABLED === 'true',
host: process.env.TMDB_PROXY_HOST || '127.0.0.1',
port: process.env.TMDB_PROXY_PORT || 1080,
protocol: process.env.TMDB_PROXY_PROTOCOL || 'http',
auth: process.env.TMDB_PROXY_AUTH ? {
username: process.env.TMDB_PROXY_USERNAME,
password: process.env.TMDB_PROXY_PASSWORD
} : undefined
};
// TMDB domains that should use proxy
const TMDB_DOMAINS = [
'api.themoviedb.org',
'image.tmdb.org',
'www.themoviedb.org'
];
/**
* Check if a URL should use proxy based on domain
* @param {string} url - URL to check
* @returns {boolean} - True if should use proxy
*/
function shouldUseProxy(url) {
if (!PROXY_CONFIG.enabled) return false;
try {
const urlObj = new URL(url);
return TMDB_DOMAINS.some(domain => urlObj.hostname.includes(domain));
} catch (error) {
console.warn('Error parsing URL for proxy:', error.message);
return false;
}
}
/**
* Create an axios instance with proxy configuration if needed
* @param {string} url - Request URL
* @returns {Object} - Configured axios instance
*/
function createAxiosInstance(url) {
const config = {
timeout: 30000,
headers: {
'User-Agent': 'TMDB-Addon/3.1.7'
}
};
if (shouldUseProxy(url)) {
console.log(`Using proxy for: ${url}`);
const proxyConfig = {
host: PROXY_CONFIG.host,
port: PROXY_CONFIG.port,
protocol: PROXY_CONFIG.protocol
};
if (PROXY_CONFIG.auth) {
proxyConfig.auth = PROXY_CONFIG.auth;
}
config.proxy = proxyConfig;
// Additional configuration for HTTPS through proxy
if (PROXY_CONFIG.protocol === 'https') {
config.httpsAgent = new https.Agent({
rejectUnauthorized: false
});
} else {
config.httpAgent = new http.Agent();
}
}
return axios.create(config);
}
/**
* Make a GET request with proxy support
* @param {string} url - URL to make request to
* @param {Object} options - Additional options
* @returns {Promise} - Promise with response
*/
async function get(url, options = {}) {
const instance = createAxiosInstance(url);
return instance.get(url, options);
}
/**
* Make a POST request with proxy support
* @param {string} url - URL to make request to
* @param {Object} data - Data to send
* @param {Object} options - Additional options
* @returns {Promise} - Promise with response
*/
async function post(url, data = {}, options = {}) {
const instance = createAxiosInstance(url);
return instance.post(url, data, options);
}
/**
* Check if proxy is configured and working
* @returns {Promise<boolean>} - True if proxy is working
*/
async function testProxy() {
if (!PROXY_CONFIG.enabled) {
console.log('Proxy is not enabled');
return false;
}
try {
console.log('Testing proxy connection...');
const testUrl = 'https://api.themoviedb.org/3/configuration';
const response = await get(testUrl);
console.log('Proxy working correctly');
return true;
} catch (error) {
console.error('Error testing proxy:', error.message);
return false;
}
}
module.exports = {
get,
post,
shouldUseProxy,
createAxiosInstance,
testProxy,
PROXY_CONFIG
};
+1 -1
View File
@@ -13,7 +13,7 @@ async function blurImage(imageUrl) {
return processedImageBuffer;
} catch (error) {
console.error('Erro ao processar imagem:', error);
console.error('Error processing image:', error);
return null;
}
}
+1 -1
View File
@@ -78,7 +78,7 @@ async function parseMDBListItems(items, type, genreFilter, language, rpdbkey) {
getMeta(item.type, language, item.id, rpdbkey)
.then(result => result.meta)
.catch(err => {
console.error(`Erro ao buscar metadados para ${item.id}:`, err.message);
console.error(`Error fetching metadata for ${item.id}:`, err.message);
return null;
})
);
+2 -2
View File
@@ -169,13 +169,13 @@ function parseCreatedBy(created_by) {
function parseConfig(catalogChoices) {
let config = {};
// Se catalogChoices for null, undefined ou vazio, retorna objeto vazio
// If catalogChoices is null, undefined or empty, return empty object
if (!catalogChoices) {
return config;
}
try {
// Tenta descomprimir com lz-string
// Try to decompress with lz-string
const decoded = decompressFromEncodedURIComponent(catalogChoices);
config = JSON.parse(decoded);
} catch (e) {
+41
View File
@@ -0,0 +1,41 @@
const { MovieDb } = require('moviedb-promise');
const { createAxiosInstance } = require('./httpClient');
/**
* Custom TMDB client with proxy support
*/
class TMDBClient extends MovieDb {
constructor(apiKey) {
super(apiKey);
// Replace default request method
this._request = async (url, options = {}) => {
const instance = createAxiosInstance(url);
try {
const response = await instance.request({
url,
method: options.method || 'GET',
data: options.data,
params: options.params,
headers: options.headers,
...options
});
return response.data;
} catch (error) {
console.error(`Error in TMDB request for ${url}:`, error.message);
throw error;
}
};
}
/**
* Override request method to use our custom HTTP client
*/
async request(url, options = {}) {
return this._request(url, options);
}
}
module.exports = { TMDBClient };
+39
View File
@@ -0,0 +1,39 @@
version: '3.8'
services:
# Cloudflare WARP como proxy
cloudflare-warp:
image: cloudflare/cloudflare-warp:latest
container_name: cloudflare-warp
restart: unless-stopped
environment:
- WARP_CLIENT_ID=${WARP_CLIENT_ID}
- WARP_CLIENT_SECRET=${WARP_CLIENT_SECRET}
ports:
- "40000:40000" # Porta do proxy SOCKS5
command: warp-svc --address 0.0.0.0:40000
networks:
- tmdb-network
# TMDB Addon com proxy configurado
tmdb-addon:
build: .
container_name: tmdb-addon
restart: unless-stopped
environment:
- TMDB_API=${TMDB_API}
- TMDB_PROXY_ENABLED=true
- TMDB_PROXY_HOST=cloudflare-warp
- TMDB_PROXY_PORT=40000
- TMDB_PROXY_PROTOCOL=socks5
- PORT=1337
ports:
- "1337:1337"
depends_on:
- cloudflare-warp
networks:
- tmdb-network
networks:
tmdb-network:
driver: bridge
+240
View File
@@ -0,0 +1,240 @@
# Proxy Implementation - Technical Documentation
This document explains the technical implementation of the proxy system in TMDB Addon.
## Architecture
### Main Components
1. **`addon/utils/httpClient.js`** - Custom HTTP client with proxy support
2. **`addon/utils/tmdbClient.js`** - MovieDb wrapper with proxy integration
3. **Environment variable configuration** - Flexible proxy control
### Request Flow
```
Request → httpClient.js → Check domain → Proxy (if TMDB) → Response
```
## Detailed Implementation
### 1. Custom HTTP Client (`httpClient.js`)
```javascript
// Configuration based on environment variables
const PROXY_CONFIG = {
enabled: process.env.TMDB_PROXY_ENABLED === 'true',
host: process.env.TMDB_PROXY_HOST || '127.0.0.1',
port: process.env.TMDB_PROXY_PORT || 1080,
protocol: process.env.TMDB_PROXY_PROTOCOL || 'http'
};
// Domain verification
function shouldUseProxy(url) {
const TMDB_DOMAINS = ['api.themoviedb.org', 'image.tmdb.org', 'www.themoviedb.org'];
const urlObj = new URL(url);
return TMDB_DOMAINS.some(domain => urlObj.hostname.includes(domain));
}
```
### 2. MovieDb Wrapper (`tmdbClient.js`)
```javascript
class TMDBClient extends MovieDb {
constructor(apiKey) {
super(apiKey);
// Replace default request method
this._request = async (url, options = {}) => {
const instance = createAxiosInstance(url);
return instance.request({ url, ...options });
};
}
}
```
### 3. Module Integration
All modules that make requests to TMDB have been updated to use `TMDBClient`:
- `getTmdb.js`
- `getMeta.js`
- `getSearch.js`
- `getCatalog.js`
- `getTrending.js`
- `getPersonalLists.js`
- `getLogo.js`
- `getLanguages.js`
- `getGenreList.js`
- `getEpisodes.js`
- `getSession.js`
## Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `TMDB_PROXY_ENABLED` | `false` | Enable/disable proxy |
| `TMDB_PROXY_HOST` | `127.0.0.1` | Proxy host |
| `TMDB_PROXY_PORT` | `1080` | Proxy port |
| `TMDB_PROXY_PROTOCOL` | `http` | Protocol (http, https, socks4, socks5) |
| `TMDB_PROXY_AUTH` | `false` | Enable authentication |
| `TMDB_PROXY_USERNAME` | - | Proxy username |
| `TMDB_PROXY_PASSWORD` | - | Proxy password |
### Configuration Examples
#### HTTP Proxy
```bash
TMDB_PROXY_ENABLED=true
TMDB_PROXY_HOST=proxy.example.com
TMDB_PROXY_PORT=8080
TMDB_PROXY_PROTOCOL=http
```
#### SOCKS5 Proxy (Cloudflare WARP)
```bash
TMDB_PROXY_ENABLED=true
TMDB_PROXY_HOST=127.0.0.1
TMDB_PROXY_PORT=40000
TMDB_PROXY_PROTOCOL=socks5
```
#### Proxy with Authentication
```bash
TMDB_PROXY_ENABLED=true
TMDB_PROXY_HOST=proxy.example.com
TMDB_PROXY_PORT=8080
TMDB_PROXY_PROTOCOL=http
TMDB_PROXY_AUTH=true
TMDB_PROXY_USERNAME=user
TMDB_PROXY_PASSWORD=pass
```
## Monitoring APIs
### Proxy Status
```
GET /api/proxy/status
```
Response:
```json
{
"enabled": true,
"host": "127.0.0.1",
"port": 40000,
"protocol": "socks5",
"working": true
}
```
## Logs and Debugging
### Automatic Logs
The system automatically logs when using proxy:
```
Usando proxy para: https://api.themoviedb.org/3/configuration
```
### Manual Testing
```bash
# Test configuration
npm run test:proxy
# Test endpoint
curl http://localhost:1337/api/proxy/status
```
## Security
### Considerations
1. **Isolation**: Only TMDB requests use proxy
2. **Credentials**: Stored only in environment variables
3. **Logs**: Do not log sensitive credentials
4. **Timeout**: Configured timeout to prevent hanging
### Recommendations
- Use trusted proxies (Cloudflare WARP, etc.)
- Monitor logs for issues
- Test connectivity regularly
- Use HTTPS when possible
## Performance
### Optimizations
1. **Cache**: Addon maintains cache to reduce requests
2. **Timeout**: Configured timeout to prevent hanging
3. **Selective**: Only TMDB uses proxy, other requests are direct
### Monitoring
- Use `/api/proxy/status` to verify operation
- Monitor logs for latency
- Configure appropriate timeouts
## Troubleshooting
### Common Issues
1. **Proxy not connecting**
- Check if proxy is running
- Test with `curl --proxy`
- Verify configurations
2. **Timeout**
- Increase timeout in configurations
- Use closer proxy
- Check bandwidth
3. **Authentication error**
- Verify credentials
- Ensure `TMDB_PROXY_AUTH=true`
- Test authentication independently
### Debugging
```bash
# Test proxy independently
curl --proxy socks5://127.0.0.1:40000 https://api.themoviedb.org/3/configuration
# Check addon logs
docker logs tmdb-addon
# Test configuration
npm run test:proxy
```
## Extensibility
### Adding New Domains
To add new domains that should use proxy:
```javascript
// In httpClient.js
const TMDB_DOMAINS = [
'api.themoviedb.org',
'image.tmdb.org',
'www.themoviedb.org',
'new-domain.com' // Add here
];
```
### Support for New Protocols
The system uses axios, which automatically supports HTTP, HTTPS, SOCKS4 and SOCKS5.
## References
- [Axios Proxy Documentation](https://axios-http.com/docs/req_config)
- [Cloudflare WARP](https://developers.cloudflare.com/warp-client/)
- [AIOStreams Implementation](https://github.com/Viren070/AIOStreams/blob/3a53e3575672a299c98e297b31d6f9bc692b7e6b/packages/core/src/utils/http.ts#L96-L122)
+2 -1
View File
@@ -10,7 +10,8 @@
"build:dev": "vite build --mode development",
"lint": "eslint .",
"preview": "vite preview",
"start": "node addon/server.js"
"start": "node addon/server.js",
"test:proxy": "node test-proxy.js"
},
"repository": {
"type": "git",
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env node
/**
* Test script to verify proxy configuration
* Usage: node test-proxy.js
*/
require('dotenv').config();
const { testProxy, PROXY_CONFIG } = require('./addon/utils/httpClient');
async function runTests() {
console.log('🔍 Testing proxy configuration for TMDB Addon\n');
// Show current configuration
console.log('📋 Current configuration:');
console.log(` Enabled: ${PROXY_CONFIG.enabled}`);
console.log(` Host: ${PROXY_CONFIG.host}`);
console.log(` Port: ${PROXY_CONFIG.port}`);
console.log(` Protocol: ${PROXY_CONFIG.protocol}`);
console.log(` Authentication: ${PROXY_CONFIG.auth ? 'Yes' : 'No'}`);
console.log('');
if (!PROXY_CONFIG.enabled) {
console.log('⚠️ Proxy is not enabled');
console.log(' To enable, configure TMDB_PROXY_ENABLED=true');
return;
}
// Test connection
console.log('🧪 Testing proxy connection...');
try {
const isWorking = await testProxy();
if (isWorking) {
console.log('✅ Proxy working correctly!');
console.log(' The addon should be able to access TMDB through the proxy.');
} else {
console.log('❌ Proxy is not working');
console.log(' Check:');
console.log(' - If the proxy is running');
console.log(' - If the settings are correct');
console.log(' - If the proxy supports HTTPS');
}
} catch (error) {
console.log('❌ Error testing proxy:', error.message);
}
console.log('\n📖 For more information, see PROXY_SETUP.md');
}
// Run tests
runTests().catch(console.error);