mirror of
https://github.com/agregarr/agregarr.git
synced 2026-05-12 13:48:50 -05:00
fix(seerr): enable Home/Recommended visibility options for Seerr Individual Requests collections
Longstanding Plex bug not respecting label restrictions for Collections on Home/Recommended has been fixed in PMS Beta 1.43.1.10540, confirmed working with Agregarr. Also removes previous easter egg which enabled the option (intended for use when the project was going to be a PR for Overseerr, allowing use of the option without waiting for an update) fix #112
This commit is contained in:
@@ -476,7 +476,6 @@ export interface PlexSettings {
|
||||
collectionConfigs?: CollectionConfig[]; // Agregarr-created collections
|
||||
hubConfigs?: PlexHubConfig[]; // Plex built-in hub configurations
|
||||
preExistingCollectionConfigs?: PreExistingCollectionConfig[]; // Pre-existing Plex collections discovered by hub discovery
|
||||
usersHomeUnlocked?: boolean; // Secret unlock for Users Home collections
|
||||
autoEmptyTrash?: boolean; // Auto-empty Plex trash after placeholder cleanup (default: true)
|
||||
}
|
||||
|
||||
@@ -738,7 +737,6 @@ class Settings {
|
||||
collectionConfigs: [],
|
||||
hubConfigs: [],
|
||||
preExistingCollectionConfigs: [],
|
||||
usersHomeUnlocked: false,
|
||||
},
|
||||
tautulli: {},
|
||||
maintainerr: {},
|
||||
|
||||
@@ -13,8 +13,8 @@ const messages = defineMessages({
|
||||
usersHome: 'Users Home',
|
||||
serverOwnerHome: 'Server Owner Home',
|
||||
libraryRecommended: 'Library Recommended',
|
||||
userRequestCollectionsRestricted:
|
||||
"Individual user request collections are restricted to Library Tab Only visibility due a Plex bug that doesn't respect label restrictions on the Home/Recommended screens. TMDB Franchise Collections and Plex Library Auto Director Collections are hidden so that you don't clog up your home/recommended screens.",
|
||||
libraryOnlyRestricted:
|
||||
'TMDB Franchise Collections and Plex Library Auto Director/Actor Collections are restricted to Library Tab Only visibility to avoid cluttering your home/recommended screens.',
|
||||
serverOwnerOnlyRestricted:
|
||||
"Server owner request collections can only appear on the server owner's home screen.",
|
||||
noVisibilityHubWarning:
|
||||
@@ -82,7 +82,7 @@ const VisibilitySection = ({
|
||||
{/* Show restriction notice for collections restricted to library only */}
|
||||
{restrictToLibraryOnly && (
|
||||
<div className="mb-3 rounded border border-orange-500/20 bg-orange-500/10 p-3 text-sm text-orange-300">
|
||||
{intl.formatMessage(messages.userRequestCollectionsRestricted)}
|
||||
{intl.formatMessage(messages.libraryOnlyRestricted)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3255,8 +3255,6 @@ const CollectionFormConfigForm = ({
|
||||
isEnhancedForm={false}
|
||||
isDefaultPlexHub={isHub}
|
||||
restrictToLibraryOnly={
|
||||
(values.type === 'overseerr' &&
|
||||
values.subtype === 'users') ||
|
||||
(values.type === 'tmdb' &&
|
||||
values.subtype === 'auto_franchise') ||
|
||||
(values.type === 'plex' &&
|
||||
|
||||
@@ -537,24 +537,10 @@ export const SourceSubtypeBadge: React.FC<SourceSubtypeBadgeProps> = ({
|
||||
// Item Count Badge
|
||||
interface ItemCountBadgeProps {
|
||||
maxItems: number;
|
||||
onBadgeClick?: () => void;
|
||||
}
|
||||
|
||||
export const ItemCountBadge: React.FC<ItemCountBadgeProps> = ({
|
||||
maxItems,
|
||||
onBadgeClick,
|
||||
}) => {
|
||||
export const ItemCountBadge: React.FC<ItemCountBadgeProps> = ({ maxItems }) => {
|
||||
const intl = useIntl();
|
||||
// Easter egg handling for maxItems === 69
|
||||
if (maxItems === 69 && onBadgeClick) {
|
||||
return (
|
||||
<button type="button" onClick={onBadgeClick} className="cursor-pointer">
|
||||
<Badge badgeType="success" className="!bg-opacity-40">
|
||||
{intl.formatMessage(messages.items, { maxItems })}
|
||||
</Badge>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge badgeType="default" className="!bg-opacity-30">
|
||||
|
||||
@@ -65,8 +65,6 @@ const messages = defineMessages({
|
||||
failedLoadPlexLibraries:
|
||||
'Failed to load Plex libraries. Please check your Plex connection.',
|
||||
noCollectionsFound: 'No Collections found. Click Discover to get started!',
|
||||
usersHomeUnlocked: 'Users Home collections unlocked!',
|
||||
failedUnlockUsersHome: 'Failed to unlock Users Home collections',
|
||||
collectionsSyncStarted: 'Collections sync started successfully!',
|
||||
failedStartSync: 'Failed to start collections sync. Please try again.',
|
||||
hubConfigSaved: 'Hub configuration saved successfully!',
|
||||
@@ -272,9 +270,6 @@ const CollectionSettings = ({
|
||||
// State to track when an inactive collection was just added (for pulsating button)
|
||||
const [showInactiveHelp, setShowInactiveHelp] = useState(false);
|
||||
|
||||
// Badge click tracking (for easter eggs)
|
||||
const [badgeClickCount, setBadgeClickCount] = useState(0);
|
||||
|
||||
// Hub discovery state
|
||||
const [discoveringHubs, setDiscoveringHubs] = useState(false);
|
||||
|
||||
@@ -378,55 +373,6 @@ const CollectionSettings = ({
|
||||
const shouldShowPlaceholderAlert =
|
||||
!isFirstTimeUser && libraryIssues.length > 0;
|
||||
|
||||
const checkForUnlockSequence = () => {
|
||||
// Check if there's an Overseerr user collection with 69 items and user has clicked 10 times
|
||||
const overseerrUserCollectionWith69Items = collectionConfigs.find(
|
||||
(config: CollectionFormConfig) =>
|
||||
config.type === 'overseerr' &&
|
||||
config.subtype === 'users' &&
|
||||
config.maxItems === 69
|
||||
);
|
||||
|
||||
if (
|
||||
overseerrUserCollectionWith69Items &&
|
||||
badgeClickCount >= 10 &&
|
||||
!data?.usersHomeUnlocked
|
||||
) {
|
||||
// Unlock Users Home collections - preserve all existing settings
|
||||
if (data) {
|
||||
const writableSettings = Object.fromEntries(
|
||||
Object.entries(data).filter(
|
||||
([key]) => !['name', 'machineId', 'libraries'].includes(key)
|
||||
)
|
||||
);
|
||||
fetch('/api/v1/settings/plex', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...writableSettings,
|
||||
usersHomeUnlocked: true, // Only change this field
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
revalidate();
|
||||
addToast(`${intl.formatMessage(messages.usersHomeUnlocked)} 🏠✨`, {
|
||||
autoDismiss: true,
|
||||
appearance: 'success',
|
||||
});
|
||||
setBadgeClickCount(0);
|
||||
})
|
||||
.catch(() => {
|
||||
addToast(intl.formatMessage(messages.failedUnlockUsersHome), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Collection configuration handlers
|
||||
const saveCollectionConfigs = async (
|
||||
configs: CollectionFormConfig[],
|
||||
@@ -2279,9 +2225,6 @@ const CollectionSettings = ({
|
||||
onPromotePreExisting={handlePromotePreExisting}
|
||||
onDemotePreExisting={handleDemotePreExisting}
|
||||
onReorderItems={handleReorderItems}
|
||||
badgeClickCount={badgeClickCount}
|
||||
setBadgeClickCount={setBadgeClickCount}
|
||||
checkForUnlockSequence={checkForUnlockSequence}
|
||||
activeTab={activeTab}
|
||||
onBulkEdit={() => setShowBulkEditModal(true)}
|
||||
/>
|
||||
@@ -2313,9 +2256,6 @@ const CollectionSettings = ({
|
||||
onPromotePreExisting={handlePromotePreExisting}
|
||||
onDemotePreExisting={handleDemotePreExisting}
|
||||
onReorderItems={handleReorderItems}
|
||||
badgeClickCount={badgeClickCount}
|
||||
setBadgeClickCount={setBadgeClickCount}
|
||||
checkForUnlockSequence={checkForUnlockSequence}
|
||||
activeTab={activeTab}
|
||||
onBulkEdit={() => setShowBulkEditModal(true)}
|
||||
/>
|
||||
|
||||
@@ -123,9 +123,6 @@ interface LibraryCollectionGroupProps {
|
||||
) & { configType: FormConfigType; position: number })[],
|
||||
itemTypeName: string
|
||||
) => Promise<void>;
|
||||
badgeClickCount: number;
|
||||
setBadgeClickCount: (value: number | ((prev: number) => number)) => void;
|
||||
checkForUnlockSequence: () => void;
|
||||
activeTab: 'home' | 'recommended' | 'library' | 'inactive' | 'unmanaged';
|
||||
onBulkEdit?: () => void;
|
||||
}
|
||||
@@ -145,8 +142,6 @@ interface SortableItemProps {
|
||||
onDemoteCollection?: (config: CollectionFormConfig) => Promise<void>;
|
||||
onPromotePreExisting?: (config: PreExistingCollectionConfig) => Promise<void>;
|
||||
onDemotePreExisting?: (config: PreExistingCollectionConfig) => Promise<void>;
|
||||
setBadgeClickCount: (value: number | ((prev: number) => number)) => void;
|
||||
checkForUnlockSequence: () => void;
|
||||
activeTab: 'home' | 'recommended' | 'library' | 'inactive' | 'unmanaged';
|
||||
onIndividualSync?: (collectionId: string) => Promise<void>;
|
||||
isSyncing?: boolean;
|
||||
@@ -164,8 +159,6 @@ const SortableItem = ({
|
||||
onDemoteCollection,
|
||||
onPromotePreExisting,
|
||||
onDemotePreExisting,
|
||||
setBadgeClickCount,
|
||||
checkForUnlockSequence,
|
||||
activeTab,
|
||||
onIndividualSync,
|
||||
isSyncing,
|
||||
@@ -327,24 +320,7 @@ const SortableItem = ({
|
||||
const maxItems = collectionConfig.maxItems;
|
||||
if (maxItems === undefined) return null;
|
||||
|
||||
return (
|
||||
<ItemCountBadge
|
||||
maxItems={maxItems}
|
||||
onBadgeClick={
|
||||
maxItems === 69
|
||||
? () => {
|
||||
setBadgeClickCount((prev) => {
|
||||
const newCount = prev + 1;
|
||||
if (newCount >= 10) {
|
||||
checkForUnlockSequence();
|
||||
}
|
||||
return newCount;
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <ItemCountBadge maxItems={maxItems} />;
|
||||
})()}
|
||||
|
||||
{/* Missing Items Badge - Shows when grab missing is enabled for collections */}
|
||||
@@ -553,15 +529,9 @@ const LibraryCollectionGroup = ({
|
||||
onPromotePreExisting,
|
||||
onDemotePreExisting,
|
||||
onReorderItems,
|
||||
badgeClickCount,
|
||||
setBadgeClickCount,
|
||||
checkForUnlockSequence,
|
||||
activeTab,
|
||||
onBulkEdit,
|
||||
}: LibraryCollectionGroupProps) => {
|
||||
// Ensure badgeClickCount is "used" to satisfy linter - this is part of easter egg state management
|
||||
void badgeClickCount;
|
||||
|
||||
const intl = useIntl();
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
const [syncingIds, setSyncingIds] = useState<Set<string>>(new Set());
|
||||
@@ -878,8 +848,6 @@ const LibraryCollectionGroup = ({
|
||||
onDemoteCollection={onDemoteCollection}
|
||||
onPromotePreExisting={onPromotePreExisting}
|
||||
onDemotePreExisting={onDemotePreExisting}
|
||||
setBadgeClickCount={setBadgeClickCount}
|
||||
checkForUnlockSequence={checkForUnlockSequence}
|
||||
activeTab={activeTab}
|
||||
onIndividualSync={handleIndividualSync}
|
||||
isSyncing={syncingIds.has(config.id)}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type {
|
||||
CollectionFormConfig,
|
||||
CollectionFormConfigForEditing,
|
||||
Library,
|
||||
VisibilityCheckboxState,
|
||||
} from '@app/types/collections';
|
||||
import { CollectionFormConfigUtils } from '@app/types/collections';
|
||||
import { useCallback, useState } from 'react';
|
||||
@@ -320,72 +319,6 @@ export const FormStateHelpers = {
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get visibility checkbox states based on collection type
|
||||
*/
|
||||
getVisibilityCheckboxStates: (
|
||||
values: CollectionFormConfig,
|
||||
data: { hasUsersHomeUnlock?: boolean }
|
||||
): Record<string, VisibilityCheckboxState> => {
|
||||
if (!values.type)
|
||||
return {
|
||||
usersHome: { enabled: false, label: 'Users Home' },
|
||||
serverOwnerHome: { enabled: false, label: 'Server Owner Home' },
|
||||
libraryRecommended: { enabled: false, label: 'Library Recommended' },
|
||||
};
|
||||
|
||||
// For User Requests (overseerr + users), check if Users Home is unlocked
|
||||
if (values.type === 'overseerr' && values.subtype === 'users') {
|
||||
const isUsersHomeUnlocked = data?.hasUsersHomeUnlock || false;
|
||||
return {
|
||||
usersHome: { enabled: isUsersHomeUnlocked, label: 'Users Home' },
|
||||
serverOwnerHome: { enabled: false, label: 'Server Owner Home' }, // Users collections shouldn't be on server owner home
|
||||
libraryRecommended: { enabled: true, label: 'Library Recommended' },
|
||||
};
|
||||
}
|
||||
|
||||
// For Server Owner requests (overseerr + server_owner), only "Server Owner Home" should be available
|
||||
if (values.type === 'overseerr' && values.subtype === 'server_owner') {
|
||||
return {
|
||||
usersHome: { enabled: false, label: 'Users Home' }, // Server owner collections shouldn't be on users' home
|
||||
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
|
||||
libraryRecommended: { enabled: true, label: 'Library Recommended' },
|
||||
};
|
||||
}
|
||||
|
||||
// For Hub configs, all options should be available
|
||||
if (values.configType === 'hub') {
|
||||
return {
|
||||
usersHome: { enabled: true, label: 'Users Home' },
|
||||
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
|
||||
libraryRecommended: { enabled: true, label: 'Library Recommended' },
|
||||
};
|
||||
}
|
||||
|
||||
// For Source collections (Tautulli/Trakt/etc), all options should be available
|
||||
if (
|
||||
values.type === 'tautulli' ||
|
||||
values.type === 'trakt' ||
|
||||
values.type === 'tmdb' ||
|
||||
values.type === 'imdb' ||
|
||||
values.type === 'letterboxd' ||
|
||||
values.type === 'multi-source'
|
||||
) {
|
||||
return {
|
||||
usersHome: { enabled: true, label: 'Users Home' },
|
||||
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
|
||||
libraryRecommended: { enabled: true, label: 'Library Recommended' },
|
||||
};
|
||||
}
|
||||
|
||||
// For overseerr global collections, all options should be available
|
||||
return {
|
||||
usersHome: { enabled: true, label: 'Users Home' },
|
||||
serverOwnerHome: { enabled: true, label: 'Server Owner Home' },
|
||||
libraryRecommended: { enabled: true, label: 'Library Recommended' },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default useFormStateManager;
|
||||
|
||||
Reference in New Issue
Block a user