Better naming for different functions.

This commit is contained in:
SergeantPanda 2025-08-05 12:53:22 -05:00
parent 151f654dd9
commit 8584aae675
5 changed files with 179 additions and 81 deletions

View file

@ -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")

View file

@ -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'),

View file

@ -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 {

View file

@ -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}

View file

@ -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;