mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 00:55:50 +00:00
Clients that build catch-up requests in QUERY layout (e.g. Open-TV / Fred TV: /streaming/timeshift.php?username=...&stream=...&start=...) had no matching urlpattern, so the request silently fell through to the frontend's <path:unused_path> catch-all and got served index.html (200 OK, wrong content) instead of reaching the proxy — no error, no log line, the client just fails to play. The PATH-style layout (timeshift/<user>/<pass>/<stream_id>/<ts>/<dur>) already worked; QUERY-style autodetection already existed for outgoing provider requests (helpers.build_timeshift_url_format_a/_b) but was never mirrored to the incoming route. Split timeshift_proxy into a shared _timeshift_proxy_impl plus two thin entry points (PATH-style timeshift_proxy, new QUERY-style timeshift_proxy_query) so both incoming layouts are recognized. Reported against the predecessor plugin as dispatcharr_timeshift#10; reproduced identically against dispatcharr:dev (confirmed via nginx access log: a QUERY-style request returned 200 with a response body exactly matching the size of frontend/dist/index.html).
81 lines
3.4 KiB
Python
81 lines
3.4 KiB
Python
from django.contrib import admin
|
|
from django.urls import path, include, re_path
|
|
from django.conf import settings
|
|
from django.conf.urls.static import static
|
|
from django.views.generic import TemplateView, RedirectView
|
|
from .routing import websocket_urlpatterns
|
|
from apps.output.views import xc_player_api, xc_panel_api, xc_get, xc_xmltv
|
|
from apps.proxy.live_proxy.views import stream_xc
|
|
from apps.proxy.vod_proxy.views import stream_xc_movie, stream_xc_episode
|
|
from apps.timeshift.views import timeshift_proxy, timeshift_proxy_query
|
|
|
|
urlpatterns = [
|
|
# API Routes
|
|
path("api/", include(("apps.api.urls", "api"), namespace="api")),
|
|
path("api", RedirectView.as_view(url="/api/", permanent=True)),
|
|
# Swagger redirects (Swagger UI is served at /api/swagger/)
|
|
path("swagger/", RedirectView.as_view(url="/api/swagger/", permanent=True)),
|
|
path("swagger", RedirectView.as_view(url="/api/swagger/", permanent=True)),
|
|
path("redoc/", RedirectView.as_view(url="/api/redoc/", permanent=True)),
|
|
path("redoc", RedirectView.as_view(url="/api/redoc/", permanent=True)),
|
|
# Outputs
|
|
path("output", RedirectView.as_view(url="/output/", permanent=True)),
|
|
path("output/", include(("apps.output.urls", "output"), namespace="output")),
|
|
# HDHR
|
|
path("hdhr", RedirectView.as_view(url="/hdhr/", permanent=True)),
|
|
path("hdhr/", include(("apps.hdhr.urls", "hdhr"), namespace="hdhr")),
|
|
# Add proxy apps - Move these before the catch-all
|
|
path("proxy/", include(("apps.proxy.urls", "proxy"), namespace="proxy")),
|
|
path("proxy", RedirectView.as_view(url="/proxy/", permanent=True)),
|
|
# xc
|
|
re_path("player_api.php", xc_player_api, name="xc_player_api"),
|
|
re_path("panel_api.php", xc_panel_api, name="xc_panel_api"),
|
|
re_path("get.php", xc_get, name="xc_get"),
|
|
re_path("xmltv.php", xc_xmltv, name="xc_xmltv"),
|
|
path(
|
|
"live/<str:username>/<str:password>/<str:channel_id>",
|
|
stream_xc,
|
|
name="xc_live_stream_endpoint",
|
|
),
|
|
path(
|
|
"<str:username>/<str:password>/<str:channel_id>",
|
|
stream_xc,
|
|
name="xc_stream_endpoint",
|
|
),
|
|
path(
|
|
"timeshift/<str:username>/<str:password>/<str:stream_id>/<str:timestamp>/<str:duration>",
|
|
timeshift_proxy,
|
|
name="timeshift_proxy",
|
|
),
|
|
path(
|
|
"streaming/timeshift.php",
|
|
timeshift_proxy_query,
|
|
name="timeshift_proxy_query",
|
|
),
|
|
# XC VOD endpoints
|
|
path(
|
|
"movie/<str:username>/<str:password>/<str:stream_id>.<str:extension>",
|
|
stream_xc_movie,
|
|
name="stream_xc_movie",
|
|
),
|
|
path(
|
|
"series/<str:username>/<str:password>/<str:stream_id>.<str:extension>",
|
|
stream_xc_episode,
|
|
name="stream_xc_episode",
|
|
),
|
|
# Admin
|
|
path("admin", RedirectView.as_view(url="/admin/", permanent=True)),
|
|
path("admin/", admin.site.urls),
|
|
|
|
# VOD proxy is now handled by the main proxy URLs above
|
|
# Catch-all routes should always be last
|
|
path("", TemplateView.as_view(template_name="index.html")), # React entry point
|
|
path("<path:unused_path>", TemplateView.as_view(template_name="index.html")),
|
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
|
|
urlpatterns += websocket_urlpatterns
|
|
|
|
# Serve static files for development (React's JS, CSS, etc.)
|
|
if settings.DEBUG:
|
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|