mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-12 04:50:44 +00:00
major ui changes
This commit is contained in:
parent
9c12f9fc08
commit
494b35b1c0
5 changed files with 211 additions and 66 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 63d560d55f1a84a16318525ad4eb1db5162e059c
|
Subproject commit 22ed3a1c96ed2a8adf5bf9f277acd9d8c53c069c
|
||||||
|
|
@ -39,7 +39,7 @@ import LogoSourceSettings from '../screens/LogoSourceSettings';
|
||||||
import ThemeScreen from '../screens/ThemeScreen';
|
import ThemeScreen from '../screens/ThemeScreen';
|
||||||
import ProfilesScreen from '../screens/ProfilesScreen';
|
import ProfilesScreen from '../screens/ProfilesScreen';
|
||||||
import OnboardingScreen from '../screens/OnboardingScreen';
|
import OnboardingScreen from '../screens/OnboardingScreen';
|
||||||
import ScraperSettingsScreen from '../screens/ScraperSettingsScreen';
|
import PluginsScreen from '../screens/PluginsScreen';
|
||||||
|
|
||||||
// Stack navigator types
|
// Stack navigator types
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
|
|
@ -1028,7 +1028,7 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="ScraperSettings"
|
name="ScraperSettings"
|
||||||
component={ScraperSettingsScreen}
|
component={PluginsScreen}
|
||||||
options={{
|
options={{
|
||||||
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
animation: Platform.OS === 'android' ? 'slide_from_right' : 'fade',
|
||||||
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
animationDuration: Platform.OS === 'android' ? 250 : 200,
|
||||||
|
|
|
||||||
|
|
@ -314,11 +314,23 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
disabledImage: {
|
disabledImage: {
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
});
|
availableIndicator: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 2,
|
||||||
|
borderRadius: 4,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
availableIndicatorText: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const ScraperSettingsScreen: React.FC = () => {
|
const PluginsScreen: React.FC = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { settings, updateSetting } = useSettings();
|
const { settings, updateSetting } = useSettings();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
|
|
@ -337,7 +349,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
const loadScrapers = async () => {
|
const loadScrapers = async () => {
|
||||||
try {
|
try {
|
||||||
const scrapers = await localScraperService.getInstalledScrapers();
|
const scrapers = await localScraperService.getAvailableScrapers();
|
||||||
setInstalledScrapers(scrapers);
|
setInstalledScrapers(scrapers);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[ScraperSettings] Failed to load scrapers:', error);
|
logger.error('[ScraperSettings] Failed to load scrapers:', error);
|
||||||
|
|
@ -395,7 +407,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
await localScraperService.refreshRepository();
|
await localScraperService.refreshRepository();
|
||||||
await loadScrapers();
|
await loadScrapers(); // This will now load available scrapers from manifest
|
||||||
Alert.alert('Success', 'Repository refreshed successfully');
|
Alert.alert('Success', 'Repository refreshed successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[ScraperSettings] Failed to refresh repository:', error);
|
logger.error('[ScraperSettings] Failed to refresh repository:', error);
|
||||||
|
|
@ -411,11 +423,25 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
const handleToggleScraper = async (scraperId: string, enabled: boolean) => {
|
const handleToggleScraper = async (scraperId: string, enabled: boolean) => {
|
||||||
try {
|
try {
|
||||||
|
if (enabled) {
|
||||||
|
// If enabling a scraper, ensure it's installed first
|
||||||
|
const installedScrapers = await localScraperService.getInstalledScrapers();
|
||||||
|
const isInstalled = installedScrapers.some(scraper => scraper.id === scraperId);
|
||||||
|
|
||||||
|
if (!isInstalled) {
|
||||||
|
// Need to install the scraper first
|
||||||
|
setIsRefreshing(true);
|
||||||
|
await localScraperService.refreshRepository();
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await localScraperService.setScraperEnabled(scraperId, enabled);
|
await localScraperService.setScraperEnabled(scraperId, enabled);
|
||||||
await loadScrapers();
|
await loadScrapers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[ScraperSettings] Failed to toggle scraper:', error);
|
logger.error('[ScraperSettings] Failed to toggle scraper:', error);
|
||||||
Alert.alert('Error', 'Failed to update scraper status');
|
Alert.alert('Error', 'Failed to update scraper status');
|
||||||
|
setIsRefreshing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -502,7 +528,7 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.headerTitle}>Local Scrapers</Text>
|
<Text style={styles.headerTitle}>Plugins</Text>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
|
|
@ -606,10 +632,10 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Installed Scrapers */}
|
{/* Available Scrapers */}
|
||||||
<View style={[styles.section, !settings.enableLocalScrapers && styles.disabledSection]}>
|
<View style={[styles.section, !settings.enableLocalScrapers && styles.disabledSection]}>
|
||||||
<View style={styles.sectionHeader}>
|
<View style={styles.sectionHeader}>
|
||||||
<Text style={[styles.sectionTitle, !settings.enableLocalScrapers && styles.disabledText]}>Installed Scrapers</Text>
|
<Text style={[styles.sectionTitle, !settings.enableLocalScrapers && styles.disabledText]}>Available Scrapers</Text>
|
||||||
{installedScrapers.length > 0 && settings.enableLocalScrapers && (
|
{installedScrapers.length > 0 && settings.enableLocalScrapers && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.clearButton}
|
style={styles.clearButton}
|
||||||
|
|
@ -619,56 +645,78 @@ const ScraperSettingsScreen: React.FC = () => {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
<Text style={[styles.sectionDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||||
|
Scrapers available in the repository. Only enabled scrapers that are also installed will be used for streaming.
|
||||||
|
</Text>
|
||||||
|
|
||||||
{installedScrapers.length === 0 ? (
|
{installedScrapers.length === 0 ? (
|
||||||
<View style={[styles.emptyContainer, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
<View style={[styles.emptyContainer, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||||
<Ionicons name="download-outline" size={48} color={!settings.enableLocalScrapers ? colors.elevation3 : colors.mediumGray} />
|
<Ionicons name="download-outline" size={48} color={!settings.enableLocalScrapers ? colors.elevation3 : colors.mediumGray} />
|
||||||
<Text style={[styles.emptyStateTitle, !settings.enableLocalScrapers && styles.disabledText]}>No Scrapers Installed</Text>
|
<Text style={[styles.emptyStateTitle, !settings.enableLocalScrapers && styles.disabledText]}>No Scrapers Available</Text>
|
||||||
<Text style={[styles.emptyStateDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
<Text style={[styles.emptyStateDescription, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||||
Configure a repository above to install scrapers.
|
Configure a repository above to view available scrapers.
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.scrapersContainer}>
|
<View style={styles.scrapersContainer}>
|
||||||
{installedScrapers.map((scraper) => (
|
{installedScrapers.map((scraper) => {
|
||||||
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
// Check if scraper is actually installed (has cached code)
|
||||||
{scraper.logo ? (
|
const isInstalled = localScraperService.getInstalledScrapers().then(installed =>
|
||||||
<Image
|
installed.some(s => s.id === scraper.id)
|
||||||
source={{ uri: scraper.logo }}
|
);
|
||||||
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
|
||||||
resizeMode="contain"
|
return (
|
||||||
/>
|
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||||
) : (
|
{scraper.logo ? (
|
||||||
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
<Image
|
||||||
)}
|
source={{ uri: scraper.logo }}
|
||||||
<View style={styles.scraperInfo}>
|
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
||||||
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
resizeMode="contain"
|
||||||
<Text style={[styles.scraperDescription, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.description}</Text>
|
/>
|
||||||
<View style={styles.scraperMeta}>
|
) : (
|
||||||
<Text style={[styles.scraperVersion, !settings.enableLocalScrapers && styles.disabledText]}>v{scraper.version}</Text>
|
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
||||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
)}
|
||||||
<Text style={[styles.scraperTypes, !settings.enableLocalScrapers && styles.disabledText]}>
|
<View style={styles.scraperInfo}>
|
||||||
{scraper.supportedTypes && Array.isArray(scraper.supportedTypes) ? scraper.supportedTypes.join(', ') : 'Unknown'}
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
</Text>
|
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
||||||
{scraper.contentLanguage && Array.isArray(scraper.contentLanguage) && scraper.contentLanguage.length > 0 && (
|
{scraper.manifestEnabled === false ? (
|
||||||
<>
|
<View style={[styles.availableIndicator, { backgroundColor: colors.mediumGray }]}>
|
||||||
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
<Text style={styles.availableIndicatorText}>Disabled</Text>
|
||||||
<Text style={[styles.scraperLanguage, !settings.enableLocalScrapers && styles.disabledText]}>
|
</View>
|
||||||
{scraper.contentLanguage.map(lang => lang.toUpperCase()).join(', ')}
|
) : !scraper.enabled && (
|
||||||
</Text>
|
<View style={styles.availableIndicator}>
|
||||||
</>
|
<Text style={styles.availableIndicatorText}>Available</Text>
|
||||||
)}
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text style={[styles.scraperDescription, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.description}</Text>
|
||||||
|
<View style={styles.scraperMeta}>
|
||||||
|
<Text style={[styles.scraperVersion, !settings.enableLocalScrapers && styles.disabledText]}>v{scraper.version}</Text>
|
||||||
|
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||||
|
<Text style={[styles.scraperTypes, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||||
|
{scraper.supportedTypes && Array.isArray(scraper.supportedTypes) ? scraper.supportedTypes.join(', ') : 'Unknown'}
|
||||||
|
</Text>
|
||||||
|
{scraper.contentLanguage && Array.isArray(scraper.contentLanguage) && scraper.contentLanguage.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Text style={[styles.scraperDot, !settings.enableLocalScrapers && styles.disabledText]}>•</Text>
|
||||||
|
<Text style={[styles.scraperLanguage, !settings.enableLocalScrapers && styles.disabledText]}>
|
||||||
|
{scraper.contentLanguage.map(lang => lang.toUpperCase()).join(', ')}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</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}
|
||||||
|
style={{ opacity: (!settings.enableLocalScrapers || scraper.manifestEnabled === false) ? 0.5 : 1 }}
|
||||||
|
/>
|
||||||
</View>
|
</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}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -945,4 +993,4 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ScraperSettingsScreen;
|
export default PluginsScreen;
|
||||||
|
|
@ -323,6 +323,13 @@ const SettingsScreen: React.FC = () => {
|
||||||
renderControl={ChevronRight}
|
renderControl={ChevronRight}
|
||||||
onPress={() => navigation.navigate('Addons')}
|
onPress={() => navigation.navigate('Addons')}
|
||||||
/>
|
/>
|
||||||
|
<SettingItem
|
||||||
|
title="Plugins"
|
||||||
|
description="Manage plugins and repositories"
|
||||||
|
icon="code"
|
||||||
|
renderControl={ChevronRight}
|
||||||
|
onPress={() => navigation.navigate('ScraperSettings')}
|
||||||
|
/>
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Catalogs"
|
title="Catalogs"
|
||||||
description={`${catalogCount} active`}
|
description={`${catalogCount} active`}
|
||||||
|
|
@ -401,13 +408,6 @@ const SettingsScreen: React.FC = () => {
|
||||||
renderControl={ChevronRight}
|
renderControl={ChevronRight}
|
||||||
onPress={() => navigation.navigate('PlayerSettings')}
|
onPress={() => navigation.navigate('PlayerSettings')}
|
||||||
/>
|
/>
|
||||||
<SettingItem
|
|
||||||
title="Local Scrapers"
|
|
||||||
description="Manage local scraper repositories"
|
|
||||||
icon="code"
|
|
||||||
renderControl={ChevronRight}
|
|
||||||
onPress={() => navigation.navigate('ScraperSettings')}
|
|
||||||
/>
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
description="Episode reminders"
|
description="Episode reminders"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export interface ScraperInfo {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
logo?: string;
|
logo?: string;
|
||||||
contentLanguage?: string[];
|
contentLanguage?: string[];
|
||||||
|
manifestEnabled?: boolean; // Whether the scraper is enabled in the manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocalScraperResult {
|
export interface LocalScraperResult {
|
||||||
|
|
@ -189,17 +190,48 @@ class LocalScraperService {
|
||||||
try {
|
try {
|
||||||
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
||||||
|
|
||||||
// Fetch manifest
|
// Fetch manifest with cache busting
|
||||||
const manifestUrl = this.repositoryUrl.endsWith('/')
|
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||||
? `${this.repositoryUrl}manifest.json`
|
? `${this.repositoryUrl}manifest.json`
|
||||||
: `${this.repositoryUrl}/manifest.json`;
|
: `${this.repositoryUrl}/manifest.json`;
|
||||||
|
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}`;
|
||||||
|
|
||||||
const response = await axios.get(manifestUrl, { timeout: 10000 });
|
const response = await axios.get(manifestUrl, {
|
||||||
const manifest: ScraperManifest = response.data;
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Expires': '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const manifest: ScraperManifest = response.data;
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
});
|
||||||
|
|
||||||
logger.log('[LocalScraperService] Found', manifest.scrapers.length, 'scrapers in repository');
|
logger.log('[LocalScraperService] Found', manifest.scrapers.length, 'scrapers in repository');
|
||||||
|
|
||||||
// Download and install each scraper
|
// Get current manifest scraper IDs
|
||||||
|
const manifestScraperIds = new Set(manifest.scrapers.map(s => s.id));
|
||||||
|
|
||||||
|
// Remove scrapers that are no longer in the manifest
|
||||||
|
const currentScraperIds = Array.from(this.installedScrapers.keys());
|
||||||
|
for (const scraperId of currentScraperIds) {
|
||||||
|
if (!manifestScraperIds.has(scraperId)) {
|
||||||
|
logger.log('[LocalScraperService] Removing scraper no longer in manifest:', this.installedScrapers.get(scraperId)?.name || scraperId);
|
||||||
|
this.installedScrapers.delete(scraperId);
|
||||||
|
this.scraperCode.delete(scraperId);
|
||||||
|
// Remove from AsyncStorage cache
|
||||||
|
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and install each scraper from manifest
|
||||||
for (const scraperInfo of manifest.scrapers) {
|
for (const scraperInfo of manifest.scrapers) {
|
||||||
await this.downloadScraper(scraperInfo);
|
await this.downloadScraper(scraperInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -296,6 +328,65 @@ class LocalScraperService {
|
||||||
return Array.from(this.installedScrapers.values());
|
return Array.from(this.installedScrapers.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get available scrapers from manifest.json (for display in settings)
|
||||||
|
async getAvailableScrapers(): Promise<ScraperInfo[]> {
|
||||||
|
if (!this.repositoryUrl) {
|
||||||
|
logger.log('[LocalScraperService] No repository URL configured, returning installed scrapers');
|
||||||
|
return this.getInstalledScrapers();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log('[LocalScraperService] Fetching available scrapers from manifest');
|
||||||
|
|
||||||
|
// Fetch manifest with cache busting
|
||||||
|
const baseManifestUrl = this.repositoryUrl.endsWith('/')
|
||||||
|
? `${this.repositoryUrl}manifest.json`
|
||||||
|
: `${this.repositoryUrl}/manifest.json`;
|
||||||
|
const manifestUrl = `${baseManifestUrl}?t=${Date.now()}`;
|
||||||
|
|
||||||
|
const response = await axios.get(manifestUrl, {
|
||||||
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Expires': '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const manifest: ScraperManifest = response.data;
|
||||||
|
|
||||||
|
// Return scrapers from manifest, respecting manifest's enabled field
|
||||||
|
const availableScrapers = manifest.scrapers.map(scraperInfo => {
|
||||||
|
const installedScraper = this.installedScrapers.get(scraperInfo.id);
|
||||||
|
|
||||||
|
// Create a copy with manifest data
|
||||||
|
const scraperWithManifestData = {
|
||||||
|
...scraperInfo,
|
||||||
|
// Store the manifest's enabled state separately
|
||||||
|
manifestEnabled: scraperInfo.enabled,
|
||||||
|
// If manifest says enabled: false, scraper cannot be enabled
|
||||||
|
// If manifest says enabled: true, use installed state or default to false
|
||||||
|
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false
|
||||||
|
};
|
||||||
|
|
||||||
|
return scraperWithManifestData;
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return availableScrapers;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[LocalScraperService] Failed to fetch available scrapers from manifest:', error);
|
||||||
|
// Fallback to installed scrapers if manifest fetch fails
|
||||||
|
return this.getInstalledScrapers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enable/disable scraper
|
// Enable/disable scraper
|
||||||
async setScraperEnabled(scraperId: string, enabled: boolean): Promise<void> {
|
async setScraperEnabled(scraperId: string, enabled: boolean): Promise<void> {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|
@ -313,8 +404,14 @@ class LocalScraperService {
|
||||||
async getStreams(type: string, tmdbId: string, season?: number, episode?: number, callback?: ScraperCallback): Promise<void> {
|
async getStreams(type: string, tmdbId: string, season?: number, episode?: number, callback?: ScraperCallback): Promise<void> {
|
||||||
await this.ensureInitialized();
|
await this.ensureInitialized();
|
||||||
|
|
||||||
const enabledScrapers = Array.from(this.installedScrapers.values())
|
// Get available scrapers from manifest (respects manifestEnabled)
|
||||||
.filter(scraper => scraper.enabled && scraper.supportedTypes.includes(type as 'movie' | 'tv'));
|
const availableScrapers = await this.getAvailableScrapers();
|
||||||
|
const enabledScrapers = availableScrapers
|
||||||
|
.filter(scraper =>
|
||||||
|
scraper.enabled &&
|
||||||
|
scraper.manifestEnabled !== false &&
|
||||||
|
scraper.supportedTypes.includes(type as 'movie' | 'tv')
|
||||||
|
);
|
||||||
|
|
||||||
if (enabledScrapers.length === 0) {
|
if (enabledScrapers.length === 0) {
|
||||||
logger.log('[LocalScraperService] No enabled scrapers found for type:', type);
|
logger.log('[LocalScraperService] No enabled scrapers found for type:', type);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue