(self, request)
| 1029 | })}, |
| 1030 | ) |
| 1031 | def get(self, request): |
| 1032 | repos = PluginRepo.objects.filter(enabled=True) |
| 1033 | configs = list(PluginConfig.objects.select_related("source_repo").all()) |
| 1034 | # Build lookup: slug -> (version, repo_id, repo_name) for managed plugins, |
| 1035 | # plus key -> version for all plugins (legacy matching) |
| 1036 | installed_by_slug = {} |
| 1037 | installed_by_key = {} |
| 1038 | # Secondary dict keyed by dash-to-underscore-normalized key, for backward compat |
| 1039 | # with existing DB entries that were saved before normalization was enforced. |
| 1040 | installed_by_key_norm = {} |
| 1041 | for cfg in configs: |
| 1042 | installed_by_key[cfg.key] = cfg.version |
| 1043 | installed_by_key_norm[cfg.key.replace("-", "_")] = cfg.key |
| 1044 | if cfg.slug: |
| 1045 | installed_by_slug[cfg.slug] = { |
| 1046 | "version": cfg.version, |
| 1047 | "source_repo_id": cfg.source_repo_id, |
| 1048 | "source_repo_name": cfg.source_repo.name if cfg.source_repo else None, |
| 1049 | "is_prerelease": cfg.installed_version_is_prerelease, |
| 1050 | } |
| 1051 | # Also index by normalized slug so a dash-variant in the manifest still matches |
| 1052 | norm_slug = cfg.slug.replace("-", "_") |
| 1053 | if norm_slug not in installed_by_slug: |
| 1054 | installed_by_slug[norm_slug] = installed_by_slug[cfg.slug] |
| 1055 | |
| 1056 | plugins = [] |
| 1057 | for repo in repos: |
| 1058 | manifest_data = repo.cached_manifest or {} |
| 1059 | manifest = manifest_data.get("manifest", manifest_data) |
| 1060 | download_base_url, metadata_base_url = _resolve_manifest_base_urls(manifest) |
| 1061 | registry_url = manifest.get("registry_url", "").rstrip("/") |
| 1062 | repo_plugins = manifest.get("plugins", []) |
| 1063 | for p in repo_plugins: |
| 1064 | slug = p.get("slug", "") |
| 1065 | plugin_data = {**p} |
| 1066 | # Resolve relative URLs; metadata and download assets use separate bases |
| 1067 | if metadata_base_url: |
| 1068 | for url_field in ("manifest_url", "icon_url"): |
| 1069 | val = plugin_data.get(url_field, "") |
| 1070 | if val and not val.startswith(("http://", "https://")): |
| 1071 | plugin_data[url_field] = f"{metadata_base_url}/{val}" |
| 1072 | if download_base_url: |
| 1073 | val = plugin_data.get("latest_url", "") |
| 1074 | if val and not val.startswith(("http://", "https://")): |
| 1075 | plugin_data["latest_url"] = f"{download_base_url}/{val}" |
| 1076 | # Fallback icon_url: if metadata_base_url is explicitly set and manifest_url |
| 1077 | # is known, assume logo.png lives in the same directory as the per-plugin |
| 1078 | # manifest. Guard against root_url-only manifests to preserve the GitHub fallback. |
| 1079 | if not plugin_data.get("icon_url") and manifest.get("metadata_base_url") and plugin_data.get("manifest_url"): |
| 1080 | manifest_dir = plugin_data["manifest_url"].rsplit("/", 1)[0] |
| 1081 | plugin_data["icon_url"] = f"{manifest_dir}/logo.png" |
| 1082 | # Fallback icon_url from main branch when not provided |
| 1083 | if not plugin_data.get("icon_url") and registry_url: |
| 1084 | # registry_url is e.g. https://github.com/Dispatcharr/Plugins |
| 1085 | # Convert to raw.githubusercontent.com URL for the main branch |
| 1086 | raw_base = registry_url.replace( |
| 1087 | "https://github.com/", "https://raw.githubusercontent.com/" |
| 1088 | ) |
nothing calls this directly
no test coverage detected