show lineup name in EPG channel dropdown for Schedules Direct sources

This commit is contained in:
Seth Van Niekerk 2026-06-24 13:26:28 -04:00
parent 578c50ea96
commit 15bb0cc4f6
No known key found for this signature in database
GPG key ID: E86ACA677312A675
5 changed files with 52 additions and 11 deletions

View file

@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0026_epgsourceindex'),
]
operations = [
migrations.AddField(
model_name='epgdata',
name='lineup',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -153,6 +153,7 @@ class EPGData(models.Model):
tvg_id = models.CharField(max_length=255, null=True, blank=True, db_index=True)
name = models.CharField(max_length=512)
icon_url = models.URLField(max_length=500, null=True, blank=True)
lineup = models.CharField(max_length=255, null=True, blank=True)
epg_source = models.ForeignKey(
EPGSource,
on_delete=models.CASCADE,

View file

@ -197,6 +197,7 @@ class EPGDataSerializer(serializers.ModelSerializer):
'tvg_id',
'name',
'icon_url',
'lineup',
'epg_source',
]

View file

@ -2971,12 +2971,20 @@ def fetch_schedules_direct(
headers=_sd_headers(),
timeout=30,
)
token_response.raise_for_status()
token_data = token_response.json()
# Parse the response body first so we can surface SD's own error message
# regardless of HTTP status code (SD sometimes returns 400 for app-level
# errors like SERVICE_OFFLINE that carry a meaningful JSON body).
try:
token_data = token_response.json()
except Exception:
token_response.raise_for_status()
raise
auth_code = token_data.get('code', 0)
if auth_code != 0:
if auth_code == 4007:
if not token_response.ok or auth_code != 0:
if auth_code == 3000:
msg = "Schedules Direct is offline for maintenance. Try again later."
elif auth_code == 4007:
msg = "Schedules Direct: this application is not authorized. Please contact the Dispatcharr maintainers."
elif auth_code == 4004:
msg = "Schedules Direct: account locked due to too many failed login attempts. Try again in 15 minutes."
@ -2986,8 +2994,13 @@ def fetch_schedules_direct(
msg = "Schedules Direct: account has expired. Please renew your subscription at schedulesdirect.org."
elif auth_code == 4008:
msg = "Schedules Direct: account is inactive. Please log in to schedulesdirect.org to reactivate."
else:
elif auth_code == 4003:
msg = "Schedules Direct: invalid username or password."
elif auth_code != 0:
msg = f"Schedules Direct authentication failed (code {auth_code}): {token_data.get('message', 'Unknown error')}"
else:
token_response.raise_for_status()
raise RuntimeError(f"Unexpected SD token response: {token_response.text}")
logger.error(msg)
source.status = EPGSource.STATUS_ERROR
source.last_message = msg
@ -3118,6 +3131,7 @@ def fetch_schedules_direct(
lineup_id = lineup.get('lineupID') or lineup.get('lineup')
if not lineup_id:
continue
lineup_name = lineup.get('name') or lineup_id
try:
detail_response = requests.get(
f"{SD_BASE_URL}/lineups/{lineup_id}",
@ -3143,6 +3157,8 @@ def fetch_schedules_direct(
'name': station.get('name', sid),
'callsign': station.get('callsign', ''),
'logo_url': logo_url,
# Preserve lineup from first encounter if station appears in multiple lineups
'lineup': station_map.get(sid, {}).get('lineup') or lineup_name,
}
logger.debug(f"Fetched {len(detail_data.get('stations', []))} stations from lineup {lineup_id}")
except requests.exceptions.RequestException as e:
@ -3182,6 +3198,7 @@ def fetch_schedules_direct(
logo = info['logo_url']
if logo and len(logo) > icon_max_length:
logo = None
lineup_val = info.get('lineup')
if sid in existing_epg_map:
epg_obj = existing_epg_map[sid]
@ -3192,6 +3209,9 @@ def fetch_schedules_direct(
if epg_obj.icon_url != logo:
epg_obj.icon_url = logo
needs_update = True
if epg_obj.lineup != lineup_val:
epg_obj.lineup = lineup_val
needs_update = True
if needs_update:
epgs_to_update.append(epg_obj)
else:
@ -3199,6 +3219,7 @@ def fetch_schedules_direct(
tvg_id=sid,
name=display_name,
icon_url=logo,
lineup=lineup_val,
epg_source=source,
))
@ -3206,7 +3227,7 @@ def fetch_schedules_direct(
EPGData.objects.bulk_create(epgs_to_create, ignore_conflicts=True)
logger.info(f"Created {len(epgs_to_create)} new EPGData entries.")
if epgs_to_update:
EPGData.objects.bulk_update(epgs_to_update, ['name', 'icon_url'])
EPGData.objects.bulk_update(epgs_to_update, ['name', 'icon_url', 'lineup'])
logger.info(f"Updated {len(epgs_to_update)} existing EPGData entries.")
gc.collect()

View file

@ -1179,11 +1179,13 @@ const ChannelForm = ({ channel: channelProp = null, isOpen, onClose }) => {
setEpgPopoverOpened(false);
}}
>
{filteredTvgs[index].name &&
filteredTvgs[index].tvg_id
? `${filteredTvgs[index].name} (${filteredTvgs[index].tvg_id})`
: filteredTvgs[index].name ||
filteredTvgs[index].tvg_id}
{(() => {
const tvg = filteredTvgs[index];
const base = tvg.name && tvg.tvg_id
? `${tvg.name} (${tvg.tvg_id})`
: tvg.name || tvg.tvg_id;
return tvg.lineup ? `${base} [${tvg.lineup}]` : base;
})()}
</Button>
</div>
)}