From 894469ae0edd13bcdb96d25e1ccec40510adc879 Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 22 Dec 2025 01:15:01 +0530 Subject: [PATCH] added torrentio configuration to debrid integration page --- .gitignore | 3 +- ios/KSPlayerView.swift | 8 +- src/screens/DebridIntegrationScreen.tsx | 1236 +++++++++++++++++++---- trakt-docs/scrape-trakt-docs.js | 205 ---- 4 files changed, 1046 insertions(+), 406 deletions(-) delete mode 100644 trakt-docs/scrape-trakt-docs.js diff --git a/.gitignore b/.gitignore index c243a6e5..16951d6f 100644 --- a/.gitignore +++ b/.gitignore @@ -86,4 +86,5 @@ expofs.md ios/sentry.properties android/sentry.properties Stremio addons refer -trakt-docs \ No newline at end of file +trakt-docs +trakt-docss \ No newline at end of file diff --git a/ios/KSPlayerView.swift b/ios/KSPlayerView.swift index c932ba98..d81b2780 100644 --- a/ios/KSPlayerView.swift +++ b/ios/KSPlayerView.swift @@ -355,11 +355,11 @@ class KSPlayerView: UIView { // Disable native player remote control center integration; use RN controls options.registerRemoteControll = false - // PERFORMANCE OPTIMIZATION: Optimal buffer durations for high bitrate - // preferredForwardBufferDuration = 1.0s: Kept low to ensure instant start. + // PERFORMANCE OPTIMIZATION: Buffer durations for smooth high bitrate playback + // preferredForwardBufferDuration = 3.0s: Slightly increased to reduce rebuffering during playback options.preferredForwardBufferDuration = 1.0 - // maxBufferDuration = 60.0s: Increased to allow the player to cache more content ahead of time - options.maxBufferDuration = 60.0 + // maxBufferDuration = 120.0s: Increased to allow the player to cache more content ahead of time (2 minutes) + options.maxBufferDuration = 120.0 // Enable "second open" to relax startup/seek buffering thresholds (already enabled) options.isSecondOpen = true diff --git a/src/screens/DebridIntegrationScreen.tsx b/src/screens/DebridIntegrationScreen.tsx index 9c8a86c1..193bbf24 100644 --- a/src/screens/DebridIntegrationScreen.tsx +++ b/src/screens/DebridIntegrationScreen.tsx @@ -14,13 +14,14 @@ import { Image, Switch, ActivityIndicator, - RefreshControl + RefreshControl, + Dimensions } from 'react-native'; import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { RootStackParamList } from '../navigation/AppNavigator'; import { useTheme } from '../contexts/ThemeContext'; -import { Feather, MaterialCommunityIcons } from '@expo/vector-icons'; +import { Feather, MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons'; import { stremioService } from '../services/stremioService'; import { logger } from '../utils/logger'; import CustomAlert from '../components/CustomAlert'; @@ -30,6 +31,7 @@ import axios from 'axios'; const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0; const TORBOX_STORAGE_KEY = 'torbox_debrid_config'; const TORBOX_API_BASE = 'https://api.torbox.app/v1'; +const TORRENTIO_CONFIG_KEY = 'torrentio_config'; interface TorboxConfig { apiKey: string; @@ -48,6 +50,116 @@ interface TorboxUserData { base_email: string; } +// Torrentio Configuration Types +interface TorrentioConfig { + providers: string[]; + sort: string; + qualityFilter: string[]; + priorityLanguages: string[]; + maxResults: string; + debridService: string; + debridApiKey: string; + noDownloadLinks: boolean; + noCatalog: boolean; + isInstalled: boolean; + manifestUrl?: string; +} + +// Torrentio Options Data +const TORRENTIO_PROVIDERS = [ + { id: 'yts', name: 'YTS' }, + { id: 'eztv', name: 'EZTV' }, + { id: 'rarbg', name: 'RARBG' }, + { id: '1337x', name: '1337x' }, + { id: 'thepiratebay', name: 'ThePirateBay' }, + { id: 'kickasstorrents', name: 'KickassTorrents' }, + { id: 'torrentgalaxy', name: 'TorrentGalaxy' }, + { id: 'magnetdl', name: 'MagnetDL' }, + { id: 'horriblesubs', name: 'HorribleSubs' }, + { id: 'nyaasi', name: 'NyaaSi' }, + { id: 'tokyotosho', name: 'TokyoTosho' }, + { id: 'anidex', name: 'AniDex' }, + { id: 'rutor', name: 'šŸ‡·šŸ‡ŗ Rutor' }, + { id: 'rutracker', name: 'šŸ‡·šŸ‡ŗ Rutracker' }, + { id: 'comando', name: 'šŸ‡µšŸ‡¹ Comando' }, + { id: 'bludv', name: 'šŸ‡§šŸ‡· BluDV' }, + { id: 'torrent9', name: 'šŸ‡«šŸ‡· Torrent9' }, + { id: 'ilcorsaronero', name: 'šŸ‡®šŸ‡¹ ilCorSaRoNeRo' }, + { id: 'mejortorrent', name: 'šŸ‡ŖšŸ‡ø MejorTorrent' }, + { id: 'wolfmax4k', name: 'šŸ‡ŖšŸ‡ø Wolfmax4k' }, + { id: 'cinecalidad', name: 'šŸ‡²šŸ‡½ Cinecalidad' }, +]; + +const TORRENTIO_SORT_OPTIONS = [ + { id: 'quality', name: 'By quality then seeders' }, + { id: 'qualitysize', name: 'By quality then size' }, + { id: 'seeders', name: 'By seeders' }, + { id: 'size', name: 'By size' }, +]; + +const TORRENTIO_QUALITY_FILTERS = [ + { id: 'brremux', name: 'BluRay REMUX' }, + { id: 'hdrall', name: 'HDR/HDR10+/Dolby Vision' }, + { id: 'dolbyvision', name: 'Dolby Vision' }, + { id: '4k', name: '4K' }, + { id: '1080p', name: '1080p' }, + { id: '720p', name: '720p' }, + { id: '480p', name: '480p' }, + { id: 'scr', name: 'Screener' }, + { id: 'cam', name: 'CAM' }, + { id: 'unknown', name: 'Unknown' }, +]; + +const TORRENTIO_LANGUAGES = [ + { id: 'english', name: 'šŸ‡¬šŸ‡§ English' }, + { id: 'russian', name: 'šŸ‡·šŸ‡ŗ Russian' }, + { id: 'italian', name: 'šŸ‡®šŸ‡¹ Italian' }, + { id: 'portuguese', name: 'šŸ‡µšŸ‡¹ Portuguese' }, + { id: 'spanish', name: 'šŸ‡ŖšŸ‡ø Spanish' }, + { id: 'latino', name: 'šŸ‡²šŸ‡½ Latino' }, + { id: 'korean', name: 'šŸ‡°šŸ‡· Korean' }, + { id: 'chinese', name: 'šŸ‡ØšŸ‡³ Chinese' }, + { id: 'french', name: 'šŸ‡«šŸ‡· French' }, + { id: 'german', name: 'šŸ‡©šŸ‡Ŗ German' }, + { id: 'dutch', name: 'šŸ‡³šŸ‡± Dutch' }, + { id: 'hindi', name: 'šŸ‡®šŸ‡³ Hindi' }, + { id: 'japanese', name: 'šŸ‡ÆšŸ‡µ Japanese' }, + { id: 'polish', name: 'šŸ‡µšŸ‡± Polish' }, + { id: 'arabic', name: 'šŸ‡øšŸ‡¦ Arabic' }, + { id: 'turkish', name: 'šŸ‡¹šŸ‡· Turkish' }, +]; + +const TORRENTIO_DEBRID_SERVICES = [ + { id: 'torbox', name: 'TorBox', keyParam: 'torbox' }, + { id: 'realdebrid', name: 'RealDebrid', keyParam: 'realdebrid' }, + { id: 'alldebrid', name: 'AllDebrid', keyParam: 'alldebrid' }, + { id: 'premiumize', name: 'Premiumize', keyParam: 'premiumize' }, + { id: 'debridlink', name: 'DebridLink', keyParam: 'debridlink' }, + { id: 'offcloud', name: 'Offcloud', keyParam: 'offcloud' }, +]; + +const TORRENTIO_MAX_RESULTS = [ + { id: '', name: 'All results' }, + { id: '1', name: '1 per quality' }, + { id: '2', name: '2 per quality' }, + { id: '3', name: '3 per quality' }, + { id: '5', name: '5 per quality' }, + { id: '10', name: '10 per quality' }, +]; + +const DEFAULT_TORRENTIO_CONFIG: TorrentioConfig = { + providers: TORRENTIO_PROVIDERS.map(p => p.id), // All providers by default + sort: 'quality', + qualityFilter: ['scr', 'cam'], + priorityLanguages: [], + maxResults: '', + debridService: 'torbox', // Default to TorBox + debridApiKey: '', + noDownloadLinks: false, + noCatalog: false, + isInstalled: false, +}; + const getPlanName = (plan: number): string => { switch (plan) { case 0: return 'Free'; @@ -57,6 +169,7 @@ const getPlanName = (plan: number): string => { default: return 'Unknown'; } }; + const createStyles = (colors: any) => StyleSheet.create({ container: { flex: 1, @@ -79,6 +192,31 @@ const createStyles = (colors: any) => StyleSheet.create({ color: colors.white, letterSpacing: 0.3, }, + tabContainer: { + flexDirection: 'row', + marginHorizontal: 16, + marginBottom: 16, + backgroundColor: colors.elevation1, + borderRadius: 12, + padding: 4, + }, + tab: { + flex: 1, + paddingVertical: 12, + alignItems: 'center', + borderRadius: 10, + }, + activeTab: { + backgroundColor: colors.primary, + }, + tabText: { + fontSize: 14, + fontWeight: '600', + color: colors.mediumEmphasis, + }, + activeTabText: { + color: colors.white, + }, content: { flex: 1, paddingHorizontal: 16, @@ -86,9 +224,8 @@ const createStyles = (colors: any) => StyleSheet.create({ description: { fontSize: 14, color: colors.mediumEmphasis, - marginBottom: 16, + marginBottom: 12, lineHeight: 20, - opacity: 0.9, }, statusCard: { backgroundColor: colors.elevation2, @@ -348,7 +485,205 @@ const createStyles = (colors: any) => StyleSheet.create({ textAlign: 'center', marginTop: 8, opacity: 0.6, - } + }, + // Torrentio specific styles + configSection: { + backgroundColor: colors.elevation2, + borderRadius: 12, + padding: 16, + marginBottom: 16, + }, + configSectionTitle: { + fontSize: 14, + fontWeight: '700', + color: colors.white, + marginBottom: 12, + textTransform: 'uppercase', + letterSpacing: 0.5, + }, + chipContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 8, + }, + chip: { + paddingHorizontal: 12, + paddingVertical: 8, + borderRadius: 20, + backgroundColor: colors.elevation3, + borderWidth: 1, + borderColor: 'transparent', + }, + chipSelected: { + backgroundColor: colors.primary + '30', + borderColor: colors.primary, + }, + chipText: { + fontSize: 13, + color: colors.mediumEmphasis, + }, + chipTextSelected: { + color: colors.primary, + fontWeight: '600', + }, + pickerContainer: { + backgroundColor: colors.elevation3, + borderRadius: 10, + overflow: 'hidden', + }, + pickerItem: { + paddingVertical: 12, + paddingHorizontal: 16, + borderBottomWidth: 1, + borderBottomColor: colors.elevation2, + }, + pickerItemSelected: { + backgroundColor: colors.primary + '20', + }, + pickerItemText: { + fontSize: 14, + color: colors.white, + }, + pickerItemTextSelected: { + color: colors.primary, + fontWeight: '600', + }, + switchRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 8, + }, + switchLabel: { + fontSize: 14, + color: colors.white, + flex: 1, + }, + warningCard: { + backgroundColor: colors.warning + '20', + borderRadius: 10, + padding: 16, + marginBottom: 16, + flexDirection: 'row', + alignItems: 'center', + }, + warningText: { + fontSize: 13, + color: colors.warning || '#FFC107', + marginLeft: 12, + flex: 1, + lineHeight: 18, + }, + manifestPreview: { + backgroundColor: colors.elevation3, + borderRadius: 8, + padding: 12, + marginTop: 12, + }, + manifestUrl: { + fontSize: 11, + color: colors.mediumEmphasis, + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + }, + installedBadge: { + backgroundColor: colors.success + '20', + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 8, + alignSelf: 'flex-start', + marginBottom: 12, + }, + installedBadgeText: { + color: colors.success || '#4CAF50', + fontSize: 12, + fontWeight: '700', + }, + selectAllButton: { + paddingVertical: 6, + paddingHorizontal: 12, + backgroundColor: colors.elevation3, + borderRadius: 6, + marginBottom: 8, + alignSelf: 'flex-start', + }, + selectAllText: { + fontSize: 12, + color: colors.primary, + fontWeight: '600', + }, + // Accordion styles + accordionHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: colors.elevation2, + borderRadius: 12, + padding: 16, + marginBottom: 16, + }, + accordionHeaderText: { + fontSize: 14, + fontWeight: '600', + color: colors.white, + }, + accordionSubtext: { + fontSize: 12, + color: colors.mediumEmphasis, + marginTop: 2, + }, + accordionContent: { + backgroundColor: colors.elevation2, + borderRadius: 12, + padding: 16, + marginBottom: 16, + marginTop: -16, + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + promoCard: { + backgroundColor: colors.primary + '15', + borderRadius: 12, + padding: 16, + marginBottom: 16, + borderWidth: 1, + borderColor: colors.primary + '30', + }, + promoTitle: { + fontSize: 15, + fontWeight: '700', + color: colors.white, + marginBottom: 6, + }, + promoText: { + fontSize: 13, + color: colors.mediumEmphasis, + lineHeight: 18, + marginBottom: 12, + }, + promoButton: { + backgroundColor: colors.primary, + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 8, + alignSelf: 'flex-start', + }, + promoButtonText: { + color: colors.white, + fontWeight: '700', + fontSize: 13, + }, + recommendedBadge: { + backgroundColor: colors.primary, + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 4, + marginLeft: 8, + }, + recommendedText: { + fontSize: 10, + color: colors.white, + fontWeight: '700', + }, }); const DebridIntegrationScreen = () => { @@ -357,6 +692,10 @@ const DebridIntegrationScreen = () => { const colors = currentTheme.colors; const styles = createStyles(colors); + // Tab state + const [activeTab, setActiveTab] = useState<'torbox' | 'torrentio'>('torbox'); + + // Torbox state const [apiKey, setApiKey] = useState(''); const [loading, setLoading] = useState(false); const [initialLoading, setInitialLoading] = useState(true); @@ -365,12 +704,26 @@ const DebridIntegrationScreen = () => { const [userDataLoading, setUserDataLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); + // Torrentio state + const [torrentioConfig, setTorrentioConfig] = useState(DEFAULT_TORRENTIO_CONFIG); + const [torrentioLoading, setTorrentioLoading] = useState(false); + + // Accordion states for collapsible sections + const [expandedSections, setExpandedSections] = useState<{ [key: string]: boolean }>({ + sorting: false, + qualityFilter: false, + languages: false, + maxResults: false, + options: false, + }); + // Alert state const [alertVisible, setAlertVisible] = useState(false); const [alertTitle, setAlertTitle] = useState(''); const [alertMessage, setAlertMessage] = useState(''); const [alertActions, setAlertActions] = useState([]); + // Load Torbox config const loadConfig = useCallback(async () => { try { const storedConfig = await mmkvStorage.getItem(TORBOX_STORAGE_KEY); @@ -387,12 +740,10 @@ const DebridIntegrationScreen = () => { ); if (torboxAddon && !parsedConfig.isConnected) { - // Update config if addon exists but config says not connected const updatedConfig = { ...parsedConfig, isConnected: true, addonId: torboxAddon.id }; setConfig(updatedConfig); await mmkvStorage.setItem(TORBOX_STORAGE_KEY, JSON.stringify(updatedConfig)); } else if (!torboxAddon && parsedConfig.isConnected) { - // Update config if addon doesn't exist but config says connected const updatedConfig = { ...parsedConfig, isConnected: false, addonId: undefined }; setConfig(updatedConfig); await mmkvStorage.setItem(TORBOX_STORAGE_KEY, JSON.stringify(updatedConfig)); @@ -405,6 +756,35 @@ const DebridIntegrationScreen = () => { } }, []); + // Load Torrentio config + const loadTorrentioConfig = useCallback(async () => { + try { + const storedConfig = await mmkvStorage.getItem(TORRENTIO_CONFIG_KEY); + if (storedConfig) { + const parsedConfig = JSON.parse(storedConfig); + setTorrentioConfig(parsedConfig); + } + + // Check if Torrentio addon is installed + const addons = await stremioService.getInstalledAddonsAsync(); + const torrentioAddon = addons.find(addon => + addon.id?.includes('torrentio') || + addon.url?.includes('torrentio.strem.fun') || + (addon as any).transport?.includes('torrentio.strem.fun') + ); + + if (torrentioAddon) { + setTorrentioConfig(prev => ({ + ...prev, + isInstalled: true, + manifestUrl: (torrentioAddon as any).transport || torrentioAddon.url + })); + } + } catch (error) { + logger.error('Failed to load Torrentio config:', error); + } + }, []); + const fetchUserData = useCallback(async () => { if (!config?.apiKey || !config?.isConnected) return; @@ -424,7 +804,6 @@ const DebridIntegrationScreen = () => { } } catch (error) { logger.error('Failed to fetch Torbox user data:', error); - // Don't show error to user, just log it } finally { setUserDataLoading(false); } @@ -433,7 +812,8 @@ const DebridIntegrationScreen = () => { useFocusEffect( useCallback(() => { loadConfig(); - }, [loadConfig]) + loadTorrentioConfig(); + }, [loadConfig, loadTorrentioConfig]) ); useEffect(() => { @@ -444,10 +824,11 @@ const DebridIntegrationScreen = () => { const onRefresh = useCallback(async () => { setRefreshing(true); - await Promise.all([loadConfig(), fetchUserData()]); + await Promise.all([loadConfig(), loadTorrentioConfig(), fetchUserData()]); setRefreshing(false); - }, [loadConfig, fetchUserData]); + }, [loadConfig, loadTorrentioConfig, fetchUserData]); + // Torbox handlers const handleConnect = async () => { if (!apiKey.trim()) { setAlertTitle('Error'); @@ -460,11 +841,8 @@ const DebridIntegrationScreen = () => { setLoading(true); try { const manifestUrl = `https://stremio.torbox.app/${apiKey.trim()}/manifest.json`; - - // Install the addon using stremioService await stremioService.installAddon(manifestUrl); - // Get the installed addon ID const addons = await stremioService.getInstalledAddonsAsync(); const torboxAddon = addons.find(addon => addon.id?.includes('torbox') || @@ -472,7 +850,6 @@ const DebridIntegrationScreen = () => { (addon as any).transport?.includes('torbox') ); - // Save config const newConfig: TorboxConfig = { apiKey: apiKey.trim(), isConnected: true, @@ -485,10 +862,7 @@ const DebridIntegrationScreen = () => { setAlertTitle('Success'); setAlertMessage('Torbox addon connected successfully!'); - setAlertActions([{ - label: 'OK', - onPress: () => setAlertVisible(false) - }]); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); setAlertVisible(true); } catch (error) { logger.error('Failed to install Torbox addon:', error); @@ -508,9 +882,6 @@ const DebridIntegrationScreen = () => { const updatedConfig = { ...config, isEnabled: enabled }; await mmkvStorage.setItem(TORBOX_STORAGE_KEY, JSON.stringify(updatedConfig)); setConfig(updatedConfig); - - // Note: Since we can't disable/enable addons in the current stremioService, - // we'll just track the state. The addon filtering will happen in AddonsScreen } catch (error) { logger.error('Failed to toggle Torbox addon:', error); } @@ -527,7 +898,6 @@ const DebridIntegrationScreen = () => { setAlertVisible(false); setLoading(true); try { - // Find and remove the torbox addon const addons = await stremioService.getInstalledAddonsAsync(); const torboxAddon = addons.find(addon => addon.id?.includes('torbox') || @@ -539,9 +909,9 @@ const DebridIntegrationScreen = () => { await stremioService.removeAddon(torboxAddon.id); } - // Clear config await mmkvStorage.removeItem(TORBOX_STORAGE_KEY); setConfig(null); + setUserData(null); setAlertTitle('Success'); setAlertMessage('Torbox disconnected successfully'); @@ -567,6 +937,631 @@ const DebridIntegrationScreen = () => { Linking.openURL('https://torbox.app/subscription?referral=493192f2-6403-440f-b414-768f72222ec7'); }; + // Torrentio handlers + const generateTorrentioManifestUrl = useCallback((): string => { + const parts: string[] = []; + + // Providers (only if not all selected) + if (torrentioConfig.providers.length > 0 && torrentioConfig.providers.length < TORRENTIO_PROVIDERS.length) { + parts.push(`providers=${torrentioConfig.providers.join(',')}`); + } + + // Sort (only if not default) + if (torrentioConfig.sort && torrentioConfig.sort !== 'quality') { + parts.push(`sort=${torrentioConfig.sort}`); + } + + // Quality filter + if (torrentioConfig.qualityFilter.length > 0) { + parts.push(`qualityfilter=${torrentioConfig.qualityFilter.join(',')}`); + } + + // Priority languages + if (torrentioConfig.priorityLanguages.length > 0) { + parts.push(`language=${torrentioConfig.priorityLanguages.join(',')}`); + } + + // Max results + if (torrentioConfig.maxResults) { + parts.push(`limit=${torrentioConfig.maxResults}`); + } + + // Debrid service and API key + if (torrentioConfig.debridService !== 'none' && torrentioConfig.debridApiKey) { + const debridInfo = TORRENTIO_DEBRID_SERVICES.find(d => d.id === torrentioConfig.debridService); + if (debridInfo) { + parts.push(`${debridInfo.keyParam}=${torrentioConfig.debridApiKey}`); + } + } + + // Options + if (torrentioConfig.noDownloadLinks) { + parts.push('nodownloadlinks=true'); + } + if (torrentioConfig.noCatalog) { + parts.push('nocatalog=true'); + } + + const configString = parts.length > 0 ? parts.join('|') + '/' : ''; + return `https://torrentio.strem.fun/${configString}manifest.json`; + }, [torrentioConfig]); + + const toggleQualityFilter = (qualityId: string) => { + setTorrentioConfig(prev => { + const newFilters = prev.qualityFilter.includes(qualityId) + ? prev.qualityFilter.filter(q => q !== qualityId) + : [...prev.qualityFilter, qualityId]; + return { ...prev, qualityFilter: newFilters }; + }); + }; + + const toggleLanguage = (langId: string) => { + setTorrentioConfig(prev => { + const newLangs = prev.priorityLanguages.includes(langId) + ? prev.priorityLanguages.filter(l => l !== langId) + : [...prev.priorityLanguages, langId]; + return { ...prev, priorityLanguages: newLangs }; + }); + }; + + const handleInstallTorrentio = async () => { + // Check if API key is provided + if (!torrentioConfig.debridApiKey.trim()) { + setAlertTitle('API Key Required'); + setAlertMessage('Please enter your debrid service API key to install Torrentio.'); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); + setAlertVisible(true); + return; + } + + setTorrentioLoading(true); + try { + const manifestUrl = generateTorrentioManifestUrl(); + + // Check if already installed + const addons = await stremioService.getInstalledAddonsAsync(); + const existingTorrentio = addons.find(addon => + addon.id?.includes('torrentio') || + addon.url?.includes('torrentio.strem.fun') || + (addon as any).transport?.includes('torrentio.strem.fun') + ); + + if (existingTorrentio) { + // Remove existing and reinstall with new config + await stremioService.removeAddon(existingTorrentio.id); + } + + await stremioService.installAddon(manifestUrl); + + // Save config + const newConfig = { + ...torrentioConfig, + isInstalled: true, + manifestUrl + }; + await mmkvStorage.setItem(TORRENTIO_CONFIG_KEY, JSON.stringify(newConfig)); + setTorrentioConfig(newConfig); + + setAlertTitle('Success'); + setAlertMessage('Torrentio addon installed successfully!'); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); + setAlertVisible(true); + } catch (error) { + logger.error('Failed to install Torrentio addon:', error); + setAlertTitle('Error'); + setAlertMessage('Failed to install Torrentio addon. Please try again.'); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); + setAlertVisible(true); + } finally { + setTorrentioLoading(false); + } + }; + + const handleRemoveTorrentio = async () => { + setAlertTitle('Remove Torrentio'); + setAlertMessage('Are you sure you want to remove the Torrentio addon?'); + setAlertActions([ + { label: 'Cancel', onPress: () => setAlertVisible(false), style: { color: colors.mediumGray } }, + { + label: 'Remove', + onPress: async () => { + setAlertVisible(false); + setTorrentioLoading(true); + try { + const addons = await stremioService.getInstalledAddonsAsync(); + const torrentioAddon = addons.find(addon => + addon.id?.includes('torrentio') || + addon.url?.includes('torrentio.strem.fun') || + (addon as any).transport?.includes('torrentio.strem.fun') + ); + + if (torrentioAddon) { + await stremioService.removeAddon(torrentioAddon.id); + } + + const newConfig = { + ...torrentioConfig, + isInstalled: false, + manifestUrl: undefined + }; + await mmkvStorage.setItem(TORRENTIO_CONFIG_KEY, JSON.stringify(newConfig)); + setTorrentioConfig(newConfig); + + setAlertTitle('Success'); + setAlertMessage('Torrentio addon removed successfully'); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); + setAlertVisible(true); + } catch (error) { + logger.error('Failed to remove Torrentio:', error); + setAlertTitle('Error'); + setAlertMessage('Failed to remove Torrentio addon'); + setAlertActions([{ label: 'OK', onPress: () => setAlertVisible(false) }]); + setAlertVisible(true); + } finally { + setTorrentioLoading(false); + } + }, + style: { color: colors.error || '#F44336' } + } + ]); + setAlertVisible(true); + }; + + // Render Torbox Tab + const renderTorboxTab = () => ( + <> + {config?.isConnected ? ( + <> + + + Status + Connected + + + + + + Enable Addon + + + + + + + {loading ? 'Disconnecting...' : 'Disconnect & Remove'} + + + + {userData && ( + + + Account Information + {userDataLoading && ( + + )} + + + + Email + + {userData.base_email || userData.email} + + + + + Plan + + + {getPlanName(userData.plan)} + + + + + + Status + + {userData.is_subscribed ? 'Active' : 'Free'} + + + + {userData.premium_expires_at && ( + + Expires + + {new Date(userData.premium_expires_at).toLocaleDateString()} + + + )} + + + Downloaded + + {(userData.total_downloaded / (1024 * 1024 * 1024)).toFixed(2)} GB + + + + )} + + + āœ“ Connected to TorBox + + Your TorBox addon is active and providing premium streams.{config.isEnabled ? '' : ' (Currently disabled)'} + + + + + Configure Addon + + Customize your streaming experience. Sort by quality, filter file sizes, and manage other integration settings. + + Linking.openURL('https://torbox.app/settings?section=integration-settings')} + > + Open Settings + + + + ) : ( + <> + + Unlock 4K high-quality streams and lightning-fast speeds by integrating Torbox. Enter your API Key below to instantly upgrade your streaming experience. + + + Linking.openURL('https://guides.viren070.me/stremio/technical-details#debrid-services')} style={styles.guideLink}> + What is a Debrid Service? + + + + Torbox API Key + + + + + + {loading ? 'Connecting...' : 'Connect & Install'} + + + + + Unlock Premium Speeds + + Get a Torbox subscription to access cached high-quality streams with zero buffering. + + + Get Subscription + + + + )} + + + Powered by + + + TorBox + + Nuvio is not affiliated with Torbox in any way. + + + ); + + // Render Torrentio Tab + const toggleSection = (section: string) => { + setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })); + }; + + const renderTorrentioTab = () => ( + <> + + Configure Torrentio to get torrent streams for movies and TV shows. A debrid service is required to stream content. + + + {torrentioConfig.isInstalled && ( + + āœ“ INSTALLED + + )} + + {/* TorBox Promotion Card */} + {!torrentioConfig.debridApiKey && ( + + ⚔ Need a Debrid Service? + + Get TorBox for lightning-fast 4K streaming with zero buffering. Premium cached torrents and instant downloads. + + Linking.openURL('https://torbox.app/subscription?referral=493192f2-6403-440f-b414-768f72222ec7')} + > + Get TorBox Subscription + + + )} + + {/* Debrid Service Selection */} + + Debrid Service * + + {TORRENTIO_DEBRID_SERVICES.map((service: any) => ( + setTorrentioConfig(prev => ({ ...prev, debridService: service.id }))} + > + + {service.name} + + + ))} + + + + {/* Debrid API Key */} + + API Key * + d.id === torrentioConfig.debridService)?.name || 'Debrid'} API Key`} + placeholderTextColor={colors.mediumGray} + value={torrentioConfig.debridApiKey} + onChangeText={(text) => setTorrentioConfig(prev => ({ ...prev, debridApiKey: text }))} + autoCapitalize="none" + autoCorrect={false} + secureTextEntry + /> + + + {/* Sorting - Accordion */} + toggleSection('sorting')} + > + + Sorting + + {TORRENTIO_SORT_OPTIONS.find(o => o.id === torrentioConfig.sort)?.name || 'By quality'} + + + + + {expandedSections.sorting && ( + + + {TORRENTIO_SORT_OPTIONS.map(option => ( + setTorrentioConfig(prev => ({ ...prev, sort: option.id }))} + > + + {option.name} + + + ))} + + + )} + + {/* Quality Filter - Accordion */} + toggleSection('qualityFilter')} + > + + Exclude Qualities + + {torrentioConfig.qualityFilter.length > 0 ? `${torrentioConfig.qualityFilter.length} excluded` : 'None excluded'} + + + + + {expandedSections.qualityFilter && ( + + + {TORRENTIO_QUALITY_FILTERS.map(quality => ( + toggleQualityFilter(quality.id)} + > + + {quality.name} + + + ))} + + + )} + + {/* Priority Languages - Accordion */} + toggleSection('languages')} + > + + Priority Languages + + {torrentioConfig.priorityLanguages.length > 0 ? `${torrentioConfig.priorityLanguages.length} selected` : 'No preference'} + + + + + {expandedSections.languages && ( + + + {TORRENTIO_LANGUAGES.map(lang => ( + toggleLanguage(lang.id)} + > + + {lang.name} + + + ))} + + + )} + + {/* Max Results - Accordion */} + toggleSection('maxResults')} + > + + Max Results + + {TORRENTIO_MAX_RESULTS.find(o => o.id === torrentioConfig.maxResults)?.name || 'All results'} + + + + + {expandedSections.maxResults && ( + + + {TORRENTIO_MAX_RESULTS.map(option => ( + setTorrentioConfig(prev => ({ ...prev, maxResults: option.id }))} + > + + {option.name} + + + ))} + + + )} + + {/* Additional Options - Accordion */} + toggleSection('options')} + > + + Additional Options + Catalog & download settings + + + + {expandedSections.options && ( + + + Don't show download links + setTorrentioConfig(prev => ({ ...prev, noDownloadLinks: val }))} + trackColor={{ false: colors.elevation3, true: colors.primary }} + thumbColor={colors.white} + /> + + + Don't show debrid catalog + setTorrentioConfig(prev => ({ ...prev, noCatalog: val }))} + trackColor={{ false: colors.elevation3, true: colors.primary }} + thumbColor={colors.white} + /> + + + )} + + {/* Manifest URL Preview */} + + Manifest URL + + + {generateTorrentioManifestUrl()} + + + + + {/* Install/Update/Remove Buttons */} + + {torrentioConfig.isInstalled ? ( + <> + + + {torrentioLoading ? 'Updating...' : 'Update Configuration'} + + + + Remove Torrentio + + + ) : ( + + + {torrentioLoading ? 'Installing...' : 'Install Torrentio'} + + + )} + + + + Nuvio is not affiliated with Torrentio in any way. + + + ); + if (initialLoading) { return ( @@ -592,6 +1587,26 @@ const DebridIntegrationScreen = () => { Debrid Integration + {/* Tab Selector */} + + setActiveTab('torbox')} + > + + TorBox + + + setActiveTab('torrentio')} + > + + Torrentio + + + + { /> } > - {config?.isConnected ? ( - // Connected state - <> - - - Status - Connected - - - - - - Enable Addon - - - - - - - {loading ? 'Disconnecting...' : 'Disconnect & Remove'} - - - - {/* User Data Card */} - {userData && ( - - - Account Information - {userDataLoading && ( - - )} - - - - Email - - {userData.base_email || userData.email} - - - - - Plan - - - {getPlanName(userData.plan)} - - - - - - Status - - {userData.is_subscribed ? 'Active' : 'Free'} - - - - {userData.premium_expires_at && ( - - Expires - - {new Date(userData.premium_expires_at).toLocaleDateString()} - - - )} - - - Downloaded - - {(userData.total_downloaded / (1024 * 1024 * 1024)).toFixed(2)} GB - - - - )} - - - āœ“ Connected to TorBox - - Your TorBox addon is active and providing premium streams.{config.isEnabled ? '' : ' (Currently disabled)'} - - - - - Configure Addon - - Customize your streaming experience. Sort by quality, filter file sizes, and manage other integration settings. - - Linking.openURL('https://torbox.app/settings?section=integration-settings')} - > - Open Settings - - - - ) : ( - // Not connected state - <> - - Unlock 4K high-quality streams and lightning-fast speeds by integrating Torbox. Enter your API Key below to instantly upgrade your streaming experience. - - - Linking.openURL('https://guides.viren070.me/stremio/technical-details#debrid-services')} style={styles.guideLink}> - What is a Debrid Service? - - - - Torbox API Key - - - - - - {loading ? 'Connecting...' : 'Connect & Install'} - - - - - Unlock Premium Speeds - - Get a Torbox subscription to access cached high-quality streams with zero buffering. - - - Get Subscription - - - - )} - - - Powered by - - - TorBox - - Nuvio is not affiliated with Torbox in any way. - + {activeTab === 'torbox' ? renderTorboxTab() : renderTorrentioTab()} diff --git a/trakt-docs/scrape-trakt-docs.js b/trakt-docs/scrape-trakt-docs.js deleted file mode 100644 index 7b5d44ca..00000000 --- a/trakt-docs/scrape-trakt-docs.js +++ /dev/null @@ -1,205 +0,0 @@ -const https = require('https'); -const fs = require('fs'); -const path = require('path'); - -const API_BLUEPRINT_URL = 'https://jsapi.apiary.io/apis/trakt.apib'; - -// Category mapping based on group names -const CATEGORIES = { - 'introduction': { file: '01-introduction.md', title: 'Introduction' }, - 'authentication-oauth': { file: '02-authentication-oauth.md', title: 'Authentication - OAuth' }, - 'authentication-devices': { file: '03-authentication-devices.md', title: 'Authentication - Devices' }, - 'calendars': { file: '04-calendars.md', title: 'Calendars' }, - 'checkin': { file: '05-checkin.md', title: 'Checkin' }, - 'certifications': { file: '06-certifications.md', title: 'Certifications' }, - 'comments': { file: '07-comments.md', title: 'Comments' }, - 'countries': { file: '08-countries.md', title: 'Countries' }, - 'genres': { file: '09-genres.md', title: 'Genres' }, - 'languages': { file: '10-languages.md', title: 'Languages' }, - 'lists': { file: '11-lists.md', title: 'Lists' }, - 'movies': { file: '12-movies.md', title: 'Movies' }, - 'networks': { file: '13-networks.md', title: 'Networks' }, - 'notes': { file: '14-notes.md', title: 'Notes' }, - 'people': { file: '15-people.md', title: 'People' }, - 'recommendations': { file: '16-recommendations.md', title: 'Recommendations' }, - 'scrobble': { file: '17-scrobble.md', title: 'Scrobble' }, - 'search': { file: '18-search.md', title: 'Search' }, - 'shows': { file: '19-shows.md', title: 'Shows' }, - 'seasons': { file: '20-seasons.md', title: 'Seasons' }, - 'episodes': { file: '21-episodes.md', title: 'Episodes' }, - 'sync': { file: '22-sync.md', title: 'Sync' }, - 'users': { file: '23-users.md', title: 'Users' }, -}; - -function fetchUrl(url) { - return new Promise((resolve, reject) => { - https.get(url, (res) => { - let data = ''; - res.on('data', chunk => data += chunk); - res.on('end', () => resolve(data)); - res.on('error', reject); - }).on('error', reject); - }); -} - -function parseApiBlueprint(content) { - const sections = {}; - let currentGroup = 'introduction'; - let currentContent = []; - - const lines = content.split('\n'); - - for (const line of lines) { - // Detect group headers like "# Group Authentication - OAuth" - const groupMatch = line.match(/^#\s+Group\s+(.+)$/i); - if (groupMatch) { - // Save previous group - if (currentContent.length > 0) { - if (!sections[currentGroup]) sections[currentGroup] = []; - sections[currentGroup].push(...currentContent); - } - - // Start new group - const groupName = groupMatch[1].toLowerCase().replace(/\s+/g, '-'); - currentGroup = groupName; - currentContent = [`# ${groupMatch[1]}\n`]; - continue; - } - - currentContent.push(line); - } - - // Save last group - if (currentContent.length > 0) { - if (!sections[currentGroup]) sections[currentGroup] = []; - sections[currentGroup].push(...currentContent); - } - - return sections; -} - -function convertApiBlueprintToMarkdown(content) { - let md = content; - - // Convert API Blueprint specific syntax to markdown - // Parameters section - md = md.replace(/\+ Parameters/g, '### Parameters'); - - // Request/Response sections - md = md.replace(/\+ Request \(([^)]+)\)/g, '### Request ($1)'); - md = md.replace(/\+ Response (\d+)(?: \(([^)]+)\))?/g, (match, code, type) => { - return type ? `### Response ${code} (${type})` : `### Response ${code}`; - }); - - // Body sections - md = md.replace(/\+ Body/g, '**Body:**'); - - // Headers - md = md.replace(/\+ Headers/g, '**Headers:**'); - - // Attributes - md = md.replace(/\+ Attributes/g, '### Attributes'); - - // Clean up indentation for code blocks - md = md.replace(/^ /gm, ' '); - - return md; -} - -async function main() { - console.log('šŸ”„ Fetching Trakt API Blueprint...'); - - try { - const content = await fetchUrl(API_BLUEPRINT_URL); - console.log(`āœ… Fetched ${content.length} bytes`); - - // Save raw blueprint - fs.writeFileSync(path.join(__dirname, 'raw-api-blueprint.apib'), content); - console.log('šŸ“ Saved raw API Blueprint'); - - // Parse and organize by groups - const sections = parseApiBlueprint(content); - console.log(`šŸ“‚ Found ${Object.keys(sections).length} sections`); - - // Create markdown files for each category - for (const [groupKey, lines] of Object.entries(sections)) { - const category = CATEGORIES[groupKey]; - const fileName = category ? category.file : `${groupKey}.md`; - const title = category ? category.title : groupKey; - - let mdContent = lines.join('\n'); - mdContent = convertApiBlueprintToMarkdown(mdContent); - - // Add header if not present - if (!mdContent.startsWith('# ')) { - mdContent = `# ${title}\n\n${mdContent}`; - } - - const filePath = path.join(__dirname, fileName); - fs.writeFileSync(filePath, mdContent); - console.log(`āœ… Created ${fileName}`); - } - - // Create README - const readme = generateReadme(Object.keys(sections)); - fs.writeFileSync(path.join(__dirname, 'README.md'), readme); - console.log('āœ… Created README.md'); - - console.log('\nšŸŽ‰ Done! All documentation files created.'); - - } catch (error) { - console.error('āŒ Error:', error.message); - process.exit(1); - } -} - -function generateReadme(groups) { - let md = `# Trakt API Documentation - -This folder contains the complete Trakt API documentation, scraped from [trakt.docs.apiary.io](https://trakt.docs.apiary.io/). - -## API Base URL - -\`\`\` -https://api.trakt.tv -\`\`\` - -## Documentation Files - -`; - - for (const groupKey of groups) { - const category = CATEGORIES[groupKey]; - if (category) { - md += `- [${category.title}](./${category.file})\n`; - } else { - md += `- [${groupKey}](./${groupKey}.md)\n`; - } - } - - md += ` -## Quick Reference - -### Required Headers - -| Header | Value | -|---|---| -| \`Content-Type\` | \`application/json\` | -| \`trakt-api-key\` | Your \`client_id\` | -| \`trakt-api-version\` | \`2\` | -| \`Authorization\` | \`Bearer [access_token]\` (for authenticated endpoints) | - -### Useful Links - -- [Create API App](https://trakt.tv/oauth/applications/new) -- [GitHub Developer Forum](https://github.com/trakt/api-help/issues) -- [API Blog](https://apiblog.trakt.tv) - ---- -*Generated on ${new Date().toISOString()}* -`; - - return md; -} - -main();