mirror of
https://github.com/Viren070/tmdb-addon.git
synced 2025-12-01 23:18:11 +01:00
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:
+18
-4
@@ -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
|
||||
@@ -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
@@ -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' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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") {
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ async function issueExistsOnGithub(title) {
|
||||
}
|
||||
|
||||
async function checkSeasonsAndReport(tmdbId, imdbId, resp, name) {
|
||||
// Se não há 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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user