import json
import os
from dataclasses import asdict
from datetime import datetime, timedelta
from lib.api.stremio.models import extract_proxy_headers
from urllib.parse import quote
from xbmcplugin import addDirectoryItem, setContent
from lib.clients.stremio.helpers import (
get_addon_by_base_url,
get_addon_display_name,
get_catalog_display_name,
get_selected_catalogs_addons,
get_selected_tv_addons,
)
from lib.clients.tmdb.utils.utils import (
add_tmdb_episode_context_menu,
add_tmdb_movie_context_menu,
add_tmdb_show_context_menu,
tmdb_get,
)
from lib.db.cached import cache
from lib.db.pickle_db import PickleDatabase
from lib.utils.general.utils import (
IndexerType,
add_next_button,
info_hash_to_magnet,
truncate_text,
)
from lib.utils.kodi.settings import get_cache_expiration
from lib.utils.kodi.utils import (
ADDON_HANDLE,
ADDON_PATH,
add_directory_items_batch,
build_url,
container_update,
end_of_directory,
kodi_play_media,
kodilog,
make_list_item,
notification,
show_keyboard,
translation,
)
from lib.utils.stremio.catalogs_utils import catalogs_get_cache
CATALOG_PAGE_SIZE = 25
def _stremio_search_history_key(params):
return "stremio_search_catalog|{}|{}|{}".format(
params.get("addon_url", ""),
params.get("catalog_type", ""),
params.get("catalog_id", ""),
)
def _show_search_catalog_history(params):
history_key = _stremio_search_history_key(params)
list_item = make_list_item(label=translation(90006))
list_item.setArt({"icon": os.path.join(ADDON_PATH, "resources", "img", "search.png")})
addDirectoryItem(
ADDON_HANDLE,
build_url(
"search_catalog",
page=1,
addon_url=params.get("addon_url", ""),
catalog_type=params.get("catalog_type", ""),
catalog_id=params.get("catalog_id", ""),
),
list_item,
isFolder=True,
)
for (text,) in cache.get_list(key=history_key):
list_item = make_list_item(label=f"[I]{text}[/I]")
list_item.setArt({"icon": os.path.join(ADDON_PATH, "resources", "img", "search.png")})
addDirectoryItem(
ADDON_HANDLE,
build_url(
"search_catalog",
page=1,
addon_url=params.get("addon_url", ""),
catalog_type=params.get("catalog_type", ""),
catalog_id=params.get("catalog_id", ""),
query=text,
is_keyboard=False,
),
list_item,
isFolder=True,
)
list_item = make_list_item(label=translation(90210))
list_item.setArt({"icon": os.path.join(ADDON_PATH, "resources", "img", "clear.png")})
addDirectoryItem(
ADDON_HANDLE,
build_url(
"clear_stremio_search_history",
addon_url=params.get("addon_url", ""),
catalog_type=params.get("catalog_type", ""),
catalog_id=params.get("catalog_id", ""),
),
list_item,
isFolder=True,
)
end_of_directory()
def clear_stremio_search_history(params):
cache.clear_list(_stremio_search_history_key(params))
_show_search_catalog_history(params)
def _build_stremio_ids(meta_id, tmdb_id="", imdb_id=""):
return {
"tmdb_id": tmdb_id or "",
"tvdb_id": "",
"imdb_id": imdb_id or "",
"original_id": meta_id if ":" in str(meta_id or "") else "",
}
def _build_stremio_meta_payload(
*,
name,
meta_type,
meta_id,
tmdb_id="",
imdb_id="",
overview="",
poster="",
fanart="",
genres=None,
addon_url="",
catalog_type="",
meta_id_for_nav="",
tv_data=None,
):
return {
"source": "stremio_catalog",
"title": name,
"overview": overview or "",
"poster": poster or "",
"fanart": fanart or poster or "",
"genres": genres or [],
"ids": _build_stremio_ids(meta_id, tmdb_id, imdb_id),
"mode": "tv" if meta_type in ["series", "anime"] else "movies",
"addon_url": addon_url,
"catalog_type": catalog_type,
"meta_id": meta_id_for_nav or meta_id,
"tv_data": tv_data or {},
"timestamp": datetime.now().strftime("%a, %d %b %Y %I:%M %p"),
}
def _has_reliable_tmdb_ids(ids):
return bool((ids or {}).get("tmdb_id"))
def _resolve_tmdb_ids_for_context_menu(ids, meta_type):
if _has_reliable_tmdb_ids(ids):
return ids
imdb_id = (ids or {}).get("imdb_id")
if not imdb_id:
return ids
result = cache.get(f"find_by_imdb_id|{imdb_id}")
if not result:
return ids
resolved_tmdb_id = ""
if meta_type == "movie":
movie_results = getattr(result, "movie_results", []) or []
if movie_results:
resolved_tmdb_id = str(movie_results[0].get("id", ""))
elif meta_type in ["series", "anime"]:
tv_results = getattr(result, "tv_results", []) or []
if tv_results:
resolved_tmdb_id = str(tv_results[0].get("id", ""))
if not resolved_tmdb_id:
return ids
resolved_ids = dict(ids or {})
resolved_ids["tmdb_id"] = resolved_tmdb_id
return resolved_ids
def _filter_tmdb_context_menu(context_menu):
excluded_labels = {translation(90205), translation(90116)}
return [item for item in context_menu if item[0] not in excluded_labels]
def _build_stremio_library_menu_item(payload):
return (
translation(90205),
kodi_play_media(name="add_to_library", data=json.dumps(payload)),
)
def _build_stremio_settings_menu_item():
return (
translation(90116),
container_update(name="settings"),
)
def _append_context_menu_items(list_item, context_menu):
if not context_menu:
return
list_item.addContextMenuItems(context_menu, False)
def _param_truthy(value):
return str(value).lower() in ("1", "true", "yes")
def list_stremio_catalogs(menu_type="", sub_menu_type=""):
if menu_type == "tv":
selected_addons = get_selected_tv_addons()
else:
selected_addons = get_selected_catalogs_addons()
if not selected_addons:
kodilog(f"No addons found for menu_type={menu_type}")
return
directory_items = []
for addon in selected_addons:
addon_name = get_addon_display_name(addon)
addon_types = addon.manifest.types
if menu_type not in addon_types:
continue
for catalog in addon.manifest.catalogs:
catalog_name = get_catalog_display_name(addon, catalog)
catalog_id = catalog.id
catalog_type = catalog.type
target_type = sub_menu_type if sub_menu_type else menu_type
# Allow 'anime' catalogs when target is 'series' and we are in 'anime' menu
allowed_types = [target_type]
if menu_type == "anime" and target_type == "series":
allowed_types.append("anime")
if target_type in ["movie", "series", "tv"] and catalog_type not in allowed_types:
continue
search_capabilities = any(extra.get("name") == "search" for extra in catalog.extra)
# A catalog whose search extra is REQUIRED has nothing to list without
# a query, so the plain browse row below can only ever come back empty.
# Upstream adds it unconditionally, which is where the dead second
# "Titles" row came from.
search_required = any(
extra.get("name") == "search" and extra.get("isRequired")
for extra in catalog.extra
)
if search_capabilities:
listitem = make_list_item(label=f"{translation(90006)} {catalog_name}")
listitem.setArt({"icon": addon.manifest.logo})
directory_items.append(
(
build_url(
"search_catalog",
page=1,
addon_url=addon.url(),
catalog_type=catalog.type,
catalog_id=catalog_id,
menu_type=menu_type,
sub_menu_type=sub_menu_type,
has_meta_resource=_addon_has_resource(addon, "meta", catalog_type),
has_stream_resource=_addon_has_resource(addon, "stream", catalog_type),
),
listitem,
True,
)
)
if not search_required and (catalog_name or catalog_id):
if addon.manifest.name == "Cinemeta":
label = f"{addon_name} - {catalog_name or catalog_id}"
else:
label = catalog_name or catalog_id
listitem = make_list_item(label=label)
listitem.setArt({"icon": addon.manifest.logo})
# A catalog advertising genre options opens a picker first;
# entering it directly would filter by nothing.
directory_items.append(
(
build_url(
action=(
"list_catalog_genres"
if getattr(catalog, "genres", None)
else "list_catalog"
),
addon_url=addon.url(),
menu_type=menu_type,
sub_menu_type=sub_menu_type,
catalog_type=catalog.type,
catalog_id=catalog.id,
),
listitem,
True,
)
)
add_directory_items_batch(directory_items)
def _get_manifest_catalog(addon_url, catalog_type, catalog_id):
addon = get_addon_by_base_url(addon_url)
if not addon:
return None
for catalog in addon.manifest.catalogs:
if catalog.type == catalog_type and catalog.id == catalog_id:
return catalog
return None
def _catalog_supports_extra(addon_url, catalog_type, catalog_id, extra_name):
catalog = _get_manifest_catalog(addon_url, catalog_type, catalog_id)
if not catalog:
return False
return any(extra.get("name") == extra_name for extra in (catalog.extra or []))
def list_catalog_genres(params):
"""Render a catalog's declared genre options as a picker.
Stremio addons advertise genres as `extra: [{name: "genre", options: [...]}]`,
and addon_manager parses them onto Catalog.genres -- but nothing ever offered
them to the user, so a genre-filtered catalog was unreachable: list_catalog
forwards a `genre` param that no menu ever set. Each entry here re-enters
list_catalog with the genre chosen.
"""
catalog = _get_manifest_catalog(
params["addon_url"], params["catalog_type"], params["catalog_id"]
)
genres = getattr(catalog, "genres", None) or []
if not genres:
notification(translation(90516))
end_of_directory()
return
directory_items = []
for genre in genres:
listitem = make_list_item(label=genre)
directory_items.append(
(
build_url(
"list_catalog",
addon_url=params["addon_url"],
menu_type=params["menu_type"],
sub_menu_type=params.get("sub_menu_type", ""),
catalog_type=params["catalog_type"],
catalog_id=params["catalog_id"],
genre=genre,
),
listitem,
True,
)
)
add_directory_items_batch(directory_items)
end_of_directory()
def list_catalog(params):
content_type = "movies" if params["menu_type"] == "movie" else "tvshows"
setContent(ADDON_HANDLE, content_type)
skip = int(params.get("skip", 0))
extras = {}
# Common Stremio extra params
for key in ["genre", "search"]:
if key in params:
extras[key] = params[key]
supports_skip = _catalog_supports_extra(
params["addon_url"], params["catalog_type"], params["catalog_id"], "skip"
)
request_kwargs = dict(extras)
if supports_skip and skip:
request_kwargs["skip"] = skip
response = catalogs_get_cache("list_catalog", params, **request_kwargs)
if not response:
end_of_directory()
return
metas = response.get("metas", [])
total_metas = len(metas)
if not supports_skip:
metas = metas[skip : skip + CATALOG_PAGE_SIZE]
if not metas:
notification(translation(90516))
end_of_directory()
return
add_meta_items(metas, params)
has_next_page = False
if supports_skip:
has_next_page = len(metas) >= CATALOG_PAGE_SIZE
else:
has_next_page = total_metas > skip + len(metas)
if has_next_page:
next_url = build_url(
"list_catalog",
addon_url=params["addon_url"],
menu_type=params["menu_type"],
sub_menu_type=params.get("sub_menu_type", ""),
catalog_type=params["catalog_type"],
catalog_id=params["catalog_id"],
skip=skip + len(metas),
)
list_item = make_list_item(label=translation(90515))
addDirectoryItem(handle=ADDON_HANDLE, url=next_url, listitem=list_item, isFolder=True)
end_of_directory()
def search_catalog(params):
page = int(params["page"])
pickle_db = PickleDatabase()
history_key = _stremio_search_history_key(params)
if "menu_type" not in params:
params = dict(params)
params["menu_type"] = params.get("catalog_type", "movie")
if "sub_menu_type" not in params:
params["sub_menu_type"] = ""
if page == 1:
if str(params.get("is_keyboard", "True")).lower() in ("false", "0", "no"):
query = params.get("query", "")
else:
query = show_keyboard(id=30241, default=params.get("query", ""))
if not query:
_show_search_catalog_history(params)
return
cache.add_to_list(
key=history_key,
item=(query,),
expires=timedelta(hours=get_cache_expiration()),
)
pickle_db.set_key("search_catalog_query", query)
else:
query = pickle_db.get_key("search_catalog_query")
response = catalogs_get_cache("search_catalog", params, query)
if not response:
return
add_meta_items(response.get("metas", []), params)
add_next_button("search_catalog", page=page, mode=params["catalog_type"])
end_of_directory()
def _addon_has_resource(addon, resource_name, content_type):
if not addon:
return False
for resource in addon.manifest.resources:
if resource.name == resource_name and content_type in resource.types:
return True
return False
def addon_has_meta(addon_url, content_type, addon=None):
"""Check if the addon serves its own meta (seasons/episodes) for the given type."""
addon = addon or get_addon_by_base_url(addon_url)
return _addon_has_resource(addon, "meta", content_type)
def addon_has_stream(addon_url, content_type, addon=None):
"""Check if the addon serves its own streams for the given type."""
addon = addon or get_addon_by_base_url(addon_url)
return _addon_has_resource(addon, "stream", content_type)
def add_meta_items(metas, params):
catalog_type = params["catalog_type"]
menu_type = params.get("menu_type", catalog_type)
sub_menu_type = params.get("sub_menu_type", "")
addon_url = params["addon_url"]
content_type = "movies" if menu_type == "movie" else "tvshows"
setContent(ADDON_HANDLE, content_type)
def should_include(meta):
meta_type = meta.type
if menu_type in ["anime", "movie"] and sub_menu_type == "movie":
return meta_type == "movie"
if menu_type in ["anime", "series"] and sub_menu_type == "series":
return meta_type in ["series", "anime"]
if menu_type == "tv":
return meta_type == "tv"
return True
metas = [m for m in metas if should_include(m)]
if not metas:
notification(f"{translation(90511)} {menu_type}")
end_of_directory()
return
addon = None
has_meta_resource = _param_truthy(params.get("has_meta_resource"))
has_stream_resource = _param_truthy(params.get("has_stream_resource"))
if not has_meta_resource and not has_stream_resource:
addon = get_addon_by_base_url(addon_url)
has_meta_resource = addon_has_meta(addon_url, catalog_type, addon=addon)
has_stream_resource = addon_has_stream(addon_url, catalog_type, addon=addon)
for meta in metas:
name = meta.name
meta_type = meta.type
meta_id = meta.id
tmdb_id = meta.moviedb_id
imdb_id = meta.imdb_id
if "tmdb" in meta_id:
tmdb_id = meta_id.split(":")[1]
elif meta_id.startswith("tt"):
imdb_id = meta_id
tmdb_id = ""
ids = _build_stremio_ids(meta_id, tmdb_id, imdb_id)
poster = meta.poster or ""
background = meta.background or ""
library_payload = _build_stremio_meta_payload(
name=name,
meta_type=meta_type,
meta_id=meta_id,
tmdb_id=tmdb_id,
imdb_id=imdb_id,
overview=meta.description or "",
poster=poster,
fanart=background,
genres=meta.genres,
addon_url=addon_url,
catalog_type=catalog_type,
meta_id_for_nav=meta_id,
)
if meta_type == "series":
if has_meta_resource:
url = build_url(
"list_stremio_seasons",
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
)
elif tmdb_id or imdb_id:
url = build_url("show_seasons_details", ids=ids, mode="tv", media_type="tv")
else:
url = build_url(
"list_stremio_seasons",
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
)
elif meta_type == "movie":
if has_stream_resource:
is_custom_movie = True
url = build_url(
"list_stremio_movie",
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
ids=json.dumps(ids),
title=name,
overview=meta.description or "",
poster=poster,
fanart=background,
genres=json.dumps(meta.genres or []),
)
elif tmdb_id or imdb_id or ":" in meta_id:
is_custom_movie = False
url = build_url("search", mode="movies", query=name, ids=ids)
else:
is_custom_movie = True
url = build_url(
"list_stremio_movie",
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
ids=json.dumps(ids),
title=name,
overview=meta.description or "",
poster=poster,
fanart=background,
genres=json.dumps(meta.genres or []),
)
elif meta_type == "tv":
if meta.streams:
streams_data = [asdict(s) for s in meta.streams]
url = build_url(
"list_stremio_tv_streams",
streams=json.dumps(streams_data),
ids=json.dumps(ids),
title=name,
overview=meta.description or "",
poster=poster,
fanart=background,
genres=json.dumps(meta.genres or []),
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
)
else:
url = build_url(
"list_stremio_tv",
addon_url=addon_url,
catalog_type=catalog_type,
meta_id=meta_id,
ids=json.dumps(ids),
title=name,
overview=meta.description or "",
poster=poster,
fanart=background,
genres=json.dumps(meta.genres or []),
)
else:
continue
list_item = make_list_item(label=name)
info_tag = list_item.getVideoInfoTag()
info_tag.setUniqueID(meta_id, type="imdb" if meta_id.startswith("tt") else "mf")
info_tag.setTitle(name)
info_tag.setPlot(truncate_text(meta.description or ""))
info_tag.setGenres(meta.genres)
info_tag.setMediaType("video")
is_folder = meta_type != "movie" or (meta_type == "movie" and is_custom_movie)
if not is_folder:
list_item.setProperty("IsPlayable", "true")
list_item.setArt(
{
"thumb": poster,
"poster": poster,
"fanart": poster,
"icon": poster,
"banner": background,
"landscape": background,
}
)
context_menu_ids = _resolve_tmdb_ids_for_context_menu(ids, meta_type)
context_menu = [
_build_stremio_library_menu_item(library_payload),
_build_stremio_settings_menu_item(),
]
if _has_reliable_tmdb_ids(context_menu_ids):
if meta_type == "movie":
context_menu.extend(
_filter_tmdb_context_menu(
add_tmdb_movie_context_menu(
mode="movies",
media_type="movie",
title=name,
ids=context_menu_ids,
)
)
)
elif meta_type in ["series", "anime"]:
context_menu.extend(
_filter_tmdb_context_menu(
add_tmdb_show_context_menu(mode="tv", ids=context_menu_ids, title=name)
)
)
_append_context_menu_items(list_item, context_menu)
addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=list_item, isFolder=is_folder)
def list_stremio_seasons(params):
kodilog("list_stremio_seasons")
response = catalogs_get_cache("list_stremio_seasons", params)
if not response:
return
meta_data = response.get("meta")
if not meta_data:
notification(translation(90517))
return
videos = meta_data.videos
if not videos:
notification(translation(90517))
return
available_seasons = set(
int(video.imdbSeason) if video.imdbSeason else int(video.season) for video in videos
)
items = []
for season in available_seasons:
url = build_url(
"list_stremio_episodes",
addon_url=params["addon_url"],
catalog_type=params["catalog_type"],
meta_id=params["meta_id"],
season=season,
)
list_item = make_list_item(label=f"{translation(90512)} {season}")
info_tag = list_item.getVideoInfoTag()
info_tag.setUniqueID(meta_data.id, type="imdb" if meta_data.id.startswith("tt") else "mf")
info_tag.setTitle(meta_data.name)
info_tag.setPlot(truncate_text(meta_data.description or ""))
info_tag.setRating(float(meta_data.imdbRating or 0))
info_tag.setGenres(meta_data.genres)
info_tag.setTvShowTitle(meta_data.name)
info_tag.setSeason(season)
list_item.setArt(
{
"thumb": meta_data.poster or "",
"poster": meta_data.poster or "",
"fanart": meta_data.poster or "",
"icon": meta_data.poster or "",
"banner": meta_data.background or "",
"landscape": meta_data.background or "",
}
)
items.append((url, list_item, True))
add_directory_items_batch(items)
end_of_directory()
def list_stremio_episodes(params):
kodilog("list_stremio_episodes")
response = catalogs_get_cache("list_stremio_episodes", params)
if not response:
return
meta_data = response.get("meta")
if not meta_data:
notification(translation(90510))
return
videos = meta_data.videos
if not videos:
notification(translation(90513))
return
# Resolve IDs once for the show
tmdb_id = ""
imdb_id = ""
# Store original ID (e.g. kitsu:123) for addons that support it
original_id = meta_data.id if ":" in meta_data.id else ""
if meta_data.imdb_id:
imdb_id = meta_data.imdb_id
res = tmdb_get("find_by_imdb_id", imdb_id)
if getattr(res, "tv_results", []):
tmdb_id = str(res.tv_results[0]["id"])
if not tmdb_id and not imdb_id and meta_data.name:
kodilog(f"No IDs found for {meta_data.name}. Searching TMDB...")
year = meta_data.releaseInfo.split("-")[0] if meta_data.releaseInfo else ""
results = tmdb_get("search_tv", {"query": meta_data.name, "page": 1, "year": year})
if results and results.results:
try:
tmdb_id = str(results.results[0].id)
kodilog(f"Fallback found TMDB ID: {tmdb_id} for {meta_data.name}")
except Exception:
pass
if tmdb_id and not imdb_id:
details = tmdb_get("tv_details", tmdb_id)
if details and getattr(details, "external_ids", None):
imdb_id = details.external_ids.get("imdb_id", "")
kodilog(f"Resolved IMDB ID: {imdb_id} from TMDB ID: {tmdb_id}")
addon = get_addon_by_base_url(params["addon_url"])
has_stream_resource = addon_has_stream(params["addon_url"], params["catalog_type"], addon=addon)
items = []
for video in videos:
try:
season = int(video.imdbSeason) if video.imdbSeason else int(video.season)
episode = int(video.imdbEpisode) if video.imdbEpisode else int(video.episode)
except (ValueError, TypeError):
continue
if season != int(params["season"]):
continue
title = video.title or getattr(video, "name", "") or meta_data.name
tv_data = {
"name": quote(title),
"episode": episode,
"season": season,
}
ids = {
"tmdb_id": tmdb_id,
"tvdb_id": "",
"imdb_id": imdb_id,
"original_id": original_id,
}
scoped_addon_url = params["addon_url"] if has_stream_resource else ""
url = build_url(
"search",
mode="tv",
media_type="tv",
query=meta_data.name,
ids=ids,
tv_data=tv_data,
scoped_addon_url=scoped_addon_url,
stremio_addon_url=params["addon_url"],
stremio_catalog_type=params["catalog_type"],
stremio_meta_id=params["meta_id"],
)
list_item = make_list_item(label=f"{season}x{episode}. {title}")
info_tag = list_item.getVideoInfoTag()
info_tag.setUniqueID(meta_data.id, type="imdb" if meta_data.id.startswith("tt") else "mf")
info_tag.setTitle(title)
info_tag.setPlot(truncate_text(video.overview or ""))
info_tag.setRating(float(meta_data.imdbRating or 0))
info_tag.setGenres(meta_data.genres)
info_tag.setTvShowTitle(title)
info_tag.setSeason(season)
info_tag.setEpisode(episode)
list_item.setProperty("IsPlayable", "true")
context_menu = [
_build_stremio_library_menu_item(
_build_stremio_meta_payload(
name=meta_data.name,
meta_type="series",
meta_id=meta_data.id,
tmdb_id=tmdb_id,
imdb_id=imdb_id,
overview=meta_data.description or video.overview or "",
poster=meta_data.poster or video.thumbnail or "",
fanart=meta_data.background or meta_data.poster or video.thumbnail or "",
genres=meta_data.genres,
addon_url=params["addon_url"],
catalog_type=params["catalog_type"],
meta_id_for_nav=params["meta_id"],
tv_data=tv_data,
)
),
_build_stremio_settings_menu_item(),
]
context_menu_ids = _resolve_tmdb_ids_for_context_menu(ids, "series")
if _has_reliable_tmdb_ids(context_menu_ids):
context_menu = (
_filter_tmdb_context_menu(
add_tmdb_episode_context_menu(
mode="tv",
tv_name=meta_data.name,
tv_data=tv_data,
ids=context_menu_ids,
)
)
+ context_menu
)
else:
context_menu.insert(
0,
(
translation(90049),
kodi_play_media(
name="search",
mode="tv",
media_type="tv",
query=meta_data.name,
ids=ids,
tv_data=tv_data,
scoped_addon_url=scoped_addon_url,
stremio_addon_url=params["addon_url"],
stremio_catalog_type=params["catalog_type"],
stremio_meta_id=params["meta_id"],
rescrape=True,
),
),
)
_append_context_menu_items(list_item, context_menu)
list_item.setArt(
{
"thumb": meta_data.poster or "",
"poster": meta_data.poster or "",
"fanart": meta_data.poster or "",
"icon": meta_data.poster or "",
"banner": meta_data.background or "",
"landscape": meta_data.background or "",
}
)
items.append((url, list_item, False))
add_directory_items_batch(items)
end_of_directory()
def list_stremio_movie(params):
response = catalogs_get_cache("list_stremio_movie", params)
if not response:
return
streams = response.get("streams", [])
if not streams:
notification(translation(90514))
return
items = []
for stream in streams:
playback_data = {
"mode": "movie",
"source": "stremio_catalog",
"title": stream.title,
"overview": stream.description or params.get("overview", ""),
"poster": params.get("poster", ""),
"fanart": params.get("fanart", params.get("poster", "")),
"genres": json.loads(params.get("genres", "[]") or "[]"),
"ids": json.loads(params.get("ids", "{}") or "{}"),
"addon_url": params.get("addon_url", ""),
"catalog_type": params.get("catalog_type", ""),
"meta_id": params.get("meta_id", ""),
}
if stream.url:
if stream.url.startswith("magnet:"):
playback_data["magnet"] = stream.url
playback_data["is_torrent"] = True
else:
playback_data["url"] = stream.url
playback_data["proxyHeaders"] = stream.get_proxy_headers()
playback_data["type"] = IndexerType.DIRECT
elif stream.infoHash:
playback_data["magnet"] = info_hash_to_magnet(stream.infoHash)
playback_data["info_hash"] = stream.infoHash
playback_data["is_torrent"] = True
else:
continue
url = build_url(
"play_media",
data=json.dumps(playback_data),
)
list_item = make_list_item(label=stream.title)
list_item.setProperty("IsPlayable", "true")
info_tag = list_item.getVideoInfoTag()
info_tag.setPlot(truncate_text(stream.description or ""))
items.append((url, list_item, False))
add_directory_items_batch(items)
end_of_directory()
def list_stremio_tv(params):
response = catalogs_get_cache("list_stremio_tv", params)
if not response:
return
streams = response.get("streams", [])
if not streams:
notification(translation(90514))
return
items = []
for stream in streams:
playback_data = {
"mode": "movie",
"url": stream.url,
"proxyHeaders": stream.get_proxy_headers(),
"type": IndexerType.DIRECT,
"source": "stremio_catalog",
"title": stream.title,
"overview": stream.description or params.get("overview", ""),
"poster": params.get("poster", ""),
"fanart": params.get("fanart", params.get("poster", "")),
"genres": json.loads(params.get("genres", "[]") or "[]"),
"ids": json.loads(params.get("ids", "{}") or "{}"),
"addon_url": params.get("addon_url", ""),
"catalog_type": params.get("catalog_type", ""),
"meta_id": params.get("meta_id", ""),
}
url = build_url(
"play_media",
data=json.dumps(playback_data),
)
list_item = make_list_item(label=stream.title)
list_item.setProperty("IsPlayable", "true")
info_tag = list_item.getVideoInfoTag()
info_tag.setPlot(truncate_text(stream.description or ""))
items.append((url, list_item, False))
add_directory_items_batch(items)
end_of_directory()
def list_stremio_tv_streams(params):
streams = json.loads(params.get("streams", {}))
items = []
for stream in streams:
playback_data = {
"mode": "movie",
"url": stream["url"],
"proxyHeaders": extract_proxy_headers(
(stream.get("behaviorHints") or {}).get("proxyHeaders")
),
"type": IndexerType.DIRECT,
"source": "stremio_catalog",
"title": stream["name"],
"overview": stream.get("description", "") or params.get("overview", ""),
"poster": params.get("poster", ""),
"fanart": params.get("fanart", params.get("poster", "")),
"genres": json.loads(params.get("genres", "[]") or "[]"),
"ids": json.loads(params.get("ids", "{}") or "{}"),
"addon_url": params.get("addon_url", ""),
"catalog_type": params.get("catalog_type", ""),
"meta_id": params.get("meta_id", ""),
}
url = build_url(
"play_media",
data=json.dumps(playback_data),
)
list_item = make_list_item(label=stream["name"])
list_item.setProperty("IsPlayable", "true")
info_tag = list_item.getVideoInfoTag()
info_tag.setPlot(truncate_text(stream.get("description", "")))
items.append((url, list_item, False))
add_directory_items_batch(items)
end_of_directory()