88from app .core .security import redact_token
99from app .core .settings import UserSettings , get_default_settings
1010from app .services .catalog_updater import catalog_updater
11- from app .services .recommendation .engine import RecommendationEngine
11+ from app .services .profile .integration import ProfileIntegration
12+ from app .services .recommendation .creators import CreatorsService
13+ from app .services .recommendation .item_based import ItemBasedService
14+ from app .services .recommendation .theme_based import ThemeBasedService
15+ from app .services .recommendation .top_picks import TopPicksService
16+ from app .services .recommendation .utils import pad_to_min
1217from app .services .stremio .service import StremioBundle
18+ from app .services .tmdb .service import get_tmdb_service
1319from app .services .token_store import token_store
1420
1521MAX_RESULTS = 50
1622DEFAULT_MIN_ITEMS = 20
1723DEFAULT_MAX_ITEMS = 32
18- SOURCE_ITEMS_LIMIT = 10
1924
2025router = APIRouter ()
2126
@@ -60,7 +65,7 @@ async def get_catalog(type: str, id: str, response: Response, token: str):
6065 raise HTTPException (status_code = 400 , detail = "Invalid type. Use 'movie' or 'series'" )
6166
6267 # Supported IDs now include dynamic themes and item-based rows
63- if id != "watchly.rec" and not any (
68+ if id not in [ "watchly.rec" , "watchly.creators" ] and not any (
6469 id .startswith (p )
6570 for p in (
6671 "tt" ,
@@ -74,8 +79,8 @@ async def get_catalog(type: str, id: str, response: Response, token: str):
7479 raise HTTPException (
7580 status_code = 400 ,
7681 detail = ( #
77- "Invalid id. Supported: 'watchly.rec', 'watchly.theme.<params> ', 'watchly.item.<id >', or "
78- " specific item IDs."
82+ "Invalid id. Supported: 'watchly.rec', 'watchly.creators ', 'watchly.theme.<params >',"
83+ "'watchly.item.<id>', or specific item IDs."
7984 ),
8085 )
8186
@@ -122,13 +127,14 @@ async def get_catalog(type: str, id: str, response: Response, token: str):
122127
123128 # 3. Fetch library once per request and reuse across recommendation paths
124129 library_items = await bundle .library .get_library_items (auth_key )
125- engine = RecommendationEngine (
126- stremio_service = bundle ,
127- language = language ,
128- user_settings = user_settings ,
129- token = token ,
130- library_data = library_items ,
131- )
130+
131+ # Initialize services
132+ tmdb_service = get_tmdb_service (language = language )
133+ integration = ProfileIntegration (language = language )
134+ item_service = ItemBasedService (tmdb_service , user_settings )
135+ theme_service = ThemeBasedService (tmdb_service , user_settings )
136+ top_picks_service = TopPicksService (tmdb_service , user_settings )
137+ creators_service = CreatorsService (tmdb_service , user_settings )
132138
133139 # Resolve per-catalog limits (min/max)
134140 def _get_limits () -> tuple [int , int ]:
@@ -157,44 +163,128 @@ def _get_limits() -> tuple[int, int]:
157163 min_items , max_items = DEFAULT_MIN_ITEMS , DEFAULT_MAX_ITEMS
158164
159165 # Handle item-based recommendations
160- if id .startswith ("tt" ):
161- engine .per_item_limit = max_items
162- recommendations = await engine .get_recommendations_for_item (item_id = id , media_type = type )
163- if len (recommendations ) < min_items :
164- recommendations = await engine .pad_to_min (type , recommendations , min_items )
165- logger .info (f"Found { len (recommendations )} recommendations for { id } " )
166-
167- elif any (
166+ if id .startswith ("tt" ) or any (
168167 id .startswith (p )
169168 for p in (
170169 "watchly.item." ,
171170 "watchly.loved." ,
172171 "watchly.watched." ,
173172 )
174173 ):
175- # Extract actual item ID (tt... or tmdb:...)
176- item_id = re .sub (r"^watchly\.(item|loved|watched)\." , "" , id )
177- engine .per_item_limit = max_items
178- recommendations = await engine .get_recommendations_for_item (item_id = item_id , media_type = type )
174+ # Extract actual item ID
175+ if id .startswith ("tt" ):
176+ item_id = id
177+ else :
178+ item_id = re .sub (r"^watchly\.(item|loved|watched)\." , "" , id )
179+
180+ # Get watched sets
181+ _ , watched_tmdb , watched_imdb = await integration .build_profile_from_library (
182+ library_items , type , bundle , auth_key
183+ )
184+
185+ # Get genre whitelist
186+ whitelist = await integration .get_genre_whitelist (library_items , type , bundle , auth_key )
187+
188+ # Use new item-based service
189+ recommendations = await item_service .get_recommendations_for_item (
190+ item_id = item_id ,
191+ content_type = type ,
192+ watched_tmdb = watched_tmdb ,
193+ watched_imdb = watched_imdb ,
194+ limit = max_items ,
195+ integration = integration ,
196+ library_items = library_items ,
197+ )
198+
179199 if len (recommendations ) < min_items :
180- recommendations = await engine .pad_to_min (type , recommendations , min_items )
200+ recommendations = await pad_to_min (
201+ type , recommendations , min_items , tmdb_service , user_settings , bundle , library_items , auth_key
202+ )
181203 logger .info (f"Found { len (recommendations )} recommendations for item { item_id } " )
182204
183205 elif id .startswith ("watchly.theme." ):
184- recommendations = await engine .get_recommendations_for_theme (
185- theme_id = id , content_type = type , limit = max_items
206+ # Build profile for theme-based recommendations
207+ profile , watched_tmdb , watched_imdb = await integration .build_profile_from_library (
208+ library_items , type , bundle , auth_key
209+ )
210+
211+ # Use new theme-based service
212+ recommendations = await theme_service .get_recommendations_for_theme (
213+ theme_id = id ,
214+ content_type = type ,
215+ profile = profile ,
216+ watched_tmdb = watched_tmdb ,
217+ watched_imdb = watched_imdb ,
218+ limit = max_items ,
219+ integration = integration ,
220+ library_items = library_items ,
186221 )
222+
187223 if len (recommendations ) < min_items :
188- recommendations = await engine .pad_to_min (type , recommendations , min_items )
224+ recommendations = await pad_to_min (
225+ type , recommendations , min_items , tmdb_service , user_settings , bundle , library_items , auth_key
226+ )
189227 logger .info (f"Found { len (recommendations )} recommendations for theme { id } " )
190228
191- else :
192- recommendations = await engine .get_recommendations (
193- content_type = type , source_items_limit = SOURCE_ITEMS_LIMIT , max_results = max_items
229+ elif id == "watchly.creators" :
230+ # Build profile for creators-based recommendations
231+ profile , watched_tmdb , watched_imdb = await integration .build_profile_from_library (
232+ library_items , type , bundle , auth_key
233+ )
234+
235+ # Get genre whitelist
236+ whitelist = await integration .get_genre_whitelist (library_items , type , bundle , auth_key )
237+
238+ if profile :
239+ # Use new creators service
240+ recommendations = await creators_service .get_recommendations_from_creators (
241+ profile = profile ,
242+ content_type = type ,
243+ library_items = library_items ,
244+ watched_tmdb = watched_tmdb ,
245+ watched_imdb = watched_imdb ,
246+ whitelist = whitelist ,
247+ limit = max_items ,
248+ )
249+ else :
250+ # No profile available, return empty
251+ recommendations = []
252+
253+ if len (recommendations ) < min_items :
254+ recommendations = await pad_to_min (
255+ type , recommendations , min_items , tmdb_service , user_settings , bundle , library_items , auth_key
256+ )
257+ logger .info (f"Found { len (recommendations )} recommendations from creators" )
258+
259+ elif id == "watchly.rec" :
260+ # Top picks - use new TopPicksService
261+ profile , watched_tmdb , watched_imdb = await integration .build_profile_from_library (
262+ library_items , type , bundle , auth_key
194263 )
264+
265+ if profile :
266+ recommendations = await top_picks_service .get_top_picks (
267+ profile = profile ,
268+ content_type = type ,
269+ library_items = library_items ,
270+ watched_tmdb = watched_tmdb ,
271+ watched_imdb = watched_imdb ,
272+ limit = max_items ,
273+ )
274+ else :
275+ # No profile available, return empty
276+ recommendations = []
277+
195278 if len (recommendations ) < min_items :
196- recommendations = await engine .pad_to_min (type , recommendations , min_items )
197- logger .info (f"Found { len (recommendations )} recommendations for { type } " )
279+ recommendations = await pad_to_min (
280+ type , recommendations , min_items , tmdb_service , user_settings , bundle , library_items , auth_key
281+ )
282+ logger .info (f"Found { len (recommendations )} top picks for { type } " )
283+
284+ else :
285+ # Unknown catalog ID, return empty
286+ logger .warning (f"Unknown catalog ID: { id } " )
287+ recommendations = []
198288
199289 logger .info (f"Returning { len (recommendations )} items for { type } " )
200290 response .headers ["Cache-Control" ] = "public, max-age=21600" # 6 hours
0 commit comments