mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-28 20:03:34 +00:00
refactor for tf
This commit is contained in:
parent
8a34bf6678
commit
9cc8b2ea67
6 changed files with 431 additions and 602 deletions
|
|
@ -155,7 +155,7 @@ export type RootStackParamList = {
|
|||
TraktSettings: undefined;
|
||||
PlayerSettings: undefined;
|
||||
ThemeSettings: undefined;
|
||||
ScraperSettings: undefined;
|
||||
PluginSettings: undefined;
|
||||
CastMovies: {
|
||||
castMember: {
|
||||
id: number;
|
||||
|
|
@ -1463,7 +1463,7 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ScraperSettings"
|
||||
name="PluginSettings"
|
||||
component={PluginsScreen}
|
||||
options={{
|
||||
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ if (Platform.OS === 'ios') {
|
|||
}
|
||||
}
|
||||
// Removed community blur and expo-constants for Android overlay
|
||||
import axios from 'axios';
|
||||
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
||||
// Extend Manifest type to include logo only (remove disabled status)
|
||||
|
|
@ -60,11 +60,7 @@ interface ExtendedManifest extends Manifest {
|
|||
};
|
||||
}
|
||||
|
||||
// Interface for Community Addon structure from the JSON URL
|
||||
interface CommunityAddon {
|
||||
transportUrl: string;
|
||||
manifest: ExtendedManifest;
|
||||
}
|
||||
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
|
|
@ -623,10 +619,7 @@ const AddonsScreen = () => {
|
|||
const colors = currentTheme.colors;
|
||||
const styles = createStyles(colors);
|
||||
|
||||
// State for community addons
|
||||
const [communityAddons, setCommunityAddons] = useState<CommunityAddon[]>([]);
|
||||
const [communityLoading, setCommunityLoading] = useState(true);
|
||||
const [communityError, setCommunityError] = useState<string | null>(null);
|
||||
|
||||
|
||||
// Promotional addon: Nuvio Streams
|
||||
const PROMO_ADDON_URL = 'https://nuviostreams.hayd.uk/manifest.json';
|
||||
|
|
@ -652,7 +645,6 @@ const AddonsScreen = () => {
|
|||
|
||||
useEffect(() => {
|
||||
loadAddons();
|
||||
loadCommunityAddons();
|
||||
}, []);
|
||||
|
||||
const loadAddons = async () => {
|
||||
|
|
@ -702,27 +694,7 @@ const AddonsScreen = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Function to load community addons
|
||||
const loadCommunityAddons = async () => {
|
||||
setCommunityLoading(true);
|
||||
setCommunityError(null);
|
||||
try {
|
||||
const response = await axios.get<CommunityAddon[]>('https://stremio-addons.com/catalog.json');
|
||||
// Filter out addons without a manifest or transportUrl (basic validation)
|
||||
let validAddons = response.data.filter(addon => addon.manifest && addon.transportUrl);
|
||||
|
||||
// Filter out Cinemeta since it's now pre-installed
|
||||
validAddons = validAddons.filter(addon => addon.manifest.id !== 'com.linvo.cinemeta');
|
||||
|
||||
setCommunityAddons(validAddons);
|
||||
} catch (error) {
|
||||
logger.error('Failed to load community addons:', error);
|
||||
setCommunityError('Failed to load community addons. Please try again later.');
|
||||
setCommunityAddons([]);
|
||||
} finally {
|
||||
setCommunityLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAddon = async (url?: string) => {
|
||||
let urlToInstall = url || addonUrl;
|
||||
|
|
@ -783,7 +755,6 @@ const AddonsScreen = () => {
|
|||
|
||||
const refreshAddons = async () => {
|
||||
loadAddons();
|
||||
loadCommunityAddons();
|
||||
};
|
||||
|
||||
const moveAddonUp = (addon: ExtendedManifest) => {
|
||||
|
|
@ -835,7 +806,7 @@ const AddonsScreen = () => {
|
|||
configUrl = addon.behaviorHints.configurationURL;
|
||||
logger.info(`Using configurationURL from behaviorHints: ${configUrl}`);
|
||||
}
|
||||
// If a transport URL was provided directly (for community addons)
|
||||
// If a transport URL was provided directly
|
||||
else if (transportUrl) {
|
||||
// Remove any trailing filename like manifest.json
|
||||
const baseUrl = transportUrl.replace(/\/[^\/]+\.json$/, '/');
|
||||
|
|
@ -1057,65 +1028,7 @@ const AddonsScreen = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// Function to render community addon items
|
||||
const renderCommunityAddonItem = ({ item }: { item: CommunityAddon }) => {
|
||||
const { manifest, transportUrl } = item;
|
||||
const types = manifest.types || [];
|
||||
const description = manifest.description || 'No description provided.';
|
||||
// @ts-ignore - logo might exist
|
||||
const logo = manifest.logo || null;
|
||||
const categoryText = types.length > 0
|
||||
? types.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')
|
||||
: 'General';
|
||||
// Check if addon is configurable
|
||||
const isConfigurable = manifest.behaviorHints?.configurable === true;
|
||||
|
||||
return (
|
||||
<View style={styles.communityAddonItem}>
|
||||
{logo ? (
|
||||
<FastImage
|
||||
source={{ uri: logo }}
|
||||
style={styles.communityAddonIcon}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.communityAddonIconPlaceholder}>
|
||||
<MaterialIcons name="extension" size={22} color={colors.darkGray} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.communityAddonDetails}>
|
||||
<Text style={styles.communityAddonName}>{manifest.name}</Text>
|
||||
<Text style={styles.communityAddonDesc} numberOfLines={2}>{description}</Text>
|
||||
<View style={styles.communityAddonMetaContainer}>
|
||||
<Text style={styles.communityAddonVersion}>v{manifest.version || 'N/A'}</Text>
|
||||
<Text style={styles.communityAddonDot}>•</Text>
|
||||
<Text style={styles.communityAddonCategory}>{categoryText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.addonActionButtons}>
|
||||
{isConfigurable && (
|
||||
<TouchableOpacity
|
||||
style={styles.configButton}
|
||||
onPress={() => handleConfigureAddon(manifest, transportUrl)}
|
||||
>
|
||||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.installButton, installing && { opacity: 0.6 }]}
|
||||
onPress={() => handleAddAddon(transportUrl)}
|
||||
disabled={installing}
|
||||
>
|
||||
{installing ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<MaterialIcons name="add" size={20} color={colors.white} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const StatsCard = ({ value, label }: { value: number; label: string }) => (
|
||||
<View style={styles.statsCard}>
|
||||
|
|
@ -1316,91 +1229,7 @@ const AddonsScreen = () => {
|
|||
</View>
|
||||
)}
|
||||
|
||||
{/* Community Addons Section */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>COMMUNITY ADDONS</Text>
|
||||
<View style={styles.addonList}>
|
||||
{communityLoading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color={colors.primary} />
|
||||
</View>
|
||||
) : communityError ? (
|
||||
<View style={styles.emptyContainer}>
|
||||
<MaterialIcons name="error-outline" size={32} color={colors.error} />
|
||||
<Text style={styles.emptyText}>{communityError}</Text>
|
||||
</View>
|
||||
) : communityAddons.length === 0 ? (
|
||||
<View style={styles.emptyContainer}>
|
||||
<MaterialIcons name="extension-off" size={32} color={colors.mediumGray} />
|
||||
<Text style={styles.emptyText}>No community addons available</Text>
|
||||
</View>
|
||||
) : (
|
||||
communityAddons.map((item, index) => (
|
||||
<View
|
||||
key={item.transportUrl}
|
||||
style={{ marginBottom: index === communityAddons.length - 1 ? 32 : 16 }}
|
||||
>
|
||||
<View style={styles.addonItem}>
|
||||
<View style={styles.addonHeader}>
|
||||
{item.manifest.logo ? (
|
||||
<FastImage
|
||||
source={{ uri: item.manifest.logo }}
|
||||
style={styles.addonIcon}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.addonIconPlaceholder}>
|
||||
<MaterialIcons name="extension" size={22} color={colors.mediumGray} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.addonTitleContainer}>
|
||||
<Text style={styles.addonName}>{item.manifest.name}</Text>
|
||||
<View style={styles.addonMetaContainer}>
|
||||
<Text style={styles.addonVersion}>v{item.manifest.version || 'N/A'}</Text>
|
||||
<Text style={styles.addonDot}>•</Text>
|
||||
<Text style={styles.addonCategory}>
|
||||
{item.manifest.types && item.manifest.types.length > 0
|
||||
? item.manifest.types.map(t => t.charAt(0).toUpperCase() + t.slice(1)).join(' • ')
|
||||
: 'General'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.addonActions}>
|
||||
{item.manifest.behaviorHints?.configurable && (
|
||||
<TouchableOpacity
|
||||
style={styles.configButton}
|
||||
onPress={() => handleConfigureAddon(item.manifest, item.transportUrl)}
|
||||
>
|
||||
<MaterialIcons name="settings" size={20} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.installButton, installing && { opacity: 0.6 }]}
|
||||
onPress={() => handleAddAddon(item.transportUrl)}
|
||||
disabled={installing}
|
||||
>
|
||||
{installing ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<MaterialIcons name="add" size={20} color={colors.white} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.addonDescription}>
|
||||
{item.manifest.description
|
||||
? (item.manifest.description.length > 100
|
||||
? item.manifest.description.substring(0, 100) + '...'
|
||||
: item.manifest.description)
|
||||
: 'No description provided.'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,17 +78,17 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
padding: 16,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginBottom: 8,
|
||||
},
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginBottom: 8,
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
sectionDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
|
|
@ -283,59 +283,59 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
marginTop: 8,
|
||||
},
|
||||
infoText: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumEmphasis,
|
||||
lineHeight: 20,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 32,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
scrapersList: {
|
||||
gap: 12,
|
||||
},
|
||||
scrapersContainer: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
lastSection: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
disabledSection: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledText: {
|
||||
color: colors.elevation3,
|
||||
},
|
||||
disabledContainer: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledInput: {
|
||||
backgroundColor: colors.elevation1,
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledImage: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumEmphasis,
|
||||
lineHeight: 20,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
},
|
||||
emptyState: {
|
||||
alignItems: 'center',
|
||||
paddingVertical: 32,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: colors.white,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
emptyStateDescription: {
|
||||
fontSize: 14,
|
||||
color: colors.mediumGray,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
},
|
||||
scrapersList: {
|
||||
gap: 12,
|
||||
},
|
||||
scrapersContainer: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
inputContainer: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
lastSection: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
disabledSection: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledText: {
|
||||
color: colors.elevation3,
|
||||
},
|
||||
disabledContainer: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledInput: {
|
||||
backgroundColor: colors.elevation1,
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledButton: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
disabledImage: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
availableIndicator: {
|
||||
|
|
@ -842,7 +842,7 @@ const PluginsScreen: React.FC = () => {
|
|||
) => {
|
||||
setAlertTitle(title);
|
||||
setAlertMessage(message);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => {} }]);
|
||||
setAlertActions(actions && actions.length > 0 ? actions : [{ label: 'OK', onPress: () => { } }]);
|
||||
setAlertVisible(true);
|
||||
};
|
||||
|
||||
|
|
@ -940,7 +940,7 @@ const PluginsScreen: React.FC = () => {
|
|||
await loadScrapers();
|
||||
openAlert('Success', `${enabled ? 'Enabled' : 'Disabled'} ${filteredScrapers.length} scrapers`);
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to bulk toggle:', error);
|
||||
logger.error('[PluginsScreen] Failed to bulk toggle:', error);
|
||||
openAlert('Error', 'Failed to update scrapers');
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
|
|
@ -1021,7 +1021,7 @@ const PluginsScreen: React.FC = () => {
|
|||
await loadScrapers();
|
||||
openAlert('Success', 'Repository switched successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to switch repository:', error);
|
||||
logger.error('[PluginsScreen] Failed to switch repository:', error);
|
||||
openAlert('Error', 'Failed to switch repository');
|
||||
} finally {
|
||||
setSwitchingRepository(null);
|
||||
|
|
@ -1044,7 +1044,7 @@ const PluginsScreen: React.FC = () => {
|
|||
alertTitle,
|
||||
alertMessage,
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Remove',
|
||||
onPress: async () => {
|
||||
|
|
@ -1057,7 +1057,7 @@ const PluginsScreen: React.FC = () => {
|
|||
: 'Repository removed successfully';
|
||||
openAlert('Success', successMessage);
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to remove repository:', error);
|
||||
logger.error('[PluginsScreen] Failed to remove repository:', error);
|
||||
openAlert('Error', error instanceof Error ? error.message : 'Failed to remove repository');
|
||||
}
|
||||
},
|
||||
|
|
@ -1097,7 +1097,7 @@ const PluginsScreen: React.FC = () => {
|
|||
setShowboxTokenVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to load scrapers:', error);
|
||||
logger.error('[PluginsScreen] Failed to load scrapers:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1118,7 +1118,7 @@ const PluginsScreen: React.FC = () => {
|
|||
setRepositoryUrl(currentRepo.url);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to load repositories:', error);
|
||||
logger.error('[PluginsScreen] Failed to load repositories:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1130,7 +1130,7 @@ const PluginsScreen: React.FC = () => {
|
|||
setRepositoryUrl(repoUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to check repository:', error);
|
||||
logger.error('[PluginsScreen] Failed to check repository:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1157,7 +1157,7 @@ const PluginsScreen: React.FC = () => {
|
|||
setHasRepository(true);
|
||||
openAlert('Success', 'Repository URL saved successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to save repository:', error);
|
||||
logger.error('[PluginsScreen] Failed to save repository:', error);
|
||||
openAlert('Error', 'Failed to save repository URL');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
|
@ -1211,28 +1211,28 @@ const PluginsScreen: React.FC = () => {
|
|||
await pluginService.setScraperEnabled(scraperId, enabled);
|
||||
await loadScrapers();
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to toggle scraper:', error);
|
||||
openAlert('Error', 'Failed to update scraper status');
|
||||
logger.error('[PluginsScreen] Failed to toggle plugin:', error);
|
||||
openAlert('Error', 'Failed to update plugin status');
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearScrapers = () => {
|
||||
openAlert(
|
||||
'Clear All Scrapers',
|
||||
'Are you sure you want to remove all installed scrapers? This action cannot be undone.',
|
||||
'Clear All Plugins',
|
||||
'Are you sure you want to remove all installed plugins? This action cannot be undone.',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await pluginService.clearScrapers();
|
||||
await loadScrapers();
|
||||
openAlert('Success', 'All scrapers have been removed');
|
||||
openAlert('Success', 'All plugins have been removed');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to clear scrapers:', error);
|
||||
openAlert('Error', 'Failed to clear scrapers');
|
||||
logger.error('[PluginsScreen] Failed to clear plugins:', error);
|
||||
openAlert('Error', 'Failed to clear plugins');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1243,9 +1243,9 @@ const PluginsScreen: React.FC = () => {
|
|||
const handleClearCache = () => {
|
||||
openAlert(
|
||||
'Clear Repository Cache',
|
||||
'This will remove the saved repository URL and clear all cached scraper data. You will need to re-enter your repository URL.',
|
||||
'This will remove the saved repository URL and clear all cached plugin data. You will need to re-enter your repository URL.',
|
||||
[
|
||||
{ label: 'Cancel', onPress: () => {} },
|
||||
{ label: 'Cancel', onPress: () => { } },
|
||||
{
|
||||
label: 'Clear Cache',
|
||||
onPress: async () => {
|
||||
|
|
@ -1258,7 +1258,7 @@ const PluginsScreen: React.FC = () => {
|
|||
await loadScrapers();
|
||||
openAlert('Success', 'Repository cache cleared successfully');
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to clear cache:', error);
|
||||
logger.error('[PluginsScreen] Failed to clear cache:', error);
|
||||
openAlert('Error', 'Failed to clear repository cache');
|
||||
}
|
||||
},
|
||||
|
|
@ -1427,7 +1427,7 @@ const PluginsScreen: React.FC = () => {
|
|||
styles={styles}
|
||||
>
|
||||
<Text style={styles.sectionDescription}>
|
||||
Manage multiple scraper repositories. Switch between repositories to access different sets of scrapers.
|
||||
Manage multiple plugin repositories. Switch between repositories to access different sets of plugins.
|
||||
</Text>
|
||||
|
||||
{/* Current Repository */}
|
||||
|
|
@ -1466,9 +1466,9 @@ const PluginsScreen: React.FC = () => {
|
|||
)}
|
||||
<Text style={styles.repositoryUrl}>{repo.url}</Text>
|
||||
<Text style={styles.repositoryMeta}>
|
||||
{repo.scraperCount || 0} scrapers • Last updated: {repo.lastUpdated ? new Date(repo.lastUpdated).toLocaleDateString() : 'Never'}
|
||||
</Text>
|
||||
</View>
|
||||
{repo.scraperCount || 0} plugins • Last updated: {repo.lastUpdated ? new Date(repo.lastUpdated).toLocaleDateString() : 'Never'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.repositoryActions}>
|
||||
{repo.id !== currentRepositoryId && (
|
||||
<TouchableOpacity
|
||||
|
|
@ -1502,7 +1502,7 @@ const PluginsScreen: React.FC = () => {
|
|||
<Text style={styles.repositoryActionButtonText}>Remove</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
|
@ -1535,15 +1535,15 @@ const PluginsScreen: React.FC = () => {
|
|||
style={styles.searchInput}
|
||||
value={searchQuery}
|
||||
onChangeText={setSearchQuery}
|
||||
placeholder="Search scrapers..."
|
||||
placeholder="Search plugins..."
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
/>
|
||||
{searchQuery.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setSearchQuery('')}>
|
||||
<Ionicons name="close-circle" size={20} color={colors.mediumGray} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Filter Chips */}
|
||||
<View style={styles.filterContainer}>
|
||||
|
|
@ -1561,7 +1561,7 @@ const PluginsScreen: React.FC = () => {
|
|||
selectedFilter === filter && styles.filterChipTextSelected
|
||||
]}>
|
||||
{filter === 'all' ? 'All' : filter === 'movie' ? 'Movies' : 'TV Shows'}
|
||||
</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
|
@ -1597,12 +1597,12 @@ const PluginsScreen: React.FC = () => {
|
|||
style={styles.emptyStateIcon}
|
||||
/>
|
||||
<Text style={styles.emptyStateTitle}>
|
||||
{searchQuery ? 'No Scrapers Found' : 'No Scrapers Available'}
|
||||
</Text>
|
||||
{searchQuery ? 'No Plugins Found' : 'No Plugins Available'}
|
||||
</Text>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
{searchQuery
|
||||
? `No scrapers match "${searchQuery}". Try a different search term.`
|
||||
: 'Configure a repository above to view available scrapers.'
|
||||
? `No plugins match "${searchQuery}". Try a different search term.`
|
||||
: 'Configure a repository above to view available plugins.'
|
||||
}
|
||||
</Text>
|
||||
{searchQuery && (
|
||||
|
|
@ -1613,44 +1613,44 @@ const PluginsScreen: React.FC = () => {
|
|||
<Text style={styles.secondaryButtonText}>Clear Search</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
{filteredScrapers.map((scraper) => (
|
||||
<View key={scraper.id} style={styles.scraperCard}>
|
||||
<View style={styles.scraperCardHeader}>
|
||||
{scraper.logo ? (
|
||||
(scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
{scraper.logo ? (
|
||||
(scraper.logo.toLowerCase().endsWith('.svg') || scraper.logo.toLowerCase().includes('.svg?')) ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<FastImage
|
||||
source={{ uri: scraper.logo }}
|
||||
style={styles.scraperLogo}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<View style={styles.scraperLogo} />
|
||||
)}
|
||||
<View style={styles.scraperCardInfo}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 4, gap: 8 }}>
|
||||
<Text style={styles.scraperName}>{scraper.name}</Text>
|
||||
<StatusBadge status={getScraperStatus(scraper)} colors={colors} />
|
||||
</View>
|
||||
<Text style={styles.scraperDescription}>{scraper.description}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={scraper.enabled && settings.enableLocalScrapers}
|
||||
onValueChange={(enabled) => handleToggleScraper(scraper.id, enabled)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={scraper.enabled && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || scraper.manifestEnabled === false || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.scraperDescription}>{scraper.description}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={scraper.enabled && settings.enableLocalScrapers}
|
||||
onValueChange={(enabled) => handleToggleScraper(scraper.id, enabled)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={scraper.enabled && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || scraper.manifestEnabled === false || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.scraperCardMeta}>
|
||||
<View style={styles.scraperCardMetaItem}>
|
||||
|
|
@ -1682,62 +1682,62 @@ const PluginsScreen: React.FC = () => {
|
|||
</View>
|
||||
|
||||
{/* ShowBox Settings - only visible when ShowBox scraper is available */}
|
||||
{showboxScraperId && scraper.id === showboxScraperId && settings.enableLocalScrapers && (
|
||||
{showboxScraperId && scraper.id === showboxScraperId && settings.enableLocalScrapers && (
|
||||
<View style={{ marginTop: 16, paddingTop: 16, borderTopWidth: 1, borderTopColor: colors.elevation3 }}>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
|
||||
<TextInput
|
||||
style={[styles.textInput, { flex: 1, marginBottom: 0 }]}
|
||||
value={showboxUiToken}
|
||||
onChangeText={setShowboxUiToken}
|
||||
placeholder="Paste your ShowBox UI token"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible}
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
{showboxSavedToken.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setShowboxTokenVisible(v => !v)} accessibilityRole="button" accessibilityLabel={showboxTokenVisible ? 'Hide token' : 'Show token'} style={{ marginLeft: 10 }}>
|
||||
<Ionicons name={showboxTokenVisible ? 'eye-off' : 'eye'} size={18} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.buttonRow}>
|
||||
{showboxUiToken !== showboxSavedToken && (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken });
|
||||
}
|
||||
setShowboxSavedToken(showboxUiToken);
|
||||
openAlert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={async () => {
|
||||
setShowboxUiToken('');
|
||||
setShowboxSavedToken('');
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox UI Token</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
|
||||
<TextInput
|
||||
style={[styles.textInput, { flex: 1, marginBottom: 0 }]}
|
||||
value={showboxUiToken}
|
||||
onChangeText={setShowboxUiToken}
|
||||
placeholder="Paste your ShowBox UI token"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
secureTextEntry={showboxSavedToken.length > 0 && !showboxTokenVisible}
|
||||
multiline={false}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
{showboxSavedToken.length > 0 && (
|
||||
<TouchableOpacity onPress={() => setShowboxTokenVisible(v => !v)} accessibilityRole="button" accessibilityLabel={showboxTokenVisible ? 'Hide token' : 'Show token'} style={{ marginLeft: 10 }}>
|
||||
<Ionicons name={showboxTokenVisible ? 'eye-off' : 'eye'} size={18} color={colors.primary} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.buttonRow}>
|
||||
{showboxUiToken !== showboxSavedToken && (
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, { uiToken: showboxUiToken });
|
||||
}
|
||||
setShowboxSavedToken(showboxUiToken);
|
||||
openAlert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={async () => {
|
||||
setShowboxUiToken('');
|
||||
setShowboxSavedToken('');
|
||||
if (showboxScraperId) {
|
||||
await pluginService.setScraperSettings(showboxScraperId, {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</CollapsibleSection>
|
||||
|
||||
{/* Additional Settings */}
|
||||
|
|
@ -1772,50 +1772,50 @@ const PluginsScreen: React.FC = () => {
|
|||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.streamDisplayMode === 'grouped'}
|
||||
onValueChange={(value) => {
|
||||
updateSetting('streamDisplayMode', value ? 'grouped' : 'separate');
|
||||
// Auto-disable quality sorting when grouping is disabled
|
||||
if (!value && settings.streamSortMode === 'quality-then-scraper') {
|
||||
updateSetting('streamSortMode', 'scraper-then-quality');
|
||||
}
|
||||
}}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
value={settings.streamDisplayMode === 'grouped'}
|
||||
onValueChange={(value) => {
|
||||
updateSetting('streamDisplayMode', value ? 'grouped' : 'separate');
|
||||
// Auto-disable quality sorting when grouping is disabled
|
||||
if (!value && settings.streamSortMode === 'quality-then-scraper') {
|
||||
updateSetting('streamSortMode', 'scraper-then-quality');
|
||||
}
|
||||
}}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamDisplayMode === 'grouped' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingRow}>
|
||||
<View style={styles.settingInfo}>
|
||||
<Text style={styles.settingTitle}>Sort by Quality First</Text>
|
||||
<Text style={styles.settingDescription}>
|
||||
When enabled, streams are sorted by quality first, then by scraper. When disabled, streams are sorted by scraper first, then by quality. Only available when grouping is enabled.
|
||||
When enabled, streams are sorted by quality first, then by plugin. When disabled, streams are sorted by plugin first, then by quality. Only available when grouping is enabled.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.streamSortMode === 'quality-then-scraper'}
|
||||
onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
||||
/>
|
||||
value={settings.streamSortMode === 'quality-then-scraper'}
|
||||
onValueChange={(value) => updateSetting('streamSortMode', value ? 'quality-then-scraper' : 'scraper-then-quality')}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.streamSortMode === 'quality-then-scraper' ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers || settings.streamDisplayMode !== 'grouped'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingRow}>
|
||||
<View style={styles.settingInfo}>
|
||||
<Text style={styles.settingTitle}>Show Scraper Logos</Text>
|
||||
<Text style={styles.settingTitle}>Show Plugin Logos</Text>
|
||||
<Text style={styles.settingDescription}>
|
||||
Display scraper logos next to streaming links on the streams screen.
|
||||
Display plugin logos next to streaming links on the streams screen.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={settings.showScraperLogos && settings.enableLocalScrapers}
|
||||
onValueChange={(value) => updateSetting('showScraperLogos', value)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
value={settings.showScraperLogos && settings.enableLocalScrapers}
|
||||
onValueChange={(value) => updateSetting('showScraperLogos', value)}
|
||||
trackColor={{ false: colors.elevation3, true: colors.primary }}
|
||||
thumbColor={settings.showScraperLogos && settings.enableLocalScrapers ? colors.white : '#f4f3f4'}
|
||||
disabled={!settings.enableLocalScrapers}
|
||||
/>
|
||||
</View>
|
||||
</CollapsibleSection>
|
||||
|
||||
|
|
@ -1944,10 +1944,10 @@ const PluginsScreen: React.FC = () => {
|
|||
2. <Text style={{ fontWeight: '600' }}>Add Repository</Text> - Add a GitHub raw URL or use the default repository
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
3. <Text style={{ fontWeight: '600' }}>Refresh Repository</Text> - Download available scrapers from the repository
|
||||
3. <Text style={{ fontWeight: '600' }}>Refresh Repository</Text> - Download available plugins from the repository
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
4. <Text style={{ fontWeight: '600' }}>Enable Scrapers</Text> - Turn on the scrapers you want to use for streaming
|
||||
4. <Text style={{ fontWeight: '600' }}>Enable Plugins</Text> - Turn on the plugins you want to use for streaming
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.modalButton}
|
||||
|
|
@ -1988,36 +1988,36 @@ const PluginsScreen: React.FC = () => {
|
|||
/>
|
||||
|
||||
|
||||
{/* Format Hint */}
|
||||
<Text style={styles.formatHint}>
|
||||
Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch
|
||||
</Text>
|
||||
{/* Format Hint */}
|
||||
<Text style={styles.formatHint}>
|
||||
Format: https://raw.githubusercontent.com/username/repo/refs/heads/branch
|
||||
</Text>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.compactActions}>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.cancelButton]}
|
||||
onPress={() => {
|
||||
setShowAddRepositoryModal(false);
|
||||
setNewRepositoryUrl('');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
{/* Action Buttons */}
|
||||
<View style={styles.compactActions}>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.cancelButton]}
|
||||
onPress={() => {
|
||||
setShowAddRepositoryModal(false);
|
||||
setNewRepositoryUrl('');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||
onPress={handleAddRepository}
|
||||
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.addButtonText}>Add</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<TouchableOpacity
|
||||
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||
onPress={handleAddRepository}
|
||||
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator size="small" color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.addButtonText}>Add</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ const SettingsScreen: React.FC = () => {
|
|||
description="Manage plugins and repositories"
|
||||
customIcon={<PluginIcon size={isTablet ? 24 : 20} color={currentTheme.colors.primary} />}
|
||||
renderControl={ChevronRight}
|
||||
onPress={() => navigation.navigate('ScraperSettings')}
|
||||
onPress={() => navigation.navigate('PluginSettings')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
<SettingItem
|
||||
|
|
|
|||
|
|
@ -913,13 +913,13 @@ export const StreamsScreen = () => {
|
|||
const handleStreamPress = useCallback(async (stream: Stream) => {
|
||||
try {
|
||||
if (stream.url) {
|
||||
// Block magnet links - not supported yet
|
||||
|
||||
// Block magnet links with sanitized message
|
||||
if (typeof stream.url === 'string' && stream.url.startsWith('magnet:')) {
|
||||
try {
|
||||
openAlert('Not supported', 'Torrent streaming is not supported yet.');
|
||||
} catch (_e) { }
|
||||
openAlert('Stream Not Supported', 'This stream format is not supported.');
|
||||
return;
|
||||
}
|
||||
|
||||
// If stream is actually MKV format, force the in-app VLC-based player on iOS
|
||||
try {
|
||||
if (Platform.OS === 'ios' && settings.preferredPlayer === 'internal') {
|
||||
|
|
@ -1078,7 +1078,7 @@ export const StreamsScreen = () => {
|
|||
const isMagnet = typeof stream.url === 'string' && stream.url.startsWith('magnet:');
|
||||
|
||||
if (isMagnet) {
|
||||
// For magnet links, open directly which will trigger the torrent app chooser
|
||||
// For magnet links, open directly
|
||||
if (__DEV__) console.log('Opening magnet link directly');
|
||||
Linking.openURL(stream.url)
|
||||
.then(() => { if (__DEV__) console.log('Successfully opened magnet link'); })
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class LocalScraperService {
|
|||
id: 'default',
|
||||
name: this.extractRepositoryName(storedRepoUrl),
|
||||
url: storedRepoUrl,
|
||||
description: 'Default repository',
|
||||
description: 'Default Plugins Repository',
|
||||
isDefault: true,
|
||||
enabled: true,
|
||||
lastUpdated: Date.now()
|
||||
|
|
@ -516,27 +516,27 @@ class LocalScraperService {
|
|||
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
|
||||
// Store repository name from manifest
|
||||
if (manifest.name) {
|
||||
this.repositoryName = manifest.name;
|
||||
}
|
||||
// Store repository name from manifest
|
||||
if (manifest.name) {
|
||||
this.repositoryName = manifest.name;
|
||||
}
|
||||
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Raw manifest data:', JSON.stringify(manifest, null, 2));
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Manifest scrapers count:', manifest.scrapers?.length || 0);
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Raw manifest data:', JSON.stringify(manifest, null, 2));
|
||||
logger.log('[LocalScraperService] getAvailableScrapers - Manifest scrapers count:', manifest.scrapers?.length || 0);
|
||||
|
||||
// Log each scraper's enabled status from manifest
|
||||
manifest.scrapers?.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] getAvailableScrapers - Scraper ${scraper.name}: enabled=${scraper.enabled}`);
|
||||
});
|
||||
// Log each scraper's enabled status from manifest
|
||||
manifest.scrapers?.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] getAvailableScrapers - Scraper ${scraper.name}: enabled=${scraper.enabled}`);
|
||||
});
|
||||
|
||||
logger.log('[LocalScraperService] Found', manifest.scrapers.length, 'scrapers in repository');
|
||||
|
||||
|
|
@ -760,13 +760,13 @@ class LocalScraperService {
|
|||
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||
|
||||
const response = await axios.get(manifestUrl, {
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
});
|
||||
const manifest: ScraperManifest = response.data;
|
||||
|
||||
// Store repository name from manifest
|
||||
|
|
@ -808,12 +808,12 @@ class LocalScraperService {
|
|||
|
||||
logger.log('[LocalScraperService] Found', availableScrapers.length, 'available scrapers in repository');
|
||||
|
||||
// Log final scraper states being returned to UI
|
||||
availableScrapers.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] Final scraper ${scraper.name}: manifestEnabled=${scraper.manifestEnabled}, enabled=${scraper.enabled}`);
|
||||
});
|
||||
// Log final scraper states being returned to UI
|
||||
availableScrapers.forEach(scraper => {
|
||||
logger.log(`[LocalScraperService] Final scraper ${scraper.name}: manifestEnabled=${scraper.manifestEnabled}, enabled=${scraper.enabled}`);
|
||||
});
|
||||
|
||||
return availableScrapers;
|
||||
return availableScrapers;
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to fetch available scrapers from manifest:', error);
|
||||
|
|
@ -920,7 +920,7 @@ class LocalScraperService {
|
|||
if (enabledScrapers.length > 0) {
|
||||
try {
|
||||
logger.log('[LocalScraperService] Enabled scrapers:', enabledScrapers.map(s => s.name).join(', '));
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
if (enabledScrapers.length === 0) {
|
||||
|
|
@ -983,7 +983,7 @@ class LocalScraperService {
|
|||
promise.finally(() => {
|
||||
const current = this.inFlightByKey.get(flightKey);
|
||||
if (current === promise) this.inFlightByKey.delete(flightKey);
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
const results = await promise;
|
||||
|
|
|
|||
Loading…
Reference in a new issue