Skip to content

Commit 77c4fa2

Browse files
feat: add option to rename, enable disable catalogs (#24)
1 parent 4b3a26b commit 77c4fa2

File tree

8 files changed

+77
-53
lines changed

8 files changed

+77
-53
lines changed

app/api/endpoints/catalogs.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ async def get_catalog(
4040
# Supported IDs now include dynamic themes and item-based rows
4141
if (
4242
id != "watchly.rec"
43-
and not id.startswith("tt")
44-
and not id.startswith("watchly.theme.")
45-
and not id.startswith("watchly.item.")
43+
and not any(id.startswith(p) for p in ("tt", "watchly.theme.", "watchly.item.", "watchly.loved.", "watchly.watched."))
4644
):
4745
logger.warning(f"Invalid id: {id}")
4846
raise HTTPException(
@@ -72,9 +70,9 @@ async def get_catalog(
7270
recommendations = await recommendation_service.get_recommendations_for_item(item_id=id)
7371
logger.info(f"Found {len(recommendations)} recommendations for {id}")
7472

75-
elif id.startswith("watchly.item."):
73+
elif id.startswith("watchly.item.") or id.startswith("watchly.loved.") or id.startswith("watchly.watched."):
7674
# Extract actual item ID (tt... or tmdb:...)
77-
item_id = id.replace("watchly.item.", "")
75+
item_id = re.sub(r"^watchly\.(item|loved|watched)\.", "", id)
7876
recommendations = await recommendation_service.get_recommendations_for_item(item_id=item_id)
7977
logger.info(f"Found {len(recommendations)} recommendations for item {item_id}")
8078

@@ -107,8 +105,7 @@ async def get_catalog(
107105

108106

109107
@router.get("/{token}/catalog/update")
110-
@router.get("/{settings_str}/{token}/catalog/update")
111-
async def update_catalogs(token: str, settings_str: str | None = None):
108+
async def update_catalogs(token: str):
112109
"""
113110
Update the catalogs for the addon. This is a manual endpoint to update the catalogs.
114111
"""

app/api/endpoints/manifest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ def get_config_id(catalog):
130130
catalog_id = catalog.get("id", "")
131131
if catalog_id.startswith("watchly.theme."):
132132
return "watchly.theme"
133+
if catalog_id.startswith("watchly.loved."):
134+
return "watchly.loved"
135+
if catalog_id.startswith("watchly.watched."):
136+
return "watchly.watched"
133137
if catalog_id.startswith("watchly.item."):
134138
return "watchly.item"
135139
if catalog_id.startswith("watchly.rec"):

app/core/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def get_default_settings() -> UserSettings:
5050
language="en-US",
5151
catalogs=[
5252
CatalogConfig(id="watchly.rec", name="Recommended", enabled=True),
53-
CatalogConfig(id="watchly.item", name="Because you Loved/Watched", enabled=True),
53+
CatalogConfig(id="watchly.loved", name="More like what you loved", enabled=True),
54+
CatalogConfig(id="watchly.watched", name="Because you watched", enabled=True),
5455
CatalogConfig(id="watchly.theme", name="Because of Genre/Theme", enabled=True),
5556
],
5657
)

app/core/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.0-rc.4"
1+
__version__ = "1.0.0-rc.5"

app/models/profile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ def get_top_crew(self, limit: int = 2) -> list[tuple[int, float]]:
8888

8989
def get_top_countries(self, limit: int = 2) -> list[tuple[str, float]]:
9090
return self.countries.get_top_features(limit)
91+
92+
def get_top_year(self, limit: int = 1) -> list[tuple[int, float]]:
93+
return self.years.get_top_features(limit)

app/services/catalog.py

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from app.core.settings import UserSettings
1+
from app.core.settings import CatalogConfig, UserSettings
22
from app.services.row_generator import RowGeneratorService
33
from app.services.scoring import ScoringService
44
from app.services.stremio_service import StremioService
@@ -26,7 +26,7 @@ def normalize_type(type_):
2626
def build_catalog_entry(self, item, label, config_id):
2727
item_id = item.get("_id", "")
2828
# Use watchly.{config_id}.{item_id} format for better organization
29-
if config_id == "watchly.item":
29+
if config_id in ["watchly.item", "watchly.loved", "watchly.watched"]:
3030
# New Item-based catalog format
3131
catalog_id = f"{config_id}.{item_id}"
3232
elif item_id.startswith("tt") and config_id in ["watchly.loved", "watchly.watched"]:
@@ -103,35 +103,38 @@ async def get_dynamic_catalogs(
103103
"""
104104
Generate all dynamic catalog rows.
105105
"""
106-
lang = user_settings.language if user_settings else "en-US"
107-
108-
include_item_based_rows = bool(
109-
next((c for c in user_settings.catalogs if c.id == "watchly.item" and c.enabled), True)
110-
)
111-
include_theme_based_rows = bool(
112-
next((c for c in user_settings.catalogs if c.id == "watchly.theme" and c.enabled), True)
113-
)
114106
catalogs = []
107+
lang = user_settings.language if user_settings else "en-US"
115108

116-
if include_theme_based_rows:
109+
# Theme Based
110+
theme_config = next((c for c in user_settings.catalogs if c.id == "watchly.theme"), None)
111+
if not theme_config or theme_config.enabled:
117112
catalogs.extend(await self.get_theme_based_catalogs(library_items, user_settings))
118113

119-
# 3. Add Item-Based Rows
120-
if include_item_based_rows:
121-
# For Movies
122-
await self._add_item_based_rows(catalogs, library_items, "movie", lang)
123-
# For Series
124-
await self._add_item_based_rows(catalogs, library_items, "series", lang)
114+
# Item Based (Loved/Watched)
115+
loved_config = next((c for c in user_settings.catalogs if c.id == "watchly.loved"), None)
116+
watched_config = next((c for c in user_settings.catalogs if c.id == "watchly.watched"), None)
117+
118+
# Fallback for old settings (watchly.item)
119+
if not loved_config and not watched_config:
120+
old_config = next((c for c in user_settings.catalogs if c.id == "watchly.item"), None)
121+
if old_config and old_config.enabled:
122+
# Create temporary configs
123+
loved_config = CatalogConfig(id="watchly.loved", name=None, enabled=True)
124+
watched_config = CatalogConfig(id="watchly.watched", name=None, enabled=True)
125+
126+
# Movies
127+
await self._add_item_based_rows(catalogs, library_items, "movie", lang, loved_config, watched_config)
128+
# Series
129+
await self._add_item_based_rows(catalogs, library_items, "series", lang, loved_config, watched_config)
125130

126131
return catalogs
127132

128-
async def _add_item_based_rows(self, catalogs: list, library_items: dict, content_type: str, language: str):
133+
async def _add_item_based_rows(
134+
self, catalogs: list, library_items: dict, content_type: str, language: str, loved_config, watched_config
135+
):
129136
"""Helper to add 'Because you watched' and 'More like' rows."""
130137

131-
# Translate labels
132-
label_more_like = await translation_service.translate("More like", language)
133-
label_bc_watched = await translation_service.translate("Because you watched", language)
134-
135138
# Helper to parse date
136139
def get_date(item):
137140
import datetime
@@ -154,24 +157,35 @@ def get_date(item):
154157
return datetime.datetime.min.replace(tzinfo=datetime.UTC)
155158

156159
# 1. More Like <Loved Item>
157-
loved = [i for i in library_items.get("loved", []) if i.get("type") == content_type]
158-
loved.sort(key=get_date, reverse=True)
160+
last_loved = None # Initialize for the watched check
161+
if loved_config and loved_config.enabled:
162+
loved = [i for i in library_items.get("loved", []) if i.get("type") == content_type]
163+
loved.sort(key=get_date, reverse=True)
164+
165+
last_loved = loved[0] if loved else None
166+
if last_loved:
167+
label = loved_config.name
168+
if not label or label == "More like what you loved": # Default
169+
label = await translation_service.translate("More like what you loved", language)
159170

160-
last_loved = loved[0] if loved else None
161-
if last_loved:
162-
catalogs.append(self.build_catalog_entry(last_loved, label_more_like, "watchly.item"))
171+
catalogs.append(self.build_catalog_entry(last_loved, label, "watchly.loved"))
163172

164173
# 2. Because you watched <Watched Item>
165-
watched = [i for i in library_items.get("watched", []) if i.get("type") == content_type]
166-
watched.sort(key=get_date, reverse=True)
167-
168-
last_watched = None
169-
for item in watched:
170-
# Avoid duplicate row if it's the same item as 'More like'
171-
if last_loved and item.get("_id") == last_loved.get("_id"):
172-
continue
173-
last_watched = item
174-
break
175-
176-
if last_watched:
177-
catalogs.append(self.build_catalog_entry(last_watched, label_bc_watched, "watchly.item"))
174+
if watched_config and watched_config.enabled:
175+
watched = [i for i in library_items.get("watched", []) if i.get("type") == content_type]
176+
watched.sort(key=get_date, reverse=True)
177+
178+
last_watched = None
179+
for item in watched:
180+
# Avoid duplicate row if it's the same item as 'More like'
181+
if last_loved and item.get("_id") == last_loved.get("_id"):
182+
continue
183+
last_watched = item
184+
break
185+
186+
if last_watched:
187+
label = watched_config.name
188+
if not label or label == "Because you watched":
189+
label = await translation_service.translate("Because you watched", language)
190+
191+
catalogs.append(self.build_catalog_entry(last_watched, label, "watchly.watched"))

app/services/discovery.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,12 @@ async def discover_recommendations(
152152
# query 6: Top year
153153
if top_year:
154154
year = top_year[0][0]
155+
# we store year in 10 years bucket
156+
start_year = f"{year}-01-01"
157+
end_year = f"{int(year) + 10}-12-31"
155158
params_rating = {
156-
"year": year,
159+
"primary_release_date.gte": start_year,
160+
"primary_release_date.lte": end_year,
157161
"sort_by": "ratings.desc",
158162
"vote_count.gte": 500,
159163
**base_params,

app/static/script.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Default catalog configurations
22
const defaultCatalogs = [
33
{ id: 'watchly.rec', name: 'Top Picks for You', enabled: true, description: 'Personalized recommendations based on your library' },
4-
{ id: 'watchly.item', name: 'Because you Loved/Watched', enabled: true, description: 'Recommendations based on content you interacted with' },
5-
{ id: 'watchly.theme', name: 'Keyword Genre Based Dynamic Recommendations', enabled: true, description: 'Recommendations based on your favorite genres and themes' },
4+
{ id: 'watchly.loved', name: 'More like what you loved', enabled: true, description: 'Recommendations similar to content you explicitly loved' },
5+
{ id: 'watchly.watched', name: 'Because you watched', enabled: true, description: 'Recommendations based on your recent watch history' },
6+
{ id: 'watchly.theme', name: 'Genre & Theme Collections', enabled: true, description: 'Dynamic collections based on your favorite genres' },
67
];
78

89
let catalogs = JSON.parse(JSON.stringify(defaultCatalogs));
@@ -578,7 +579,7 @@ function createCatalogItem(cat, index) {
578579
item.className = `catalog-item group bg-slate-900 border border-slate-700 rounded-xl p-4 transition-all hover:border-slate-600 ${disabledClass}`;
579580
item.setAttribute('data-index', index);
580581

581-
const isRenamable = cat.id === 'watchly.rec';
582+
const isRenamable = true;
582583
item.innerHTML = `
583584
<div class="flex items-start gap-3 sm:items-center sm:gap-4">
584585
<div class="sort-buttons flex flex-col gap-1 flex-shrink-0 mt-0.5 sm:mt-0">

0 commit comments

Comments
 (0)