From aa6cbf3b6579daaa43dcc19f6718d4a47d9fee35 Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Fri, 21 Mar 2025 08:22:17 -0500 Subject: [PATCH 1/5] Removed HDHR from root url --- apps/hdhr/api_views.py | 4 ++-- apps/hdhr/views.py | 4 ++-- apps/m3u/tasks.py | 2 +- dispatcharr/urls.py | 8 -------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/hdhr/api_views.py b/apps/hdhr/api_views.py index 409ecbfd..4dd9c07d 100644 --- a/apps/hdhr/api_views.py +++ b/apps/hdhr/api_views.py @@ -39,7 +39,7 @@ class DiscoverAPIView(APIView): responses={200: openapi.Response("HDHR Discovery JSON")} ) def get(self, request): - base_url = request.build_absolute_uri('/').rstrip('/') + base_url = request.build_absolute_uri('/hdhr/').rstrip('/') device = HDHRDevice.objects.first() if not device: @@ -115,7 +115,7 @@ class HDHRDeviceXMLAPIView(APIView): responses={200: openapi.Response("HDHR Device XML")} ) def get(self, request): - base_url = request.build_absolute_uri('/').rstrip('/') + base_url = request.build_absolute_uri('/hdhr/').rstrip('/') xml_response = f""" diff --git a/apps/hdhr/views.py b/apps/hdhr/views.py index 409ecbfd..4dd9c07d 100644 --- a/apps/hdhr/views.py +++ b/apps/hdhr/views.py @@ -39,7 +39,7 @@ class DiscoverAPIView(APIView): responses={200: openapi.Response("HDHR Discovery JSON")} ) def get(self, request): - base_url = request.build_absolute_uri('/').rstrip('/') + base_url = request.build_absolute_uri('/hdhr/').rstrip('/') device = HDHRDevice.objects.first() if not device: @@ -115,7 +115,7 @@ class HDHRDeviceXMLAPIView(APIView): responses={200: openapi.Response("HDHR Device XML")} ) def get(self, request): - base_url = request.build_absolute_uri('/').rstrip('/') + base_url = request.build_absolute_uri('/hdhr/').rstrip('/') xml_response = f""" diff --git a/apps/m3u/tasks.py b/apps/m3u/tasks.py index b3de8567..068626b2 100644 --- a/apps/m3u/tasks.py +++ b/apps/m3u/tasks.py @@ -117,7 +117,7 @@ def refresh_single_m3u_account(account_id): return err_msg headers = {"User-Agent": account.user_agent.user_agent} - response = requests.get(account.server_url, timeout=60, headers=headers) + response = requests.get(account.server_url, headers=headers) response.raise_for_status() lines = response.text.splitlines() elif account.uploaded_file: diff --git a/dispatcharr/urls.py b/dispatcharr/urls.py index 72361783..f0de138e 100644 --- a/dispatcharr/urls.py +++ b/dispatcharr/urls.py @@ -7,7 +7,6 @@ from rest_framework import permissions from drf_yasg.views import get_schema_view from drf_yasg import openapi from .routing import websocket_urlpatterns -from apps.hdhr.api_views import HDHRDeviceViewSet, DiscoverAPIView, LineupAPIView, LineupStatusAPIView, HDHRDeviceXMLAPIView, hdhr_dashboard_view # Define schema_view for Swagger @@ -45,13 +44,6 @@ urlpatterns = [ path('proxy/', include(('apps.proxy.urls', 'proxy'), namespace='proxy')), path('proxy', RedirectView.as_view(url='/proxy/', permanent=True)), - # HDHR API - path('discover.json', DiscoverAPIView.as_view(), name='discover'), - path('lineup.json', LineupAPIView.as_view(), name='lineup'), - path('lineup_status.json', LineupStatusAPIView.as_view(), name='lineup_status'), - path('device.xml', HDHRDeviceXMLAPIView.as_view(), name='device_xml'), - - # Swagger UI path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), From fe8d6fd0822b3b279b83f74276848a65391e3283 Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Fri, 21 Mar 2025 12:23:23 -0500 Subject: [PATCH 2/5] Updated EPGData Added EPG Data endpoint --- apps/epg/api_urls.py | 3 ++- apps/epg/api_views.py | 17 +++++++++++++++-- apps/epg/serializers.py | 16 +++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/epg/api_urls.py b/apps/epg/api_urls.py index d1ad7dcf..2818e66b 100644 --- a/apps/epg/api_urls.py +++ b/apps/epg/api_urls.py @@ -1,12 +1,13 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .api_views import EPGSourceViewSet, ProgramViewSet, EPGGridAPIView, EPGImportAPIView +from .api_views import EPGSourceViewSet, ProgramViewSet, EPGGridAPIView, EPGImportAPIView, EPGDataViewSet app_name = 'epg' router = DefaultRouter() router.register(r'sources', EPGSourceViewSet, basename='epg-source') router.register(r'programs', ProgramViewSet, basename='program') +router.register(r'epgdata', EPGDataViewSet, basename='epgdata') urlpatterns = [ path('grid/', EPGGridAPIView.as_view(), name='epg_grid'), diff --git a/apps/epg/api_views.py b/apps/epg/api_views.py index f59f63e2..571a7165 100644 --- a/apps/epg/api_views.py +++ b/apps/epg/api_views.py @@ -7,8 +7,8 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from django.utils import timezone from datetime import timedelta -from .models import EPGSource, ProgramData # Using ProgramData -from .serializers import ProgramDataSerializer, EPGSourceSerializer # Updated serializer +from .models import EPGSource, ProgramData, EPGData # Added ProgramData +from .serializers import ProgramDataSerializer, EPGSourceSerializer, EPGDataSerializer # Updated serializer from .tasks import refresh_epg_data logger = logging.getLogger(__name__) @@ -78,3 +78,16 @@ class EPGImportAPIView(APIView): refresh_epg_data.delay() # Trigger Celery task logger.info("EPGImportAPIView: Task dispatched to refresh EPG data.") return Response({'success': True, 'message': 'EPG data import initiated.'}, status=status.HTTP_202_ACCEPTED) + + +# ───────────────────────────── +# 5) EPG Data View +# ───────────────────────────── +class EPGDataViewSet(viewsets.ReadOnlyModelViewSet): + """ + API endpoint that allows EPGData objects to be viewed. + """ + queryset = EPGData.objects.all() + serializer_class = EPGDataSerializer + permission_classes = [IsAuthenticated] + diff --git a/apps/epg/serializers.py b/apps/epg/serializers.py index 9a62e74e..b10e7371 100644 --- a/apps/epg/serializers.py +++ b/apps/epg/serializers.py @@ -13,12 +13,14 @@ class ProgramDataSerializer(serializers.ModelSerializer): fields = ['id', 'start_time', 'end_time', 'title', 'sub_title', 'description', 'tvg_id'] class EPGDataSerializer(serializers.ModelSerializer): - programs = ProgramDataSerializer(many=True, read_only=True) - channel = serializers.SerializerMethodField() - - def get_channel(self, obj): - return {"id": obj.channel.id, "name": obj.channel.name} if obj.channel else None - + """ + Only returns the tvg_id and the 'name' field from EPGData. + We assume 'name' is effectively the channel name. + """ class Meta: model = EPGData - fields = ['id', 'channel', 'name', 'programs'] + fields = [ + 'id', + 'tvg_id', + 'name', + ] \ No newline at end of file From f0c93fa41f25572a3bab1000f67ed6b76a299153 Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Sat, 22 Mar 2025 07:57:08 -0500 Subject: [PATCH 3/5] Added /hdhr to nginx --- docker/nginx.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/nginx.conf b/docker/nginx.conf index 077914f7..0a90633d 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -22,6 +22,11 @@ server { return 301 /login; } + # Route HDHR request to Django + location /hdhr { + proxy_pass http://127.0.0.1:5656; + } + # Serve FFmpeg streams efficiently location /output/stream/ { proxy_pass http://127.0.0.1:5656; From 9733fca24278a3fc4bf1fc6c1ce9e247d3e85c1d Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Sat, 22 Mar 2025 08:32:06 -0500 Subject: [PATCH 4/5] Fixed HDHR copy url --- frontend/src/components/tables/ChannelsTable.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index eef3bfb5..6b6b9f1c 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -175,7 +175,7 @@ const ChannelStreams = ({ channel, isExpanded }) => { const m3uUrl = `${window.location.protocol}//${window.location.host}/output/m3u`; const epgUrl = `${window.location.protocol}//${window.location.host}/output/epg`; -const hdhrUrl = `${window.location.protocol}//${window.location.host}/output/hdhr`; +const hdhrUrl = `${window.location.protocol}//${window.location.host}/hdhr/`; const ChannelsTable = ({}) => { const [channel, setChannel] = useState(null); @@ -441,7 +441,7 @@ const ChannelsTable = ({}) => { }; const copyHDHRUrl = () => { handleCopy( - `${window.location.protocol}//${window.location.host}/output/hdhr`, + `${window.location.protocol}//${window.location.host}/hdhr/`, hdhrUrlRef ); }; From 028026b38a136418d5c3bf1e7073f9442a46ae8e Mon Sep 17 00:00:00 2001 From: Dispatcharr Date: Sat, 22 Mar 2025 08:56:15 -0500 Subject: [PATCH 5/5] Fixed EPG refresh bug --- apps/epg/tasks.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/epg/tasks.py b/apps/epg/tasks.py index dd6bb2a0..d7109a9a 100644 --- a/apps/epg/tasks.py +++ b/apps/epg/tasks.py @@ -12,6 +12,8 @@ from celery import shared_task from django.conf import settings from django.db import transaction from django.utils import timezone +from apps.channels.models import Channel + from .models import EPGSource, EPGData, ProgramData @@ -60,9 +62,11 @@ def fetch_xmltv(source): source.file_path = file_path source.save(update_fields=['file_path']) - # If you store the path on EPGSource, do so here: - # source.file_path = file_path - # source.save(update_fields=['file_path']) + epg_entries = EPGData.objects.exclude(tvg_id__isnull=True).exclude(tvg_id__exact='') + for epg in epg_entries: + if Channel.objects.filter(tvg_id=epg.tvg_id).exists(): + logger.info(f"Refreshing program data for tvg_id: {epg.tvg_id}") + parse_programs_for_tvg_id(file_path, epg.tvg_id) # Now parse blocks only parse_channels_only(file_path)