mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 02:35:14 +00:00
Better naming for different functions.
This commit is contained in:
parent
151f654dd9
commit
8584aae675
5 changed files with 179 additions and 81 deletions
|
|
@ -245,6 +245,82 @@ class SeriesViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
serializer = EpisodeSerializer(episodes, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='provider-info')
|
||||
def series_info(self, request, pk=None):
|
||||
"""Get detailed series information, refreshing from provider if needed"""
|
||||
series = self.get_object()
|
||||
|
||||
if not series.m3u_account:
|
||||
return Response(
|
||||
{'error': 'No M3U account associated with this series'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
# Check if we should refresh data (optional force refresh parameter)
|
||||
force_refresh = request.query_params.get('force_refresh', 'false').lower() == 'true'
|
||||
refresh_interval_hours = int(request.query_params.get("refresh_interval", 24)) # Default to 24 hours
|
||||
|
||||
now = timezone.now()
|
||||
last_refreshed = series.updated_at or series.created_at or now - timedelta(days=1)
|
||||
|
||||
if force_refresh or (now - last_refreshed) > timedelta(hours=refresh_interval_hours):
|
||||
# Use existing refresh logic
|
||||
from .tasks import refresh_series_episodes
|
||||
account = series.m3u_account
|
||||
if account and account.is_active:
|
||||
refresh_series_episodes(account, series, series.series_id)
|
||||
series.refresh_from_db() # Reload from database after refresh
|
||||
|
||||
# Return the database data (which should now be fresh)
|
||||
response_data = {
|
||||
'id': series.id,
|
||||
'series_id': series.series_id,
|
||||
'name': series.name,
|
||||
'description': series.description,
|
||||
'year': series.year,
|
||||
'genre': series.genre,
|
||||
'rating': series.rating,
|
||||
'tmdb_id': series.tmdb_id,
|
||||
'imdb_id': series.imdb_id,
|
||||
'category_id': series.category.id if series.category else None,
|
||||
'category_name': series.category.name if series.category else None,
|
||||
'cover': series.logo.url if series.logo else None,
|
||||
'last_refreshed': series.updated_at,
|
||||
'custom_properties': series.custom_properties or {},
|
||||
}
|
||||
|
||||
# Add episodes info if requested
|
||||
include_episodes = request.query_params.get('include_episodes', 'false').lower() == 'true'
|
||||
if include_episodes:
|
||||
episodes_by_season = {}
|
||||
for episode in series.episodes.all().order_by('season_number', 'episode_number'):
|
||||
season_key = str(episode.season_number or 0)
|
||||
if season_key not in episodes_by_season:
|
||||
episodes_by_season[season_key] = []
|
||||
|
||||
episodes_by_season[season_key].append({
|
||||
'id': episode.stream_id,
|
||||
'title': episode.name,
|
||||
'episode_num': episode.episode_number,
|
||||
'season_number': episode.season_number,
|
||||
'plot': episode.description,
|
||||
'duration_secs': episode.duration * 60 if episode.duration else None,
|
||||
'rating': episode.rating,
|
||||
'container_extension': episode.container_extension,
|
||||
})
|
||||
|
||||
response_data['episodes'] = episodes_by_season
|
||||
|
||||
return Response(response_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching series info for series {pk}: {str(e)}")
|
||||
return Response(
|
||||
{'error': f'Failed to fetch series information: {str(e)}'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
|
||||
class VODCategoryFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ router = DefaultRouter()
|
|||
router.register(r'useragents', UserAgentViewSet, basename='useragent')
|
||||
router.register(r'streamprofiles', StreamProfileViewSet, basename='streamprofile')
|
||||
router.register(r'settings', CoreSettingsViewSet, basename='coresettings')
|
||||
router.register(r'settings', CoreSettingsViewSet, basename='settings')
|
||||
urlpatterns = [
|
||||
path('settings/env/', environment, name='token_refresh'),
|
||||
path('version/', version, name='version'),
|
||||
|
|
|
|||
|
|
@ -1700,28 +1700,47 @@ export default class API {
|
|||
}
|
||||
|
||||
// VOD Methods
|
||||
static async getVODs(params = new URLSearchParams()) {
|
||||
static async getMovies(params = new URLSearchParams()) {
|
||||
try {
|
||||
const response = await request(
|
||||
`${host}/api/vod/movies/?${params.toString()}`
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve VODs', e);
|
||||
errorNotification('Failed to retrieve movies', e);
|
||||
}
|
||||
}
|
||||
|
||||
static async getVODSeries(params = new URLSearchParams()) {
|
||||
static async getSeries(params = new URLSearchParams()) {
|
||||
try {
|
||||
const response = await request(
|
||||
`${host}/api/vod/series/?${params.toString()}`
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve VOD series', e);
|
||||
errorNotification('Failed to retrieve series', e);
|
||||
}
|
||||
}
|
||||
|
||||
static async getMovieDetails(movieId) {
|
||||
try {
|
||||
const response = await request(`${host}/api/vod/movies/${movieId}/`);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve movie details', e);
|
||||
}
|
||||
}
|
||||
|
||||
static async getMovieProviderInfo(movieId) {
|
||||
try {
|
||||
const response = await request(`${host}/api/vod/movies/${movieId}/provider-info/`);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve movie provider info', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static async getSeriesEpisodes(seriesId) {
|
||||
try {
|
||||
const response = await request(
|
||||
|
|
@ -1742,23 +1761,6 @@ export default class API {
|
|||
}
|
||||
}
|
||||
|
||||
static async getVODInfo(vodId) {
|
||||
try {
|
||||
const response = await request(`${host}/api/vod/movies/${vodId}/`);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve VOD info', e);
|
||||
}
|
||||
}
|
||||
|
||||
static async getVODInfoFromProvider(vodId) {
|
||||
try {
|
||||
const response = await request(`${host}/api/vod/movies/${vodId}/provider-info/`);
|
||||
return response;
|
||||
} catch (e) {
|
||||
errorNotification('Failed to retrieve VOD info from provider', e);
|
||||
}
|
||||
}
|
||||
|
||||
static async getSeriesInfo(seriesId) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -213,8 +213,9 @@ const SeriesCard = ({ series, onClick }) => {
|
|||
};
|
||||
|
||||
const SeriesModal = ({ series, opened, onClose }) => {
|
||||
const { fetchSeriesEpisodes, vods, loading } = useVODStore();
|
||||
const { fetchSeriesEpisodes, episodes, loading } = useVODStore();
|
||||
const showVideo = useVideoStore((s) => s.showVideo);
|
||||
const env_mode = useSettingsStore((s) => s.environment.env_mode);
|
||||
|
||||
useEffect(() => {
|
||||
if (opened && series) {
|
||||
|
|
@ -222,8 +223,8 @@ const SeriesModal = ({ series, opened, onClose }) => {
|
|||
}
|
||||
}, [opened, series, fetchSeriesEpisodes]);
|
||||
|
||||
const episodes = Object.values(vods).filter(
|
||||
vod => vod.type === 'episode' && vod.series?.id === series?.id
|
||||
const seriesEpisodes = Object.values(episodes).filter(
|
||||
episode => episode.series?.id === series?.id
|
||||
).sort((a, b) => {
|
||||
if (a.season_number !== b.season_number) {
|
||||
return (a.season_number || 0) - (b.season_number || 0);
|
||||
|
|
@ -232,8 +233,13 @@ const SeriesModal = ({ series, opened, onClose }) => {
|
|||
});
|
||||
|
||||
const handlePlayEpisode = (episode) => {
|
||||
const streamUrl = `${window.location.origin}${episode.stream_url}`;
|
||||
showVideo(streamUrl, 'vod'); // Specify VOD content type
|
||||
let streamUrl = `/proxy/vod/episode/${episode.uuid}`;
|
||||
if (env_mode === 'dev') {
|
||||
streamUrl = `${window.location.protocol}//${window.location.hostname}:5656${streamUrl}`;
|
||||
} else {
|
||||
streamUrl = `${window.location.origin}${streamUrl}`;
|
||||
}
|
||||
showVideo(streamUrl, 'vod', episode);
|
||||
};
|
||||
|
||||
if (!series) return null;
|
||||
|
|
@ -267,7 +273,7 @@ const SeriesModal = ({ series, opened, onClose }) => {
|
|||
</Flex>
|
||||
) : (
|
||||
<Grid>
|
||||
{episodes.map(episode => (
|
||||
{seriesEpisodes.map(episode => (
|
||||
<Grid.Col span={6} key={episode.id}>
|
||||
<VODCard vod={episode} onClick={handlePlayEpisode} />
|
||||
</Grid.Col>
|
||||
|
|
@ -284,14 +290,14 @@ const VODModal = ({ vod, opened, onClose }) => {
|
|||
const [loadingDetails, setLoadingDetails] = useState(false);
|
||||
const [trailerModalOpened, setTrailerModalOpened] = useState(false);
|
||||
const [trailerUrl, setTrailerUrl] = useState('');
|
||||
const { fetchVODDetailsFromProvider } = useVODStore();
|
||||
const { fetchMovieDetailsFromProvider } = useVODStore();
|
||||
const showVideo = useVideoStore((s) => s.showVideo);
|
||||
const env_mode = useSettingsStore((s) => s.environment.env_mode);
|
||||
|
||||
useEffect(() => {
|
||||
if (opened && vod && !detailedVOD) {
|
||||
setLoadingDetails(true);
|
||||
fetchVODDetailsFromProvider(vod.id)
|
||||
fetchMovieDetailsFromProvider(vod.id)
|
||||
.then((details) => {
|
||||
setDetailedVOD(details);
|
||||
})
|
||||
|
|
@ -303,7 +309,7 @@ const VODModal = ({ vod, opened, onClose }) => {
|
|||
setLoadingDetails(false);
|
||||
});
|
||||
}
|
||||
}, [opened, vod, detailedVOD, fetchVODDetailsFromProvider]);
|
||||
}, [opened, vod, detailedVOD, fetchMovieDetailsFromProvider]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!opened) {
|
||||
|
|
@ -661,8 +667,9 @@ const useCardColumns = () => {
|
|||
|
||||
const VODsPage = () => {
|
||||
const {
|
||||
vods,
|
||||
movies,
|
||||
series,
|
||||
episodes,
|
||||
categories,
|
||||
loading,
|
||||
filters,
|
||||
|
|
@ -671,7 +678,7 @@ const VODsPage = () => {
|
|||
pageSize,
|
||||
setFilters,
|
||||
setPage,
|
||||
fetchVODs,
|
||||
fetchMovies,
|
||||
fetchSeries,
|
||||
fetchCategories
|
||||
} = useVODStore();
|
||||
|
|
@ -684,6 +691,18 @@ const VODsPage = () => {
|
|||
const [initialLoad, setInitialLoad] = useState(true);
|
||||
const columns = useCardColumns();
|
||||
|
||||
// Helper function to get display data based on current filters
|
||||
const getDisplayData = () => {
|
||||
if (filters.type === 'series') {
|
||||
return Object.values(series);
|
||||
} else if (filters.type === 'movies') {
|
||||
return Object.values(movies);
|
||||
} else {
|
||||
// 'all' - combine movies and series
|
||||
return [...Object.values(movies), ...Object.values(series)];
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories();
|
||||
}, [fetchCategories]);
|
||||
|
|
@ -692,9 +711,9 @@ const VODsPage = () => {
|
|||
if (filters.type === 'series') {
|
||||
fetchSeries().finally(() => setInitialLoad(false));
|
||||
} else {
|
||||
fetchVODs().finally(() => setInitialLoad(false));
|
||||
fetchMovies().finally(() => setInitialLoad(false));
|
||||
}
|
||||
}, [filters, currentPage, fetchVODs, fetchSeries]);
|
||||
}, [filters, currentPage, fetchMovies, fetchSeries]);
|
||||
|
||||
const handleVODCardClick = (vod) => {
|
||||
setSelectedVOD(vod);
|
||||
|
|
@ -777,7 +796,7 @@ const VODsPage = () => {
|
|||
</Grid>
|
||||
) : (
|
||||
<Grid gutter="md">
|
||||
{Object.values(vods).map(vod => (
|
||||
{getDisplayData().map(vod => (
|
||||
<Grid.Col
|
||||
span={12 / columns}
|
||||
key={vod.id}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import { create } from 'zustand';
|
|||
import api from '../api';
|
||||
|
||||
const useVODStore = create((set, get) => ({
|
||||
vods: {},
|
||||
movies: {},
|
||||
series: {},
|
||||
episodes: {},
|
||||
categories: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
|
|
@ -27,7 +28,7 @@ const useVODStore = create((set, get) => ({
|
|||
currentPage: page,
|
||||
})),
|
||||
|
||||
fetchVODs: async () => {
|
||||
fetchMovies: async () => {
|
||||
try {
|
||||
set({ loading: true, error: null });
|
||||
const state = get();
|
||||
|
|
@ -44,27 +45,23 @@ const useVODStore = create((set, get) => ({
|
|||
params.append('category', state.filters.category);
|
||||
}
|
||||
|
||||
if (state.filters.type === 'movies') {
|
||||
params.append('type', 'movie');
|
||||
}
|
||||
|
||||
const response = await api.getVODs(params);
|
||||
const response = await api.getMovies(params);
|
||||
|
||||
// Handle both paginated and non-paginated responses
|
||||
const results = response.results || response;
|
||||
const count = response.count || results.length;
|
||||
|
||||
set({
|
||||
vods: results.reduce((acc, vod) => {
|
||||
acc[vod.id] = vod;
|
||||
movies: results.reduce((acc, movie) => {
|
||||
acc[movie.id] = movie;
|
||||
return acc;
|
||||
}, {}),
|
||||
totalCount: count,
|
||||
loading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch VODs:', error);
|
||||
set({ error: 'Failed to load VODs.', loading: false });
|
||||
console.error('Failed to fetch movies:', error);
|
||||
set({ error: 'Failed to load movies.', loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -85,7 +82,7 @@ const useVODStore = create((set, get) => ({
|
|||
params.append('category', state.filters.category);
|
||||
}
|
||||
|
||||
const response = await api.getVODSeries(params);
|
||||
const response = await api.getSeries(params);
|
||||
|
||||
// Handle both paginated and non-paginated responses
|
||||
const results = response.results || response;
|
||||
|
|
@ -111,8 +108,8 @@ const useVODStore = create((set, get) => ({
|
|||
const response = await api.getSeriesEpisodes(seriesId);
|
||||
|
||||
set((state) => ({
|
||||
vods: {
|
||||
...state.vods,
|
||||
episodes: {
|
||||
...state.episodes,
|
||||
...response.reduce((acc, episode) => {
|
||||
acc[episode.id] = episode;
|
||||
return acc;
|
||||
|
|
@ -120,61 +117,64 @@ const useVODStore = create((set, get) => ({
|
|||
},
|
||||
loading: false,
|
||||
}));
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch series episodes:', error);
|
||||
set({ error: 'Failed to load episodes.', loading: false });
|
||||
throw error; // Re-throw to allow calling component to handle
|
||||
}
|
||||
},
|
||||
|
||||
fetchVODDetails: async (vodId) => {
|
||||
fetchMovieDetails: async (movieId) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const response = await api.getVODInfo(vodId);
|
||||
const response = await api.getMovieDetails(movieId);
|
||||
|
||||
// Transform the response data to match our expected format
|
||||
const vodDetails = {
|
||||
id: response.id || vodId,
|
||||
const movieDetails = {
|
||||
id: response.id || movieId,
|
||||
name: response.name || '',
|
||||
description: response.description || '',
|
||||
year: response.year || null,
|
||||
genre: response.genre || '',
|
||||
rating: response.rating || '',
|
||||
duration: response.duration || null,
|
||||
stream_url: response.stream_url || '',
|
||||
logo: response.logo || null,
|
||||
stream_url: response.url || '',
|
||||
logo: response.logo_url || null,
|
||||
type: 'movie',
|
||||
director: response.director || '',
|
||||
actors: response.actors || '',
|
||||
country: response.country || '',
|
||||
tmdb_id: response.tmdb_id || '',
|
||||
youtube_trailer: response.youtube_trailer || '',
|
||||
imdb_id: response.imdb_id || '',
|
||||
m3u_account: response.m3u_account || '',
|
||||
};
|
||||
console.log('Fetched VOD Details:', vodDetails);
|
||||
console.log('Fetched Movie Details:', movieDetails);
|
||||
set((state) => ({
|
||||
vods: {
|
||||
...state.vods,
|
||||
[vodDetails.id]: vodDetails,
|
||||
movies: {
|
||||
...state.movies,
|
||||
[movieDetails.id]: movieDetails,
|
||||
},
|
||||
loading: false,
|
||||
}));
|
||||
|
||||
return vodDetails;
|
||||
return movieDetails;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch VOD details:', error);
|
||||
set({ error: 'Failed to load VOD details.', loading: false });
|
||||
console.error('Failed to fetch movie details:', error);
|
||||
set({ error: 'Failed to load movie details.', loading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
fetchVODDetailsFromProvider: async (vodId) => {
|
||||
fetchMovieDetailsFromProvider: async (movieId) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const response = await api.getVODInfoFromProvider(vodId);
|
||||
const response = await api.getMovieProviderInfo(movieId);
|
||||
|
||||
// Transform the response data to match our expected format
|
||||
const vodDetails = {
|
||||
id: response.id || vodId,
|
||||
const movieDetails = {
|
||||
id: response.id || movieId,
|
||||
name: response.name || '',
|
||||
description: response.description || response.plot || '',
|
||||
year: response.year || null,
|
||||
|
|
@ -201,13 +201,13 @@ const useVODStore = create((set, get) => ({
|
|||
audio: response.audio || {},
|
||||
};
|
||||
|
||||
set({ loading: false }); // Only update loading state, do not update vods{
|
||||
set({ loading: false }); // Only update loading state
|
||||
|
||||
// Do NOT merge or overwrite the store VOD entry
|
||||
return vodDetails;
|
||||
// Do NOT merge or overwrite the store entry
|
||||
return movieDetails;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch VOD details from provider:', error);
|
||||
set({ error: 'Failed to load VOD details from provider.', loading: false });
|
||||
console.error('Failed to fetch movie details from provider:', error);
|
||||
set({ error: 'Failed to load movie details from provider.', loading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
|
@ -230,21 +230,21 @@ const useVODStore = create((set, get) => ({
|
|||
}
|
||||
},
|
||||
|
||||
addVOD: (vod) =>
|
||||
addMovie: (movie) =>
|
||||
set((state) => ({
|
||||
vods: { ...state.vods, [vod.id]: vod },
|
||||
movies: { ...state.movies, [movie.id]: movie },
|
||||
})),
|
||||
|
||||
updateVOD: (vod) =>
|
||||
updateMovie: (movie) =>
|
||||
set((state) => ({
|
||||
vods: { ...state.vods, [vod.id]: vod },
|
||||
movies: { ...state.movies, [movie.id]: movie },
|
||||
})),
|
||||
|
||||
removeVOD: (vodId) =>
|
||||
removeMovie: (movieId) =>
|
||||
set((state) => {
|
||||
const updatedVODs = { ...state.vods };
|
||||
delete updatedVODs[vodId];
|
||||
return { vods: updatedVODs };
|
||||
const updatedMovies = { ...state.movies };
|
||||
delete updatedMovies[movieId];
|
||||
return { movies: updatedMovies };
|
||||
}),
|
||||
|
||||
addSeries: (series) =>
|
||||
|
|
@ -263,6 +263,8 @@ const useVODStore = create((set, get) => ({
|
|||
delete updatedSeries[seriesId];
|
||||
return { series: updatedSeries };
|
||||
}),
|
||||
|
||||
|
||||
}));
|
||||
|
||||
export default useVODStore;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue