Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/sethwv/1308

This commit is contained in:
SergeantPanda 2026-05-31 16:38:41 -05:00
commit 1d07b26a01
13 changed files with 283 additions and 190 deletions

View file

@ -7,6 +7,12 @@ import useSettingsStore from '../../store/settings';
import { copyToClipboard } from '../../utils';
// Mock stores
vi.mock('../../store/auth', () => ({
default: {
getState: () => ({ accessToken: null }),
},
}));
vi.mock('../../store/useVODStore', () => ({
default: vi.fn(),
}));

View file

@ -194,15 +194,25 @@ describe('usePlaylistsStore', () => {
});
});
it('should remove playlists', () => {
it('should remove playlists and their profiles', () => {
const { result } = renderHook(() => usePlaylistsStore());
act(() => {
result.current.playlists = [
{ id: 'playlist1', name: 'Playlist 1' },
{ id: 'playlist2', name: 'Playlist 2' },
{ id: 'playlist3', name: 'Playlist 3' },
];
result.current.addPlaylist({
id: 'playlist1',
name: 'Playlist 1',
profiles: ['profile1'],
});
result.current.addPlaylist({
id: 'playlist2',
name: 'Playlist 2',
profiles: ['profile2'],
});
result.current.addPlaylist({
id: 'playlist3',
name: 'Playlist 3',
profiles: ['profile3'],
});
});
act(() => {
@ -210,8 +220,11 @@ describe('usePlaylistsStore', () => {
});
expect(result.current.playlists).toEqual([
{ id: 'playlist2', name: 'Playlist 2' },
{ id: 'playlist2', name: 'Playlist 2', profiles: ['profile2'] },
]);
expect(result.current.profiles).toEqual({ playlist2: ['profile2'] });
expect(result.current.profiles.playlist1).toBeUndefined();
expect(result.current.profiles.playlist3).toBeUndefined();
});
it('should set refresh progress with two parameters', () => {

View file

@ -86,12 +86,16 @@ const usePlaylistsStore = create((set) => ({
})),
removePlaylists: (playlistIds) =>
set((state) => ({
playlists: state.playlists.filter(
(playlist) => !playlistIds.includes(playlist.id)
),
// @TODO: remove playlist profiles here
})),
set((state) => {
const updatedProfiles = { ...state.profiles };
playlistIds.forEach((id) => delete updatedProfiles[id]);
return {
playlists: state.playlists.filter(
(playlist) => !playlistIds.includes(playlist.id)
),
profiles: updatedProfiles,
};
}),
setRefreshProgress: (accountIdOrData, data) =>
set((state) => {

View file

@ -1,3 +1,5 @@
import useAuthStore from '../../store/auth';
export const imdbUrl = (imdb_id) =>
imdb_id ? `https://www.imdb.com/title/${imdb_id}` : '';
@ -27,7 +29,9 @@ const extractQuality = (relation) => {
// 2. Secondary: Custom properties detailed_info
if (relation.custom_properties?.detailed_info) {
const fromDetailedInfo = getQualityFromDetailedInfo(relation.custom_properties.detailed_info);
const fromDetailedInfo = getQualityFromDetailedInfo(
relation.custom_properties.detailed_info
);
if (fromDetailedInfo) return fromDetailedInfo;
}
@ -55,7 +59,10 @@ const getQualityFromBackend = (qualityInfo) => {
const getQualityFromDetailedInfo = (detailedInfo) => {
// Check video dimensions first
if (detailedInfo.video?.width && detailedInfo.video?.height) {
return getQualityInfoFromDimensions(detailedInfo.video.width, detailedInfo.video.height);
return getQualityInfoFromDimensions(
detailedInfo.video.width,
detailedInfo.video.height
);
}
// Check name field
@ -91,7 +98,7 @@ const getQualityInfoFromDimensions = (width, height) => {
} else {
return ` - ${width}x${height}`;
}
}
};
export const sortEpisodesList = (episodesList) => {
return episodesList.sort((a, b) => {
@ -123,15 +130,18 @@ export const sortBySeasonNumber = (episodesBySeason) => {
export const getEpisodeStreamUrl = (episode, selectedProvider, env_mode) => {
let streamUrl = `/proxy/vod/episode/${episode.uuid}`;
// Add selected provider as query parameter if available
const params = new URLSearchParams();
if (selectedProvider) {
// Use stream_id for most specific selection, fallback to account_id
if (selectedProvider.stream_id) {
streamUrl += `?stream_id=${encodeURIComponent(selectedProvider.stream_id)}`;
params.set('stream_id', selectedProvider.stream_id);
} else {
streamUrl += `?m3u_account_id=${selectedProvider.m3u_account.id}`;
params.set('m3u_account_id', selectedProvider.m3u_account.id);
}
}
const token = useAuthStore.getState().accessToken;
if (token) params.set('token', token);
if (params.toString())
streamUrl += `?${params.toString().replace(/\+/g, '%20')}`;
if (env_mode === 'dev') {
streamUrl = `${window.location.protocol}//${window.location.hostname}:5656${streamUrl}`;

View file

@ -1,3 +1,5 @@
import useAuthStore from '../../store/auth';
const hasValidTechnicalDetails = (obj) => {
return obj?.bitrate || obj?.video || obj?.audio;
};
@ -60,15 +62,18 @@ export const getTechnicalDetails = (selectedProvider, defaultVOD) => {
export const getMovieStreamUrl = (vod, selectedProvider, env_mode) => {
let streamUrl = `/proxy/vod/movie/${vod.uuid}`;
// Add selected provider as query parameter if available
const params = new URLSearchParams();
if (selectedProvider) {
// Use stream_id for most specific selection, fallback to account_id
if (selectedProvider.stream_id) {
streamUrl += `?stream_id=${encodeURIComponent(selectedProvider.stream_id)}`;
params.set('stream_id', selectedProvider.stream_id);
} else {
streamUrl += `?m3u_account_id=${selectedProvider.m3u_account.id}`;
params.set('m3u_account_id', selectedProvider.m3u_account.id);
}
}
const token = useAuthStore.getState().accessToken;
if (token) params.set('token', token);
if (params.toString())
streamUrl += `?${params.toString().replace(/\+/g, '%20')}`;
if (env_mode === 'dev') {
streamUrl = `${window.location.protocol}//${window.location.hostname}:5656${streamUrl}`;