mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
refactor(http headers): centralize User-Agent construction and streamline API requests
- Introduced `dispatcharr_user_agent`, `dispatcharr_dvr_user_agent`, and `dispatcharr_http_headers` functions in `core.utils` to standardize User-Agent strings and HTTP headers for outbound requests. - Updated various components, including `LogoViewSet`, `EPGSourceViewSet`, and VOD proxy views, to utilize the new header functions, ensuring consistent User-Agent usage across the application. - Enhanced the handling of Schedules Direct API requests by including proper User-Agent headers, addressing previous API compliance issues.
This commit is contained in:
parent
5f9fa4c11f
commit
843940f552
9 changed files with 81 additions and 37 deletions
|
|
@ -2800,8 +2800,9 @@ class LogoViewSet(viewsets.ModelViewSet):
|
|||
user_agent_obj = UserAgent.objects.get(id=int(default_user_agent_id))
|
||||
user_agent = user_agent_obj.user_agent
|
||||
except (CoreSettings.DoesNotExist, UserAgent.DoesNotExist, ValueError):
|
||||
# Fallback to hardcoded if default not found
|
||||
user_agent = 'Dispatcharr/1.0'
|
||||
# Fallback if default not found
|
||||
from core.utils import dispatcharr_user_agent
|
||||
user_agent = dispatcharr_user_agent()
|
||||
|
||||
# Hard total timeout (connect + full download) prevents a slow
|
||||
# server dripping bytes from holding a greenlet indefinitely.
|
||||
|
|
|
|||
|
|
@ -1181,12 +1181,13 @@ def _dvr_ffmpeg_retry_backoff_seconds(retry_index):
|
|||
|
||||
def _dvr_build_ffmpeg_cmd(stream_url, recording_id, hls_m3u8, hls_seg_pattern, hls_start_number):
|
||||
"""Build the FFmpeg command for DVR HLS segment recording."""
|
||||
from core.utils import dispatcharr_dvr_user_agent
|
||||
return [
|
||||
"ffmpeg", "-y",
|
||||
"-reconnect", "1",
|
||||
"-reconnect_streamed", "1",
|
||||
"-reconnect_delay_max", "5",
|
||||
"-user_agent", f"Dispatcharr-DVR/recording-{recording_id}",
|
||||
"-user_agent", dispatcharr_dvr_user_agent(recording_id),
|
||||
# Regenerate monotonic PTS to handle erratic/discontinuous timestamps
|
||||
# from IPTV sources.
|
||||
"-fflags", "+genpts",
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
import hashlib
|
||||
import requests as http_requests
|
||||
from apps.epg.tasks import SD_BASE_URL
|
||||
from version import __version__ as dispatcharr_version
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
username = (source.username or '').strip()
|
||||
password = (source.password or '').strip()
|
||||
|
|
@ -143,7 +143,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
auth_response = http_requests.post(
|
||||
f"{SD_BASE_URL}/token",
|
||||
json={'username': username, 'password': sha1_password},
|
||||
headers={'Content-Type': 'application/json', 'User-Agent': f'Dispatcharr/{dispatcharr_version}'},
|
||||
headers=dispatcharr_http_headers(),
|
||||
timeout=15,
|
||||
)
|
||||
auth_response.raise_for_status()
|
||||
|
|
@ -231,12 +231,12 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
"""Fetch the SD country list (token not required; User-Agent is)."""
|
||||
import requests as http_requests
|
||||
from apps.epg.tasks import SD_BASE_URL
|
||||
from version import __version__ as dispatcharr_version
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
try:
|
||||
resp = http_requests.get(
|
||||
f"{SD_BASE_URL}/available/countries",
|
||||
headers={'User-Agent': f'Dispatcharr/{dispatcharr_version}'},
|
||||
headers=dispatcharr_http_headers(content_type=None),
|
||||
timeout=15,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
|
@ -254,7 +254,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
"""
|
||||
import requests as http_requests
|
||||
from apps.epg.tasks import SD_BASE_URL
|
||||
from version import __version__ as dispatcharr_version
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
source = self.get_object()
|
||||
if source.source_type != 'schedules_direct':
|
||||
|
|
@ -267,11 +267,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
if error:
|
||||
return error
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': f'Dispatcharr/{dispatcharr_version}',
|
||||
'token': token,
|
||||
}
|
||||
headers = dispatcharr_http_headers(token=token)
|
||||
|
||||
if request.method == "GET":
|
||||
countries = self._fetch_sd_countries()
|
||||
|
|
@ -420,7 +416,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
"""
|
||||
import requests as http_requests
|
||||
from apps.epg.tasks import SD_BASE_URL
|
||||
from version import __version__ as dispatcharr_version
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
source = self.get_object()
|
||||
if source.source_type != 'schedules_direct':
|
||||
|
|
@ -441,11 +437,7 @@ class EPGSourceViewSet(viewsets.ModelViewSet):
|
|||
if error:
|
||||
return error
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': f'Dispatcharr/{dispatcharr_version}',
|
||||
'token': token,
|
||||
}
|
||||
headers = dispatcharr_http_headers(token=token)
|
||||
|
||||
try:
|
||||
resp = http_requests.get(
|
||||
|
|
@ -514,6 +506,7 @@ class ProgramViewSet(viewsets.ModelViewSet):
|
|||
"""Proxy endpoint for SD program poster images. Nginx caches the response."""
|
||||
import requests as http_requests
|
||||
from apps.epg.tasks import SD_BASE_URL
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
program = self.get_object()
|
||||
poster_sd_url = (program.custom_properties or {}).get('sd_icon')
|
||||
|
|
@ -537,14 +530,10 @@ class ProgramViewSet(viewsets.ModelViewSet):
|
|||
if not token:
|
||||
sha1_password = hashlib.sha1(source.password.encode('utf-8')).hexdigest()
|
||||
try:
|
||||
from version import __version__ as dispatcharr_version
|
||||
auth_resp = http_requests.post(
|
||||
f"{SD_BASE_URL}/token",
|
||||
json={'username': source.username, 'password': sha1_password},
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': f'Dispatcharr/{dispatcharr_version}',
|
||||
},
|
||||
headers=dispatcharr_http_headers(),
|
||||
timeout=10,
|
||||
)
|
||||
auth_data = auth_resp.json()
|
||||
|
|
@ -570,7 +559,7 @@ class ProgramViewSet(viewsets.ModelViewSet):
|
|||
try:
|
||||
img_resp = http_requests.get(
|
||||
poster_sd_url,
|
||||
headers={'token': token},
|
||||
headers=dispatcharr_http_headers(token=token, content_type=None),
|
||||
timeout=15,
|
||||
allow_redirects=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2173,17 +2173,10 @@ def fetch_schedules_direct(source, stations_only=False, force=False):
|
|||
# SD API spec requires the User-Agent to identify the application and version.
|
||||
# SergeantPanda confirmed Dispatcharr should identify itself properly.
|
||||
# -------------------------------------------------------------------------
|
||||
from version import __version__ as dispatcharr_version
|
||||
sd_user_agent = f"Dispatcharr/{dispatcharr_version}"
|
||||
from core.utils import dispatcharr_http_headers
|
||||
|
||||
def _sd_headers(token=None):
|
||||
h = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': sd_user_agent,
|
||||
}
|
||||
if token:
|
||||
h['token'] = token
|
||||
return h
|
||||
return dispatcharr_http_headers(token=token)
|
||||
|
||||
def _sd_post_refresh_tasks(mapped_epg_ids, program_metadata, today):
|
||||
"""Poster fetch, logo auto-apply, and pruning — runs even when schedules are unchanged."""
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from rest_framework_simplejwt.authentication import JWTAuthentication
|
|||
from apps.accounts.authentication import ApiKeyAuthentication, QueryParamJWTAuthentication
|
||||
from apps.proxy.utils import check_user_stream_limits
|
||||
from dispatcharr.utils import network_access_allowed
|
||||
from core.utils import dispatcharr_user_agent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -704,7 +705,7 @@ def head_vod(request, content_type, content_id, session_id=None, profile_id=None
|
|||
# Use M3U account's user agent as primary, client user agent as fallback
|
||||
m3u_user_agent = m3u_account.get_user_agent().user_agent if m3u_account.get_user_agent() else None
|
||||
headers = {
|
||||
'User-Agent': m3u_user_agent or client_user_agent or 'Dispatcharr/1.0',
|
||||
'User-Agent': m3u_user_agent or client_user_agent or dispatcharr_user_agent(),
|
||||
'Accept': '*/*',
|
||||
'Range': 'bytes=0-1' # Request only first 2 bytes
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue