mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
somechanges
This commit is contained in:
parent
71a9042dc4
commit
a32fb39743
8 changed files with 276 additions and 97 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 0a040cc12da805fa8b38411d128e607e86e0f919
|
||||
Subproject commit c176aabb4edd73a709ebdc097688e780b65b651a
|
||||
|
|
@ -650,6 +650,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
(settings?.episodeLayoutStyle === 'horizontal') ? (
|
||||
// Horizontal Layout (Netflix-style)
|
||||
<FlashList
|
||||
key={`episodes-${settings?.episodeLayoutStyle}-${selectedSeason}`}
|
||||
ref={episodeScrollViewRef}
|
||||
data={currentSeasonEpisodes}
|
||||
renderItem={({ item: episode, index }) => (
|
||||
|
|
@ -679,6 +680,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
) : (
|
||||
// Vertical Layout (Traditional)
|
||||
<FlashList
|
||||
key={`episodes-${settings?.episodeLayoutStyle}-${selectedSeason}`}
|
||||
ref={episodeScrollViewRef}
|
||||
data={currentSeasonEpisodes}
|
||||
renderItem={({ item: episode, index }) => (
|
||||
|
|
|
|||
|
|
@ -784,16 +784,34 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}
|
||||
disableImmersiveMode();
|
||||
|
||||
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||
]
|
||||
});
|
||||
if (Platform.OS === 'ios') {
|
||||
// iOS: rebuild stack so Streams is presented as modal above Metadata
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 2,
|
||||
routes: [
|
||||
{ name: 'MainTabs' },
|
||||
{ name: 'Metadata', params: { id, type } },
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else if ((navigation as any).canGoBack()) {
|
||||
(navigation as any).goBack();
|
||||
} else {
|
||||
(navigation as any).navigate('MainTabs');
|
||||
}
|
||||
} else {
|
||||
navigation.goBack();
|
||||
// Android: hard reset to avoid stacking multiple pages/modals
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
(navigation as any).goBack();
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
// Fallback: still try to restore portrait then navigate
|
||||
|
|
@ -804,16 +822,34 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
}
|
||||
disableImmersiveMode();
|
||||
|
||||
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||
]
|
||||
});
|
||||
if (Platform.OS === 'ios') {
|
||||
// iOS: rebuild stack so Streams is presented as modal above Metadata
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 2,
|
||||
routes: [
|
||||
{ name: 'MainTabs' },
|
||||
{ name: 'Metadata', params: { id, type } },
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else if ((navigation as any).canGoBack()) {
|
||||
(navigation as any).goBack();
|
||||
} else {
|
||||
(navigation as any).navigate('MainTabs');
|
||||
}
|
||||
} else {
|
||||
navigation.goBack();
|
||||
// Android: hard reset to avoid stacking multiple pages/modals
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else {
|
||||
(navigation as any).goBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -828,19 +828,37 @@ const VideoPlayer: React.FC = () => {
|
|||
|
||||
// Navigate back with proper handling for fullscreen modal
|
||||
try {
|
||||
// For series, hard reset to a single Streams route to avoid stacking multiple modals/pages
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId } }
|
||||
]
|
||||
});
|
||||
} else if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
// On iOS, ensure Streams shows the CURRENT episode as modal by navigating directly
|
||||
if (Platform.OS === 'ios') {
|
||||
if (type === 'series' && id && episodeId) {
|
||||
// Ensure modal by restoring MainTabs -> Metadata -> Streams
|
||||
(navigation as any).reset({
|
||||
index: 2,
|
||||
routes: [
|
||||
{ name: 'MainTabs' },
|
||||
{ name: 'Metadata', params: { id, type } },
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
navigation.navigate('MainTabs');
|
||||
}
|
||||
} else {
|
||||
// Fallback: navigate to main tabs if can't go back
|
||||
navigation.navigate('MainTabs');
|
||||
// Android: hard reset to avoid stacking multiple pages/modals
|
||||
if (type === 'series' && id && episodeId) {
|
||||
(navigation as any).reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{ name: 'Streams', params: { id, type: 'series', episodeId, fromPlayer: true } }
|
||||
]
|
||||
});
|
||||
} else if (navigation.canGoBack()) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
navigation.navigate('MainTabs');
|
||||
}
|
||||
}
|
||||
logger.log('[VideoPlayer] Navigation completed');
|
||||
} catch (navError) {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export type RootStackParamList = {
|
|||
type: string;
|
||||
episodeId?: string;
|
||||
episodeThumbnail?: string;
|
||||
fromPlayer?: boolean;
|
||||
};
|
||||
VideoPlayer: {
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -368,6 +368,21 @@ const PluginsScreen: React.FC = () => {
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [hasRepository, setHasRepository] = useState(false);
|
||||
const [showboxCookie, setShowboxCookie] = useState<string>('');
|
||||
const [showboxRegion, setShowboxRegion] = useState<string>('');
|
||||
const regionOptions = [
|
||||
{ value: 'USA7', label: 'US East' },
|
||||
{ value: 'USA6', label: 'US West' },
|
||||
{ value: 'USA5', label: 'US Middle' },
|
||||
{ value: 'UK3', label: 'United Kingdom' },
|
||||
{ value: 'CA1', label: 'Canada' },
|
||||
{ value: 'FR1', label: 'France' },
|
||||
{ value: 'DE2', label: 'Germany' },
|
||||
{ value: 'HK1', label: 'Hong Kong' },
|
||||
{ value: 'IN1', label: 'India' },
|
||||
{ value: 'AU1', label: 'Australia' },
|
||||
{ value: 'SZ', label: 'China' },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
loadScrapers();
|
||||
|
|
@ -378,6 +393,13 @@ const PluginsScreen: React.FC = () => {
|
|||
try {
|
||||
const scrapers = await localScraperService.getAvailableScrapers();
|
||||
setInstalledScrapers(scrapers);
|
||||
// preload showbox settings if present
|
||||
const sb = scrapers.find(s => s.id === 'showboxog');
|
||||
if (sb) {
|
||||
const s = await localScraperService.getScraperSettings('showboxog');
|
||||
setShowboxCookie(s.cookie || '');
|
||||
setShowboxRegion(s.region || '');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[ScraperSettings] Failed to load scrapers:', error);
|
||||
}
|
||||
|
|
@ -706,66 +728,117 @@ const PluginsScreen: React.FC = () => {
|
|||
</View>
|
||||
) : (
|
||||
<View style={styles.scrapersContainer}>
|
||||
{installedScrapers.map((scraper) => {
|
||||
// Check if scraper is actually installed (has cached code)
|
||||
const isInstalled = localScraperService.getInstalledScrapers().then(installed =>
|
||||
installed.some(s => s.id === scraper.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||
{scraper.logo ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
||||
)}
|
||||
<View style={styles.scraperInfo}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
||||
{scraper.manifestEnabled === false ? (
|
||||
<View style={[styles.availableIndicator, { backgroundColor: colors.mediumGray }]}>
|
||||
<Text style={styles.availableIndicatorText}>Disabled</Text>
|
||||
</View>
|
||||
) : scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android') ? (
|
||||
<View style={[styles.availableIndicator, { backgroundColor: '#ff9500' }]}>
|
||||
<Text style={styles.availableIndicatorText}>Platform Disabled</Text>
|
||||
</View>
|
||||
) : !scraper.enabled && (
|
||||
<View style={styles.availableIndicator}>
|
||||
<Text style={styles.availableIndicatorText}>Available</Text>
|
||||
</View>
|
||||
)}
|
||||
{installedScrapers.map((scraper) => {
|
||||
return (
|
||||
<View key={scraper.id} style={[styles.scraperItem, !settings.enableLocalScrapers && styles.disabledContainer]}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', width: '100%' }}>
|
||||
{scraper.logo ? (
|
||||
<Image
|
||||
source={{ uri: scraper.logo }}
|
||||
style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledImage]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View style={[styles.scraperLogo, !settings.enableLocalScrapers && styles.disabledContainer]} />
|
||||
)}
|
||||
<View style={styles.scraperInfo}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={[styles.scraperName, !settings.enableLocalScrapers && styles.disabledText]}>{scraper.name}</Text>
|
||||
{scraper.manifestEnabled === false ? (
|
||||
<View style={[styles.availableIndicator, { backgroundColor: colors.mediumGray }]}>
|
||||
<Text style={styles.availableIndicatorText}>Disabled</Text>
|
||||
</View>
|
||||
) : scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android') ? (
|
||||
<View style={[styles.availableIndicator, { backgroundColor: '#ff9500' }]}>
|
||||
<Text style={styles.availableIndicatorText}>Platform Disabled</Text>
|
||||
</View>
|
||||
) : !scraper.enabled && (
|
||||
<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>
|
||||
<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>
|
||||
<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'))}
|
||||
style={{ opacity: (!settings.enableLocalScrapers || scraper.manifestEnabled === false || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))) ? 0.5 : 1 }}
|
||||
/>
|
||||
</View>
|
||||
{scraper.id === 'showboxog' && settings.enableLocalScrapers && (
|
||||
<View style={{ marginTop: 16, width: '100%', paddingTop: 16, borderTopWidth: 1, borderTopColor: colors.elevation3 }}>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>ShowBox Cookie</Text>
|
||||
<TextInput
|
||||
style={[styles.textInput, { marginBottom: 12 }]}
|
||||
value={showboxCookie}
|
||||
onChangeText={setShowboxCookie}
|
||||
placeholder="Paste FebBox ui cookie value"
|
||||
placeholderTextColor={colors.mediumGray}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
multiline={true}
|
||||
numberOfLines={3}
|
||||
/>
|
||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Region</Text>
|
||||
<View style={[styles.qualityChipsContainer, { marginBottom: 16 }]}>
|
||||
{regionOptions.map(opt => {
|
||||
const selected = showboxRegion === opt.value;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={opt.value}
|
||||
style={[styles.qualityChip, selected && styles.qualityChipSelected]}
|
||||
onPress={() => setShowboxRegion(opt.value)}
|
||||
>
|
||||
<Text style={[styles.qualityChipText, selected && styles.qualityChipTextSelected]}>
|
||||
{opt.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
<View style={styles.buttonRow}>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.primaryButton]}
|
||||
onPress={async () => {
|
||||
await localScraperService.setScraperSettings('showboxog', { cookie: showboxCookie, region: showboxRegion });
|
||||
Alert.alert('Saved', 'ShowBox settings updated');
|
||||
}}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.button, styles.secondaryButton]}
|
||||
onPress={async () => {
|
||||
setShowboxCookie('');
|
||||
setShowboxRegion('');
|
||||
await localScraperService.setScraperSettings('showboxog', {});
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Clear</Text>
|
||||
</TouchableOpacity>
|
||||
</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 || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))}
|
||||
style={{ opacity: (!settings.enableLocalScrapers || scraper.manifestEnabled === false || (scraper.disabledPlatforms && scraper.disabledPlatforms.includes(Platform.OS as 'ios' | 'android'))) ? 0.5 : 1 }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ export const StreamsScreen = () => {
|
|||
const insets = useSafeAreaInsets();
|
||||
const route = useRoute<RouteProp<RootStackParamList, 'Streams'>>();
|
||||
const navigation = useNavigation<RootStackNavigationProp>();
|
||||
const { id, type, episodeId, episodeThumbnail } = route.params;
|
||||
const { id, type, episodeId, episodeThumbnail, fromPlayer } = route.params;
|
||||
const { settings } = useSettings();
|
||||
const { currentTheme } = useTheme();
|
||||
const { colors } = currentTheme;
|
||||
|
|
@ -565,17 +565,20 @@ export const StreamsScreen = () => {
|
|||
|
||||
// Reset autoplay state when content changes
|
||||
setAutoplayTriggered(false);
|
||||
if (settings.autoplayBestStream) {
|
||||
if (settings.autoplayBestStream && !fromPlayer) {
|
||||
setIsAutoplayWaiting(true);
|
||||
logger.log('🔄 Autoplay enabled, waiting for best stream...');
|
||||
} else {
|
||||
setIsAutoplayWaiting(false);
|
||||
if (fromPlayer) {
|
||||
logger.log('🚫 Autoplay disabled: returning from player');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkProviders();
|
||||
}, [type, id, episodeId, settings.autoplayBestStream]);
|
||||
}, [type, id, episodeId, settings.autoplayBestStream, fromPlayer]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Trigger entrance animations
|
||||
|
|
@ -1432,7 +1435,7 @@ export const StreamsScreen = () => {
|
|||
<TouchableOpacity
|
||||
style={[
|
||||
styles.backButton,
|
||||
Platform.OS === 'ios' ? { paddingTop: Math.max(insets.top, 12) + 6 } : null
|
||||
Platform.OS === 'ios' ? { marginTop: 20 } : null
|
||||
]}
|
||||
onPress={handleBack}
|
||||
activeOpacity={0.7}
|
||||
|
|
@ -1662,7 +1665,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'box-none',
|
||||
},
|
||||
backButton: {
|
||||
|
|
@ -1670,7 +1673,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: 14,
|
||||
paddingTop: Platform.OS === 'android' ? 45 : 15,
|
||||
paddingTop: 0,
|
||||
},
|
||||
backButtonText: {
|
||||
color: colors.highEmphasis,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ class LocalScraperService {
|
|||
private repositoryUrl: string = '';
|
||||
private repositoryName: string = '';
|
||||
private initialized: boolean = false;
|
||||
private scraperSettingsCache: Record<string, any> | null = null;
|
||||
|
||||
private constructor() {
|
||||
this.initialize();
|
||||
|
|
@ -409,6 +410,38 @@ class LocalScraperService {
|
|||
return Array.from(this.installedScrapers.values());
|
||||
}
|
||||
|
||||
// Per-scraper settings storage
|
||||
async getScraperSettings(scraperId: string): Promise<Record<string, any>> {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
if (!this.scraperSettingsCache) {
|
||||
const raw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
this.scraperSettingsCache = raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
const cache = this.scraperSettingsCache || {};
|
||||
return cache[scraperId] || {};
|
||||
} catch (error) {
|
||||
logger.warn('[LocalScraperService] Failed to get scraper settings for', scraperId, error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async setScraperSettings(scraperId: string, settings: Record<string, any>): Promise<void> {
|
||||
await this.ensureInitialized();
|
||||
try {
|
||||
if (!this.scraperSettingsCache) {
|
||||
const raw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
this.scraperSettingsCache = raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
const cache = this.scraperSettingsCache || {};
|
||||
cache[scraperId] = settings || {};
|
||||
this.scraperSettingsCache = cache;
|
||||
await AsyncStorage.setItem(this.SCRAPER_SETTINGS_KEY, JSON.stringify(cache));
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to set scraper settings for', scraperId, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Get available scrapers from manifest.json (for display in settings)
|
||||
async getAvailableScrapers(): Promise<ScraperInfo[]> {
|
||||
if (!this.repositoryUrl) {
|
||||
|
|
@ -568,12 +601,17 @@ class LocalScraperService {
|
|||
|
||||
logger.log('[LocalScraperService] Executing scraper:', scraper.name);
|
||||
|
||||
// Load per-scraper settings
|
||||
const scraperSettings = await this.getScraperSettings(scraper.id);
|
||||
|
||||
// Create a sandboxed execution environment
|
||||
const results = await this.executeSandboxed(code, {
|
||||
tmdbId,
|
||||
mediaType: type,
|
||||
season,
|
||||
episode
|
||||
episode,
|
||||
scraperId: scraper.id,
|
||||
settings: scraperSettings
|
||||
});
|
||||
|
||||
// Convert results to Nuvio Stream format
|
||||
|
|
@ -602,6 +640,11 @@ class LocalScraperService {
|
|||
const settings = settingsData ? JSON.parse(settingsData) : {};
|
||||
const urlValidationEnabled = settings.enableScraperUrlValidation ?? true;
|
||||
|
||||
// Load per-scraper settings for this run
|
||||
const allScraperSettingsRaw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
const allScraperSettings = allScraperSettingsRaw ? JSON.parse(allScraperSettingsRaw) : {};
|
||||
const perScraperSettings = (params && params.scraperId && allScraperSettings[params.scraperId]) ? allScraperSettings[params.scraperId] : (params?.settings || {});
|
||||
|
||||
// Create a limited global context
|
||||
const moduleExports = {};
|
||||
const moduleObj = { exports: moduleExports };
|
||||
|
|
@ -683,7 +726,10 @@ class LocalScraperService {
|
|||
exports: moduleExports,
|
||||
global: {}, // Empty global object
|
||||
// URL validation setting
|
||||
URL_VALIDATION_ENABLED: urlValidationEnabled
|
||||
URL_VALIDATION_ENABLED: urlValidationEnabled,
|
||||
// Expose per-scraper settings to the plugin code
|
||||
SCRAPER_SETTINGS: perScraperSettings,
|
||||
SCRAPER_ID: params?.scraperId
|
||||
};
|
||||
|
||||
// Execute the scraper code without timeout
|
||||
|
|
|
|||
Loading…
Reference in a new issue