mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-31 00:29:22 +00:00
added support for multiple addon installation of the same addon.id
This commit is contained in:
parent
e5fb18fe5c
commit
83bb91e1d1
7 changed files with 143 additions and 79 deletions
|
|
@ -2439,7 +2439,7 @@ PODS:
|
|||
- SDWebImageSVGCoder (~> 1.7.0)
|
||||
- SDWebImageWebPCoder (~> 0.14)
|
||||
- Yoga
|
||||
- RNGestureHandler (2.29.1):
|
||||
- RNGestureHandler (2.30.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2461,7 +2461,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNReanimated (4.2.0):
|
||||
- RNReanimated (4.2.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2483,10 +2483,10 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated (= 4.2.0)
|
||||
- RNReanimated/reanimated (= 4.2.1)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated (4.2.0):
|
||||
- RNReanimated/reanimated (4.2.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2508,10 +2508,10 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated/apple (= 4.2.0)
|
||||
- RNReanimated/reanimated/apple (= 4.2.1)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated/apple (4.2.0):
|
||||
- RNReanimated/reanimated/apple (4.2.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2535,7 +2535,7 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNScreens (4.18.0):
|
||||
- RNScreens (4.20.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2557,9 +2557,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNScreens/common (= 4.18.0)
|
||||
- RNScreens/common (= 4.20.0)
|
||||
- Yoga
|
||||
- RNScreens/common (4.18.0):
|
||||
- RNScreens/common (4.20.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -3309,9 +3309,9 @@ SPEC CHECKSUMS:
|
|||
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
|
||||
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
|
||||
RNFastImage: 2d36f4cfed9b2342f94f8591c8be69dd047ac67c
|
||||
RNGestureHandler: 723f29dac55e25f109d263ed65cecc4b9c4bd46a
|
||||
RNReanimated: e1c71e6e693a66b203ae98773347b625d3cc85ee
|
||||
RNScreens: 61c18865ab074f4d995ac8d7cf5060522a649d05
|
||||
RNGestureHandler: e0d0bce5599f6120b7adf90c38d2805e2935795f
|
||||
RNReanimated: 8a7182314bb7afc01041a529e409a9112c007a50
|
||||
RNScreens: a00979e0d17609f1c9b0f97881c34550fc4565bf
|
||||
RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
|
||||
RNSVG: cf9ae78f2edf2988242c71a6392d15ff7dd62522
|
||||
RNVectorIcons: 4351544f100d4f12cac156a7c13399e60bab3e26
|
||||
|
|
|
|||
|
|
@ -729,8 +729,6 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
|
||||
return (
|
||||
<View style={[styles.container, {
|
||||
width: playerState.screenDimensions.width,
|
||||
height: playerState.screenDimensions.height,
|
||||
position: 'absolute', top: 0, left: 0
|
||||
}]}>
|
||||
<LoadingOverlay
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
|
|||
// Reset
|
||||
logoOpacity.value = 0;
|
||||
logoScale.value = 1;
|
||||
|
||||
|
||||
// Start animations after 1 second delay
|
||||
logoOpacity.value = withDelay(
|
||||
1000,
|
||||
|
|
@ -53,7 +53,7 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
|
|||
easing: Easing.out(Easing.cubic),
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
logoScale.value = withDelay(
|
||||
1000,
|
||||
withRepeat(
|
||||
|
|
@ -82,24 +82,23 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
|
|||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.openingOverlay,
|
||||
{
|
||||
opacity: backgroundFadeAnim,
|
||||
zIndex: 3000,
|
||||
},
|
||||
// Cast to any to support both number and string dimensions
|
||||
{ width, height } as any,
|
||||
{ width: '100%', height: '100%' },
|
||||
]}
|
||||
>
|
||||
{backdrop && (
|
||||
<Animated.View style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
opacity: backdropImageOpacityAnim
|
||||
}
|
||||
]}>
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
opacity: backdropImageOpacityAnim
|
||||
}
|
||||
]}>
|
||||
<Image
|
||||
source={{ uri: backdrop }}
|
||||
style={StyleSheet.absoluteFillObject}
|
||||
|
|
@ -117,15 +116,15 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
|
|||
locations={[0, 0.3, 0.7, 1]}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.loadingCloseButton}
|
||||
onPress={onClose}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons name="close" size={24} color="#ffffff" />
|
||||
</TouchableOpacity>
|
||||
|
||||
|
||||
<View style={styles.openingContent}>
|
||||
{hasLogo && logo ? (
|
||||
<Reanimated.View style={[
|
||||
|
|
|
|||
|
|
@ -669,14 +669,14 @@ const AddonsScreen = () => {
|
|||
};
|
||||
|
||||
const moveAddonUp = (addon: ExtendedManifest) => {
|
||||
if (stremioService.moveAddonUp(addon.id)) {
|
||||
if (stremioService.moveAddonUp(addon.installationId || addon.id)) {
|
||||
// Refresh the list to reflect the new order
|
||||
loadAddons();
|
||||
}
|
||||
};
|
||||
|
||||
const moveAddonDown = (addon: ExtendedManifest) => {
|
||||
if (stremioService.moveAddonDown(addon.id)) {
|
||||
if (stremioService.moveAddonDown(addon.installationId || addon.id)) {
|
||||
// Refresh the list to reflect the new order
|
||||
loadAddons();
|
||||
}
|
||||
|
|
@ -690,8 +690,8 @@ const AddonsScreen = () => {
|
|||
{
|
||||
label: t('addons.uninstall_button'),
|
||||
onPress: async () => {
|
||||
await stremioService.removeAddon(addon.id);
|
||||
setAddons(prev => prev.filter(a => a.id !== addon.id));
|
||||
await stremioService.removeAddon(addon.installationId || addon.id);
|
||||
setAddons(prev => prev.filter(a => (a.installationId || a.id) !== (addon.installationId || addon.id)));
|
||||
// Ensure we re-read from storage/order to avoid reappearing on next load
|
||||
await loadAddons();
|
||||
},
|
||||
|
|
@ -1066,7 +1066,7 @@ const AddonsScreen = () => {
|
|||
) : (
|
||||
addons.map((addon, index) => (
|
||||
<View
|
||||
key={addon.id}
|
||||
key={addon.installationId || addon.id}
|
||||
style={{ marginBottom: index === addons.length - 1 ? 32 : 0 }}
|
||||
>
|
||||
{renderAddonItem({ item: addon, index })}
|
||||
|
|
|
|||
|
|
@ -308,9 +308,12 @@ const CatalogSettingsScreen = () => {
|
|||
addons.forEach(addon => {
|
||||
if (addon.catalogs && addon.catalogs.length > 0) {
|
||||
const uniqueCatalogs = new Map<string, CatalogSetting>();
|
||||
// Use installationId if available, otherwise fallback to id
|
||||
const uniqueAddonId = addon.installationId || addon.id;
|
||||
|
||||
addon.catalogs.forEach(catalog => {
|
||||
const settingKey = `${addon.id}:${catalog.type}:${catalog.id}`;
|
||||
// Generate a truly unique key using installationId
|
||||
const settingKey = `${uniqueAddonId}:${catalog.type}:${catalog.id}`;
|
||||
let displayName = catalog.name || catalog.id;
|
||||
const catalogType = catalog.type === 'movie' ? 'Movies' : catalog.type === 'series' ? 'TV Shows' : catalog.type.charAt(0).toUpperCase() + catalog.type.slice(1);
|
||||
|
||||
|
|
@ -335,7 +338,7 @@ const CatalogSettingsScreen = () => {
|
|||
}
|
||||
|
||||
uniqueCatalogs.set(settingKey, {
|
||||
addonId: addon.id,
|
||||
addonId: uniqueAddonId, // Store unique ID here
|
||||
catalogId: catalog.id,
|
||||
type: catalog.type,
|
||||
name: displayName,
|
||||
|
|
@ -351,11 +354,15 @@ const CatalogSettingsScreen = () => {
|
|||
// Group settings by addon name
|
||||
const grouped: GroupedCatalogs = {};
|
||||
availableCatalogs.forEach(setting => {
|
||||
const addon = addons.find(a => a.id === setting.addonId);
|
||||
// Find addon by matching either installationId or id
|
||||
const addon = addons.find(a => (a.installationId || a.id) === setting.addonId);
|
||||
if (!addon) return;
|
||||
|
||||
if (!grouped[setting.addonId]) {
|
||||
grouped[setting.addonId] = {
|
||||
// Use the unique addon ID (installationId) as the group key
|
||||
const groupKey = setting.addonId;
|
||||
|
||||
if (!grouped[groupKey]) {
|
||||
grouped[groupKey] = {
|
||||
name: addon.name,
|
||||
catalogs: [],
|
||||
expanded: true,
|
||||
|
|
@ -363,9 +370,9 @@ const CatalogSettingsScreen = () => {
|
|||
};
|
||||
}
|
||||
|
||||
grouped[setting.addonId].catalogs.push(setting);
|
||||
grouped[groupKey].catalogs.push(setting);
|
||||
if (setting.enabled) {
|
||||
grouped[setting.addonId].enabledCount++;
|
||||
grouped[groupKey].enabledCount++;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,10 @@ export const useStreamsScreen = () => {
|
|||
|
||||
const getProviderPriority = (addonId: string): number => {
|
||||
const installedAddons = stremioService.getInstalledAddons();
|
||||
const addonIndex = installedAddons.findIndex(addon => addon.id === addonId);
|
||||
// Check installationId first, then id for backward compatibility
|
||||
const addonIndex = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === addonId) || addon.id === addonId
|
||||
);
|
||||
if (addonIndex !== -1) {
|
||||
return 50 - addonIndex;
|
||||
}
|
||||
|
|
@ -759,7 +762,9 @@ export const useStreamsScreen = () => {
|
|||
const pluginProviders: string[] = [];
|
||||
|
||||
Array.from(allProviders).forEach(provider => {
|
||||
const isInstalledAddon = installedAddons.some(addon => addon.id === provider);
|
||||
const isInstalledAddon = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === provider) || addon.id === provider
|
||||
);
|
||||
if (isInstalledAddon) {
|
||||
addonProviders.push(provider);
|
||||
} else {
|
||||
|
|
@ -771,12 +776,18 @@ export const useStreamsScreen = () => {
|
|||
|
||||
addonProviders
|
||||
.sort((a, b) => {
|
||||
const indexA = installedAddons.findIndex(addon => addon.id === a);
|
||||
const indexB = installedAddons.findIndex(addon => addon.id === b);
|
||||
const indexA = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === a) || addon.id === a
|
||||
);
|
||||
const indexB = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === b) || addon.id === b
|
||||
);
|
||||
return indexA - indexB;
|
||||
})
|
||||
.forEach(provider => {
|
||||
const installedAddon = installedAddons.find(addon => addon.id === provider);
|
||||
const installedAddon = installedAddons.find(addon =>
|
||||
(addon.installationId && addon.installationId === provider) || addon.id === provider
|
||||
);
|
||||
filterChips.push({ id: provider, name: installedAddon?.name || provider });
|
||||
});
|
||||
|
||||
|
|
@ -806,8 +817,12 @@ export const useStreamsScreen = () => {
|
|||
{ id: 'all', name: 'All Providers' },
|
||||
...Array.from(allProviders)
|
||||
.sort((a, b) => {
|
||||
const indexA = installedAddons.findIndex(addon => addon.id === a);
|
||||
const indexB = installedAddons.findIndex(addon => addon.id === b);
|
||||
const indexA = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === a) || addon.id === a
|
||||
);
|
||||
const indexB = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === b) || addon.id === b
|
||||
);
|
||||
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
|
||||
if (indexA !== -1) return -1;
|
||||
if (indexB !== -1) return 1;
|
||||
|
|
@ -815,7 +830,9 @@ export const useStreamsScreen = () => {
|
|||
})
|
||||
.map(provider => {
|
||||
const addonInfo = streams[provider];
|
||||
const installedAddon = installedAddons.find(addon => addon.id === provider);
|
||||
const installedAddon = installedAddons.find(addon =>
|
||||
(addon.installationId && addon.installationId === provider) || addon.id === provider
|
||||
);
|
||||
let displayName = provider;
|
||||
if (installedAddon) displayName = installedAddon.name;
|
||||
else if (addonInfo?.addonName) displayName = addonInfo.addonName;
|
||||
|
|
@ -847,7 +864,9 @@ export const useStreamsScreen = () => {
|
|||
|
||||
// Legacy: handle old grouped-plugins filter (fallback)
|
||||
if (settings.streamDisplayMode === 'grouped' && selectedProvider === 'grouped-plugins') {
|
||||
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
|
||||
const isInstalledAddon = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === addonId) || addon.id === addonId
|
||||
);
|
||||
return !isInstalledAddon;
|
||||
}
|
||||
|
||||
|
|
@ -856,8 +875,12 @@ export const useStreamsScreen = () => {
|
|||
|
||||
// Sort entries: installed addons first (in their installation order), then plugins
|
||||
const sortedEntries = filteredEntries.sort(([addonIdA], [addonIdB]) => {
|
||||
const isAddonA = installedAddons.some(addon => addon.id === addonIdA);
|
||||
const isAddonB = installedAddons.some(addon => addon.id === addonIdB);
|
||||
const isAddonA = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === addonIdA) || addon.id === addonIdA
|
||||
);
|
||||
const isAddonB = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === addonIdB) || addon.id === addonIdB
|
||||
);
|
||||
|
||||
// Addons always come before plugins
|
||||
if (isAddonA && !isAddonB) return -1;
|
||||
|
|
@ -865,8 +888,12 @@ export const useStreamsScreen = () => {
|
|||
|
||||
// Both are addons - sort by installation order
|
||||
if (isAddonA && isAddonB) {
|
||||
const indexA = installedAddons.findIndex(addon => addon.id === addonIdA);
|
||||
const indexB = installedAddons.findIndex(addon => addon.id === addonIdB);
|
||||
const indexA = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === addonIdA) || addon.id === addonIdA
|
||||
);
|
||||
const indexB = installedAddons.findIndex(addon =>
|
||||
(addon.installationId && addon.installationId === addonIdB) || addon.id === addonIdB
|
||||
);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
|
|
@ -884,7 +911,9 @@ export const useStreamsScreen = () => {
|
|||
const pluginStreams: Stream[] = [];
|
||||
|
||||
sortedEntries.forEach(([addonId, { streams: providerStreams }]) => {
|
||||
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
|
||||
const isInstalledAddon = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === addonId) || addon.id === addonId
|
||||
);
|
||||
|
||||
if (isInstalledAddon) {
|
||||
addonStreams.push(...providerStreams);
|
||||
|
|
@ -931,7 +960,9 @@ export const useStreamsScreen = () => {
|
|||
|
||||
return sortedEntries
|
||||
.map(([addonId, { addonName, streams: providerStreams }]) => {
|
||||
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
|
||||
const isInstalledAddon = installedAddons.some(addon =>
|
||||
(addon.installationId && addon.installationId === addonId) || addon.id === addonId
|
||||
);
|
||||
let filteredStreams = providerStreams;
|
||||
|
||||
if (!isInstalledAddon) {
|
||||
|
|
|
|||
|
|
@ -208,6 +208,7 @@ export interface Manifest {
|
|||
background?: string; // Background image URL
|
||||
logo?: string; // Logo URL
|
||||
contactEmail?: string; // Contact email
|
||||
installationId?: string; // Unique ID for this specific installation
|
||||
}
|
||||
|
||||
// Config object for addon configuration per protocol
|
||||
|
|
@ -422,7 +423,11 @@ class StremioService {
|
|||
this.installedAddons = new Map();
|
||||
for (const addon of parsed) {
|
||||
if (addon && addon.id) {
|
||||
this.installedAddons.set(addon.id, addon);
|
||||
// Migration: Ensure installationId exists
|
||||
if (!addon.installationId) {
|
||||
addon.installationId = addon.id;
|
||||
}
|
||||
this.installedAddons.set(addon.installationId, addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,14 +436,21 @@ class StremioService {
|
|||
const cinemetaId = 'com.linvo.cinemeta';
|
||||
const hasUserRemovedCinemeta = await this.hasUserRemovedAddon(cinemetaId);
|
||||
|
||||
if (!this.installedAddons.has(cinemetaId) && !hasUserRemovedCinemeta) {
|
||||
// Check if any installed addon has this ID (using valid values iteration)
|
||||
const isCinemetaInstalled = Array.from(this.installedAddons.values()).some(a => a.id === cinemetaId);
|
||||
|
||||
if (!isCinemetaInstalled && !hasUserRemovedCinemeta) {
|
||||
try {
|
||||
const cinemetaManifest = await this.getManifest('https://v3-cinemeta.strem.io/manifest.json');
|
||||
// For default addons, we can use the ID as installationId to keep it clean (or generate one)
|
||||
// Using ID ensures only one instance of default addon is auto-installed
|
||||
cinemetaManifest.installationId = cinemetaId;
|
||||
this.installedAddons.set(cinemetaId, cinemetaManifest);
|
||||
} catch (error) {
|
||||
// Fallback to minimal manifest if fetch fails
|
||||
const fallbackManifest: Manifest = {
|
||||
id: cinemetaId,
|
||||
installationId: cinemetaId,
|
||||
name: 'Cinemeta',
|
||||
version: '3.0.13',
|
||||
description: 'Provides metadata for movies and series from TheTVDB, TheMovieDB, etc.',
|
||||
|
|
@ -483,13 +495,17 @@ class StremioService {
|
|||
const opensubsId = 'org.stremio.opensubtitlesv3';
|
||||
const hasUserRemovedOpenSubtitles = await this.hasUserRemovedAddon(opensubsId);
|
||||
|
||||
if (!this.installedAddons.has(opensubsId) && !hasUserRemovedOpenSubtitles) {
|
||||
const isOpenSubsInstalled = Array.from(this.installedAddons.values()).some(a => a.id === opensubsId);
|
||||
|
||||
if (!isOpenSubsInstalled && !hasUserRemovedOpenSubtitles) {
|
||||
try {
|
||||
const opensubsManifest = await this.getManifest('https://opensubtitles-v3.strem.io/manifest.json');
|
||||
opensubsManifest.installationId = opensubsId;
|
||||
this.installedAddons.set(opensubsId, opensubsManifest);
|
||||
} catch (error) {
|
||||
const fallbackManifest: Manifest = {
|
||||
id: opensubsId,
|
||||
installationId: opensubsId,
|
||||
name: 'OpenSubtitles v3',
|
||||
version: '1.0.0',
|
||||
description: 'OpenSubtitles v3 Addon for Stremio',
|
||||
|
|
@ -522,8 +538,9 @@ class StremioService {
|
|||
this.addonOrder = this.addonOrder.filter(id => this.installedAddons.has(id));
|
||||
}
|
||||
|
||||
// Add Cinemeta to order only if user hasn't removed it
|
||||
// Add Cinemeta to order only if user hasn't removed it and it's installed
|
||||
const hasUserRemovedCinemetaOrder = await this.hasUserRemovedAddon(cinemetaId);
|
||||
// We check if the installationId (which is cinemetaId for default) is in the map
|
||||
if (!this.addonOrder.includes(cinemetaId) && this.installedAddons.has(cinemetaId) && !hasUserRemovedCinemetaOrder) {
|
||||
this.addonOrder.push(cinemetaId);
|
||||
}
|
||||
|
|
@ -651,43 +668,54 @@ class StremioService {
|
|||
async installAddon(url: string): Promise<void> {
|
||||
const manifest = await this.getManifest(url);
|
||||
if (manifest && manifest.id) {
|
||||
this.installedAddons.set(manifest.id, manifest);
|
||||
// Generate a unique installation ID
|
||||
const installationId = `${manifest.id}-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
||||
manifest.installationId = installationId;
|
||||
|
||||
this.installedAddons.set(installationId, manifest);
|
||||
|
||||
// If addon was previously removed by user, unmark it on reinstall and clean up
|
||||
await this.unmarkAddonAsRemovedByUser(manifest.id);
|
||||
await this.cleanupRemovedAddonFromStorage(manifest.id);
|
||||
|
||||
// Add to order if not already present (new addons go to the end)
|
||||
if (!this.addonOrder.includes(manifest.id)) {
|
||||
this.addonOrder.push(manifest.id);
|
||||
}
|
||||
// Note: cleanupRemovedAddonFromStorage takes an installationId (from addonOrder context),
|
||||
// but here we are dealing with a new installationId, strictly speaking.
|
||||
// However, we might want to cleanup any stray legacy entries for this addon ID if we wanted strict uniqueness,
|
||||
// but we clearly support duplicates now, so we don't need to clean up other instances.
|
||||
// We ONLY keep the unmarkAddonAsRemovedByUser to allow re-auto-install of defaults if user manually installs them.
|
||||
|
||||
// Add to order (new addons go to the end)
|
||||
this.addonOrder.push(installationId);
|
||||
|
||||
await this.saveInstalledAddons();
|
||||
await this.saveAddonOrder();
|
||||
// Emit an event that an addon was added
|
||||
addonEmitter.emit(ADDON_EVENTS.ADDON_ADDED, manifest.id);
|
||||
addonEmitter.emit(ADDON_EVENTS.ADDON_ADDED, installationId);
|
||||
} else {
|
||||
throw new Error('Invalid addon manifest');
|
||||
}
|
||||
}
|
||||
|
||||
async removeAddon(id: string): Promise<void> {
|
||||
// Allow removal of any addon, including pre-installed ones like Cinemeta
|
||||
if (this.installedAddons.has(id)) {
|
||||
this.installedAddons.delete(id);
|
||||
async removeAddon(installationId: string): Promise<void> {
|
||||
// Allow removal of any addon
|
||||
if (this.installedAddons.has(installationId)) {
|
||||
const addon = this.installedAddons.get(installationId);
|
||||
this.installedAddons.delete(installationId);
|
||||
// Remove from order
|
||||
this.addonOrder = this.addonOrder.filter(addonId => addonId !== id);
|
||||
this.addonOrder = this.addonOrder.filter(id => id !== installationId);
|
||||
|
||||
// Track user explicit removal for any addon (tombstone)
|
||||
await this.markAddonAsRemovedByUser(id);
|
||||
// Proactively clean up any persisted orders/legacy keys for this addon
|
||||
await this.cleanupRemovedAddonFromStorage(id);
|
||||
// Track user explicit removal for the addon ID (tombstone to prevent auto-reinstall of defaults)
|
||||
if (addon && addon.id) {
|
||||
await this.markAddonAsRemovedByUser(addon.id);
|
||||
}
|
||||
|
||||
// Clean up this specific installation from storage keys
|
||||
await this.cleanupRemovedAddonFromStorage(installationId);
|
||||
|
||||
// Persist removals before app possibly exits
|
||||
await this.saveInstalledAddons();
|
||||
await this.saveAddonOrder();
|
||||
// Emit an event that an addon was removed
|
||||
addonEmitter.emit(ADDON_EVENTS.ADDON_REMOVED, id);
|
||||
addonEmitter.emit(ADDON_EVENTS.ADDON_REMOVED, installationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1402,7 +1430,7 @@ class StremioService {
|
|||
try {
|
||||
if (!addon.url) {
|
||||
logger.warn(`⚠️ [getStreams] Addon ${addon.id} has no URL`);
|
||||
if (callback) callback(null, addon.id, addon.name, new Error('Addon has no URL'));
|
||||
if (callback) callback(null, addon.installationId || addon.id, addon.name, new Error('Addon has no URL'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1427,12 +1455,13 @@ class StremioService {
|
|||
|
||||
if (callback) {
|
||||
// Call callback with processed streams (can be empty array)
|
||||
callback(processedStreams, addon.id, addon.name, null);
|
||||
// Use installationId if available to distinct between multiple installations of same addon
|
||||
callback(processedStreams, addon.installationId || addon.id, addon.name, null);
|
||||
}
|
||||
} catch (error) {
|
||||
if (callback) {
|
||||
// Call callback with error
|
||||
callback(null, addon.id, addon.name, error as Error);
|
||||
callback(null, addon.installationId || addon.id, addon.name, error as Error);
|
||||
}
|
||||
}
|
||||
})(); // Immediately invoke the async function
|
||||
|
|
@ -1613,7 +1642,7 @@ class StremioService {
|
|||
name: name,
|
||||
title: displayTitle,
|
||||
addonName: addon.name,
|
||||
addonId: addon.id,
|
||||
addonId: addon.installationId || addon.id,
|
||||
|
||||
// Include description as-is to preserve full details
|
||||
description: stream.description,
|
||||
|
|
|
|||
Loading…
Reference in a new issue