mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-15 06:21:58 +00:00
UI changes for pluginscreen
This commit is contained in:
parent
8d488298cf
commit
5143917117
3 changed files with 234 additions and 53 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -52,3 +52,4 @@ VERSION_UPDATE_README.md
|
||||||
hackintosh-emulator-fix.sh
|
hackintosh-emulator-fix.sh
|
||||||
/ota-builds
|
/ota-builds
|
||||||
src/screens/xavio.md
|
src/screens/xavio.md
|
||||||
|
/nuvio-providers
|
||||||
|
|
|
||||||
|
|
@ -490,16 +490,18 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
modalContent: {
|
modalContent: {
|
||||||
backgroundColor: colors.darkBackground,
|
backgroundColor: colors.darkBackground,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
padding: 24,
|
padding: 20,
|
||||||
margin: 20,
|
margin: 20,
|
||||||
maxHeight: '80%',
|
maxHeight: '70%',
|
||||||
width: screenWidth - 40,
|
width: screenWidth - 40,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.elevation3,
|
||||||
},
|
},
|
||||||
modalTitle: {
|
modalTitle: {
|
||||||
fontSize: 20,
|
fontSize: 18,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: colors.white,
|
color: colors.white,
|
||||||
marginBottom: 16,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
modalText: {
|
modalText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
@ -522,6 +524,82 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
// Compact modal styles
|
||||||
|
modalHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 6,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
compactTextInput: {
|
||||||
|
backgroundColor: colors.darkBackground,
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 12,
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 15,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.elevation3,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
compactExamples: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
quickButton: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.elevation2,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 6,
|
||||||
|
gap: 6,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.elevation3,
|
||||||
|
},
|
||||||
|
quickButtonText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.white,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
formatHint: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.mediumGray,
|
||||||
|
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
|
||||||
|
marginBottom: 16,
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
|
compactActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
compactButton: {
|
||||||
|
flex: 1,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
borderRadius: 6,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: 40,
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
backgroundColor: colors.elevation2,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.elevation3,
|
||||||
|
},
|
||||||
|
cancelButtonText: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
},
|
||||||
|
addButtonText: {
|
||||||
|
color: colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
quickSetupContainer: {
|
quickSetupContainer: {
|
||||||
backgroundColor: colors.elevation2,
|
backgroundColor: colors.elevation2,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
|
@ -563,6 +641,7 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.elevation3,
|
borderColor: colors.elevation3,
|
||||||
|
minHeight: 120,
|
||||||
},
|
},
|
||||||
scraperCardHeader: {
|
scraperCardHeader: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -577,12 +656,14 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
gap: 12,
|
gap: 8,
|
||||||
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
scraperCardMetaItem: {
|
scraperCardMetaItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 4,
|
gap: 2,
|
||||||
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
scraperCardMetaText: {
|
scraperCardMetaText: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
@ -857,8 +938,27 @@ const PluginsScreen: React.FC = () => {
|
||||||
const url = newRepositoryUrl.trim();
|
const url = newRepositoryUrl.trim();
|
||||||
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
|
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Invalid URL Format',
|
'Invalid URL Format',
|
||||||
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
|
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nor include manifest.json:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch/manifest.json\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if URL already includes manifest.json
|
||||||
|
const isManifestUrl = url.includes('/manifest.json');
|
||||||
|
|
||||||
|
// Normalize URL - if it's a manifest URL, extract the base repository URL
|
||||||
|
let normalizedUrl = url;
|
||||||
|
if (isManifestUrl) {
|
||||||
|
normalizedUrl = url.replace('/manifest.json', '');
|
||||||
|
logger.log('[PluginsScreen] Detected manifest URL, extracting base repository URL:', normalizedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional validation for normalized URL
|
||||||
|
if (!normalizedUrl.endsWith('/refs/heads/') && !normalizedUrl.includes('/refs/heads/')) {
|
||||||
|
Alert.alert(
|
||||||
|
'Invalid Repository Structure',
|
||||||
|
'The URL should point to a GitHub repository branch.\n\nExpected format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch'
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -867,7 +967,7 @@ const PluginsScreen: React.FC = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const repoId = await localScraperService.addRepository({
|
const repoId = await localScraperService.addRepository({
|
||||||
name: '', // Let the service fetch from manifest
|
name: '', // Let the service fetch from manifest
|
||||||
url,
|
url: normalizedUrl, // Use normalized URL (without manifest.json)
|
||||||
description: '',
|
description: '',
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
|
|
@ -952,6 +1052,8 @@ const PluginsScreen: React.FC = () => {
|
||||||
const loadScrapers = async () => {
|
const loadScrapers = async () => {
|
||||||
try {
|
try {
|
||||||
const scrapers = await localScraperService.getAvailableScrapers();
|
const scrapers = await localScraperService.getAvailableScrapers();
|
||||||
|
|
||||||
|
|
||||||
setInstalledScrapers(scrapers);
|
setInstalledScrapers(scrapers);
|
||||||
// preload showbox settings if present
|
// preload showbox settings if present
|
||||||
const sb = scrapers.find(s => s.id === 'showboxog');
|
const sb = scrapers.find(s => s.id === 'showboxog');
|
||||||
|
|
@ -1036,14 +1138,20 @@ const PluginsScreen: React.FC = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
|
logger.log('[PluginsScreen] Starting hard refresh of repository...');
|
||||||
|
|
||||||
|
// Force a complete hard refresh by clearing any cached data first
|
||||||
await localScraperService.refreshRepository();
|
await localScraperService.refreshRepository();
|
||||||
await loadScrapers(); // This will now load available scrapers from manifest
|
|
||||||
Alert.alert('Success', 'Repository refreshed successfully');
|
// Load fresh scrapers from the updated repository
|
||||||
|
await loadScrapers();
|
||||||
|
|
||||||
|
Alert.alert('Success', 'Repository refreshed successfully with latest files');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[ScraperSettings] Failed to refresh repository:', error);
|
logger.error('[PluginsScreen] Failed to refresh repository:', error);
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Repository Error',
|
'Repository Error',
|
||||||
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch`
|
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch`
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -1191,7 +1299,25 @@ const PluginsScreen: React.FC = () => {
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
<RefreshControl refreshing={isRefreshing} onRefresh={loadScrapers} />
|
<RefreshControl
|
||||||
|
refreshing={isRefreshing}
|
||||||
|
onRefresh={async () => {
|
||||||
|
try {
|
||||||
|
setIsRefreshing(true);
|
||||||
|
logger.log('[PluginsScreen] Pull-to-refresh: Starting hard refresh...');
|
||||||
|
|
||||||
|
// Force hard refresh of repository
|
||||||
|
await localScraperService.refreshRepository();
|
||||||
|
await loadScrapers();
|
||||||
|
|
||||||
|
logger.log('[PluginsScreen] Pull-to-refresh completed');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[PluginsScreen] Pull-to-refresh failed:', error);
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* Quick Setup banner removed */}
|
{/* Quick Setup banner removed */}
|
||||||
|
|
@ -1489,6 +1615,14 @@ const PluginsScreen: React.FC = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{scraper.supportsExternalPlayer === false && (
|
||||||
|
<View style={styles.scraperCardMetaItem}>
|
||||||
|
<Ionicons name="play-circle" size={12} color={colors.mediumGray} />
|
||||||
|
<Text style={styles.scraperCardMetaText}>
|
||||||
|
No external player
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ShowBox Settings */}
|
{/* ShowBox Settings */}
|
||||||
|
|
@ -1727,43 +1861,66 @@ const PluginsScreen: React.FC = () => {
|
||||||
>
|
>
|
||||||
<View style={styles.modalOverlay}>
|
<View style={styles.modalOverlay}>
|
||||||
<View style={styles.modalContent}>
|
<View style={styles.modalContent}>
|
||||||
<Text style={styles.modalTitle}>Add New Repository</Text>
|
<ScrollView showsVerticalScrollIndicator={false}>
|
||||||
|
<View style={styles.modalHeader}>
|
||||||
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Repository URL</Text>
|
<Ionicons name="add-circle" size={20} color={colors.primary} />
|
||||||
<TextInput
|
<Text style={styles.modalTitle}>Add Repository</Text>
|
||||||
style={styles.textInput}
|
</View>
|
||||||
value={newRepositoryUrl}
|
|
||||||
onChangeText={handleUrlChange}
|
<TextInput
|
||||||
placeholder="https://raw.githubusercontent.com/username/repo/refs/heads/branch"
|
style={styles.compactTextInput}
|
||||||
placeholderTextColor={colors.mediumGray}
|
value={newRepositoryUrl}
|
||||||
autoCapitalize="none"
|
onChangeText={handleUrlChange}
|
||||||
autoCorrect={false}
|
placeholder="Repository URL"
|
||||||
keyboardType="url"
|
placeholderTextColor={colors.mediumGray}
|
||||||
/>
|
autoCapitalize="none"
|
||||||
|
autoCorrect={false}
|
||||||
<View style={styles.buttonRow}>
|
keyboardType="url"
|
||||||
|
multiline={false}
|
||||||
|
numberOfLines={1}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Quick Example */}
|
||||||
|
<View style={styles.compactExamples}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, styles.secondaryButton, { flex: 1 }]}
|
style={styles.quickButton}
|
||||||
|
onPress={() => setNewRepositoryUrl('https://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main')}
|
||||||
|
>
|
||||||
|
<Ionicons name="star" size={14} color={colors.primary} />
|
||||||
|
<Text style={styles.quickButtonText}>Official</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 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={() => {
|
onPress={() => {
|
||||||
setShowAddRepositoryModal(false);
|
setShowAddRepositoryModal(false);
|
||||||
setNewRepositoryUrl('');
|
setNewRepositoryUrl('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={styles.secondaryButtonText}>Cancel</Text>
|
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.button, styles.primaryButton, { flex: 1 }]}
|
style={[styles.compactButton, styles.addButton, (!newRepositoryUrl.trim() || isLoading) && styles.disabledButton]}
|
||||||
onPress={handleAddRepository}
|
onPress={handleAddRepository}
|
||||||
disabled={isLoading}
|
disabled={!newRepositoryUrl.trim() || isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<ActivityIndicator size="small" color="#ffffff" />
|
<ActivityIndicator size="small" color={colors.white} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.buttonText}>Add Repository</Text>
|
<Text style={styles.addButtonText}>Add</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export interface ScraperInfo {
|
||||||
formats?: string[];
|
formats?: string[];
|
||||||
supportedFormats?: string[];
|
supportedFormats?: string[];
|
||||||
repositoryId?: string; // Which repository this scraper came from
|
repositoryId?: string; // Which repository this scraper came from
|
||||||
|
supportsExternalPlayer?: boolean; // Whether this scraper supports external players
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RepositoryInfo {
|
export interface RepositoryInfo {
|
||||||
|
|
@ -600,14 +601,25 @@ class LocalScraperService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
logger.log('[LocalScraperService] Fetching repository manifest from:', this.repositoryUrl);
|
||||||
|
|
||||||
|
// Clear all cached scraper code for this repository to force hard refresh
|
||||||
|
const cachedScraperIds = Array.from(this.installedScrapers.keys());
|
||||||
|
for (const scraperId of cachedScraperIds) {
|
||||||
|
const scraper = this.installedScrapers.get(scraperId);
|
||||||
|
if (scraper && scraper.repositoryId === this.currentRepositoryId) {
|
||||||
|
this.scraperCode.delete(scraperId);
|
||||||
|
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||||
|
logger.log('[LocalScraperService] Cleared cached code for scraper:', scraper.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch manifest with cache busting
|
// Fetch manifest with cache busting
|
||||||
const baseManifestUrl = 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 manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||||
|
|
||||||
const response = await axios.get(manifestUrl, {
|
const response = await axios.get(manifestUrl, {
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
|
|
@ -700,7 +712,17 @@ class LocalScraperService {
|
||||||
|
|
||||||
logger.log('[LocalScraperService] Downloading scraper:', scraperInfo.name);
|
logger.log('[LocalScraperService] Downloading scraper:', scraperInfo.name);
|
||||||
|
|
||||||
const response = await axios.get(scraperUrl, { timeout: 15000 });
|
// Add cache-busting parameters to force fresh download
|
||||||
|
const scraperUrlWithCacheBust = `${scraperUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||||
|
|
||||||
|
const response = await axios.get(scraperUrlWithCacheBust, {
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Expires': '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
const scraperCode = response.data;
|
const scraperCode = response.data;
|
||||||
|
|
||||||
// Store scraper info and code
|
// Store scraper info and code
|
||||||
|
|
@ -836,19 +858,19 @@ class LocalScraperService {
|
||||||
logger.log('[LocalScraperService] Fetching available scrapers from manifest');
|
logger.log('[LocalScraperService] Fetching available scrapers from manifest');
|
||||||
|
|
||||||
// Fetch manifest with cache busting
|
// Fetch manifest with cache busting
|
||||||
const baseManifestUrl = 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 manifestUrl = `${baseManifestUrl}?t=${Date.now()}&v=${Math.random()}`;
|
||||||
|
|
||||||
const response = await axios.get(manifestUrl, {
|
const response = await axios.get(manifestUrl, {
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Pragma': 'no-cache',
|
'Pragma': 'no-cache',
|
||||||
'Expires': '0'
|
'Expires': '0'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const manifest: ScraperManifest = response.data;
|
const manifest: ScraperManifest = response.data;
|
||||||
|
|
||||||
// Store repository name from manifest
|
// Store repository name from manifest
|
||||||
|
|
@ -872,6 +894,7 @@ class LocalScraperService {
|
||||||
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false
|
enabled: scraperInfo.enabled ? (installedScraper?.enabled ?? false) : false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Normalize formats fields (support both `formats` and `supportedFormats`)
|
// Normalize formats fields (support both `formats` and `supportedFormats`)
|
||||||
const anyScraper: any = scraperWithManifestData as any;
|
const anyScraper: any = scraperWithManifestData as any;
|
||||||
if (typeof anyScraper.formats === 'string') {
|
if (typeof anyScraper.formats === 'string') {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue