Skip to content

Commit 0874530

Browse files
feat: add recommendation source items limit and enhance loved items fetching logic
1 parent 08e8b97 commit 0874530

File tree

4 files changed

+58
-12
lines changed

4 files changed

+58
-12
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ TOKEN_SALT=replace-with-a-long-random-string
77
TOKEN_TTL_SECONDS=0
88
ANNOUNCEMENT_HTML=
99
HOST_NAME=<your_addon_url>
10+
RECOMMENDATION_SOURCE_ITEMS_LIMIT=10
11+
# fetches recent watched/loved 10 movies and series to recommend based on those

app/core/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ async def lifespan(app: FastAPI):
5656
logger.info("Background catalog updates stopped")
5757

5858

59+
if settings.APP_ENV != "development":
60+
lifespan = lifespan
61+
else:
62+
lifespan = None
63+
5964
app = FastAPI(
6065
title="Watchly",
6166
description="Stremio catalog addon for movie and series recommendations",

app/core/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class Settings(BaseSettings):
4848
APP_ENV: Literal["development", "production"] = "development"
4949
HOST_NAME: str = "https://1ccea4301587-watchly.baby-beamup.club"
5050

51+
# recommendation Settings
52+
RECOMMENDATION_SOURCE_ITEMS_LIMIT: int = 10
53+
5154
@property
5255
def TMDB_ADDON_URL(self) -> str:
5356
return f"{self.TMDB_ADDON_HOST}/{self.TMDB_ADDON_CONFIG}"

app/services/stremio_service.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,53 @@ async def get_library_items(self) -> dict[str, list[dict]]:
170170
]
171171
logger.info(f"Filtered {len(watched_items)} watched library items")
172172

173-
# Check if user has loved the movie or series in parallel
174-
loved_statuses = await asyncio.gather(
175-
*[self.is_loved(auth_key, item.get("_id"), item.get("type")) for item in watched_items]
176-
)
173+
# Sort watched items by modification time (most recent first)
174+
watched_items.sort(key=lambda x: x.get("_mtime", ""), reverse=True)
175+
176+
# is_loved only until we find 10 movies and 10 series
177+
loved_items = []
178+
movies_found = 0
179+
series_found = 0
180+
target_count = settings.RECOMMENDATION_SOURCE_ITEMS_LIMIT
181+
batch_size = 20
182+
183+
# Process in batches to stop early
184+
for i in range(0, len(watched_items), batch_size):
185+
if movies_found >= target_count and series_found >= target_count:
186+
logger.info("Found enough loved items, stopping check")
187+
break
177188

178-
# Separate loved and watched items
179-
loved_items = [item for item, loved in zip(watched_items, loved_statuses) if loved]
180-
logger.info(f"Found {len(loved_items)} loved library items")
189+
batch = watched_items[i : i + batch_size] # noqa: E203
190+
191+
# Filter batch to only check types we still need
192+
check_candidates = []
193+
for item in batch:
194+
itype = item.get("type")
195+
if itype == "movie" and movies_found < target_count:
196+
check_candidates.append(item)
197+
elif itype == "series" and series_found < target_count:
198+
check_candidates.append(item)
199+
200+
if not check_candidates:
201+
continue
202+
203+
# Check loved status for candidates in parallel
204+
loved_statuses = await asyncio.gather(
205+
*[self.is_loved(auth_key, item.get("_id"), item.get("type")) for item in check_candidates]
206+
)
207+
208+
# Process results
209+
for item, is_loved_status in zip(check_candidates, loved_statuses):
210+
if is_loved_status:
211+
loved_items.append(item)
212+
if item.get("type") == "movie":
213+
movies_found += 1
214+
elif item.get("type") == "series":
215+
series_found += 1
216+
217+
logger.info(
218+
f"Found {len(loved_items)} loved library items (Movies: {movies_found}, Series: {series_found})"
219+
)
181220

182221
# Format watched items
183222
formatted_watched = []
@@ -191,7 +230,7 @@ async def get_library_items(self) -> dict[str, list[dict]]:
191230
}
192231
)
193232

194-
# Format and sort loved items
233+
# Format loved items (they are already somewhat sorted by discovery order, which aligns with mtime)
195234
formatted_loved = []
196235
for item in loved_items:
197236
formatted_loved.append(
@@ -203,9 +242,6 @@ async def get_library_items(self) -> dict[str, list[dict]]:
203242
}
204243
)
205244

206-
# Sort loved items by modification time (most recent first)
207-
formatted_loved.sort(key=lambda x: x.get("_mtime", ""), reverse=True)
208-
209245
return {"watched": formatted_watched, "loved": formatted_loved}
210246
except Exception as e:
211247
logger.error(f"Error fetching library items: {e}", exc_info=True)
@@ -233,7 +269,7 @@ async def get_addons(self, auth_key: str | None = None) -> list[dict]:
233269
message = error_payload.get("message") or message
234270
elif isinstance(error_payload, str):
235271
message = error_payload or message
236-
logger.warning("Addon collection request failed: {}", error_payload)
272+
logger.warning(f"Addon collection request failed: {error_payload}")
237273
raise ValueError(f"Stremio: {message}")
238274
addons = data.get("result", {}).get("addons", [])
239275
logger.info(f"Found {len(addons)} addons")

0 commit comments

Comments
 (0)