Erweiterte Staffelwahl

This commit is contained in:
Akamaru
2025-09-15 16:16:44 +02:00
parent 758aa0065f
commit 79b8aa2a34

View File

@@ -401,14 +401,30 @@ class SerienChecker(QMainWindow):
# Serien und Episoden Layout
content_layout = QHBoxLayout()
# Linke Seite - Serien Liste
# Linke Seite - Serien Liste und Staffelauswahl
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
# Serien Liste
list_group = QGroupBox("Gespeicherte Serien")
list_layout = QVBoxLayout()
self.series_list = QListWidget()
self.series_list.currentItemChanged.connect(self.on_series_selected)
list_layout.addWidget(self.series_list)
list_group.setLayout(list_layout)
content_layout.addWidget(list_group)
left_layout.addWidget(list_group)
# Staffelauswahl
season_group = QGroupBox("Staffelauswahl")
season_layout = QVBoxLayout()
self.season_combo = QComboBox()
self.season_combo.addItem("Alle Staffeln")
self.season_combo.currentIndexChanged.connect(self.on_season_selected)
season_layout.addWidget(self.season_combo)
season_group.setLayout(season_layout)
left_layout.addWidget(season_group)
content_layout.addWidget(left_widget)
# Rechte Seite - Episoden
episodes_group = QGroupBox("Episoden")
@@ -664,47 +680,78 @@ class SerienChecker(QMainWindow):
logging.error(f"Fehler beim Extrahieren der Episodeninformationen: {str(e)}")
return None, None, None
def get_staffel_url(self, slug, staffel_nr=None):
"""Generiert die URL für eine bestimmte Staffel"""
logging.debug(f"Generiere Staffel-URL für Slug: {slug}, Staffel: {staffel_nr}")
def get_available_seasons(self, slug):
"""Holt alle verfügbaren Staffeln einer Serie"""
logging.debug(f"Hole verfügbare Staffeln für Slug: {slug}")
try:
# Hole die Übersichtsseite
url = f"https://www.fernsehserien.de/{slug}/episodenguide"
logging.debug(f"Hole Übersichtsseite: {url}")
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
staffel_links = []
headers = soup.find_all('h2', class_='header-2015')
for header in headers:
link = header.find('a', class_='fs-linkable')
# Suche nach dem Episodenguide-Menü
menu_items = soup.find_all('li')
for item in menu_items:
link = item.find('a')
if not link or not link.get('href'):
continue
href = link['href']
title = link.text.strip()
# Überspringe irrelevante Links
if any(x in href for x in ['/news', '/cast-crew', '/sendetermine']):
continue
# Prüfe auf normale Staffel-Links
match = re.search(r'/staffel-(\d+)/(\d+)$', href)
if match:
s_nr = int(match.group(1))
serie_id = match.group(2)
staffel_links.append(('staffel', s_nr, serie_id))
logging.debug(f"Gefundene Staffel: {s_nr} mit ID: {serie_id}")
staffel_links.append(('staffel', s_nr, serie_id, title))
logging.debug(f"Gefundene Staffel: {title} mit ID: {serie_id}")
continue
# Prüfe auf "bisher X Folgen" oder Jahres-Format
match = re.search(r'episodenguide/(\d+)/(\d+)$', href)
if match:
section_id = match.group(1)
serie_id = match.group(2)
# Prüfe den Text für die Art des Eintrags
text = link.text.strip()
if "bisher" in text and "Folgen" in text:
logging.debug(f"Gefunden 'bisher X Folgen' mit ID: {serie_id}")
staffel_links.append(('folgen', section_id, serie_id))
elif text.isdigit(): # Jahresformat
logging.debug(f"Gefunden Jahr {text} mit ID: {serie_id}")
staffel_links.append(('jahr', section_id, serie_id))
# Prüfe auf andere Formate (Jahre, Specials, etc.)
if href.startswith('#'):
# Überspringe "zurück nach oben" Link
if title.lower() in ['zurück nach oben', 'nach oben']:
continue
# Lokaler Anker, extrahiere den Namen
section_name = href[1:] # Entferne das #
staffel_links.append(('section', section_name, '0', title))
logging.debug(f"Gefundene Sektion: {title}")
else:
# Prüfe auf andere Episodenguide-Links
match = re.search(r'episodenguide/([^/]+)/(\d+)$', href)
if match:
section_id = match.group(1)
serie_id = match.group(2)
staffel_links.append(('other', section_id, serie_id, title))
logging.debug(f"Gefundene andere Staffel: {title} mit ID: {serie_id}")
# Entferne Duplikate (basierend auf Titel)
unique_links = []
seen_titles = set()
for link in staffel_links:
if link[3] not in seen_titles:
unique_links.append(link)
seen_titles.add(link[3])
return unique_links
except Exception as e:
logging.error(f"Fehler beim Abrufen der Staffeln: {str(e)}")
return []
def get_staffel_url(self, slug, staffel_nr=None):
"""Generiert die URL für eine bestimmte Staffel"""
logging.debug(f"Generiere Staffel-URL für Slug: {slug}, Staffel: {staffel_nr}")
try:
staffel_links = self.get_available_seasons(slug)
if not staffel_links:
logging.warning("Keine Staffeln oder Episodengruppen gefunden!")
@@ -712,7 +759,7 @@ class SerienChecker(QMainWindow):
if staffel_nr:
# Suche nach der gewünschten Staffel (nur für normale Staffeln)
for typ, nr, serie_id in staffel_links:
for typ, nr, serie_id, title in staffel_links:
if typ == 'staffel' and nr == staffel_nr:
url = f"https://www.fernsehserien.de/{slug}/episodenguide/staffel-{staffel_nr}/{serie_id}"
logging.debug(f"Generierte URL für spezifische Staffel: {url}")
@@ -726,23 +773,42 @@ class SerienChecker(QMainWindow):
# Sortiere nach Typ und dann nach Nummer/ID
def sort_key(x):
typ, nr, serie_id = x
type_priority = {'staffel': 3, 'jahr': 2, 'folgen': 1}
# Bei Staffeln nach Staffelnummer sortieren, sonst nach section_id
if typ == 'staffel':
return (type_priority.get(typ, 0), int(nr)) # nr ist hier die Staffelnummer
else:
return (type_priority.get(typ, 0), int(nr)) # nr ist hier die section_id
typ, nr, serie_id, title = x
type_priority = {'staffel': 3, 'other': 2, 'section': 1}
# Versuche nr als Zahl zu interpretieren, wenn möglich
try:
num_nr = int(nr)
except (ValueError, TypeError):
num_nr = 0
return (type_priority.get(typ, 0), num_nr)
# Finde den neuesten Eintrag
newest = max(staffel_links, key=sort_key)
typ, nr, serie_id = newest
typ, nr, serie_id, title = newest
# Hole die Serie-ID aus einem beliebigen Link
serie_id = None
for t, n, sid, _ in staffel_links:
if t == 'other' and sid != '0':
serie_id = sid
break
if not serie_id:
logging.warning("Keine gültige Serie-ID gefunden!")
return None
if typ == 'staffel':
url = f"https://www.fernsehserien.de/{slug}/episodenguide/staffel-{nr}/{serie_id}"
logging.debug(f"Generierte URL für neueste Staffel ({nr}): {url}")
logging.debug(f"Generierte URL für neueste Staffel ({title}): {url}")
elif typ == 'section':
# Bei Sektionen (Jahren) verwende die erste Sektion mit der Serie-ID
url = f"https://www.fernsehserien.de/{slug}/episodenguide/1/{serie_id}"
logging.debug(f"Generierte URL für Sektion ({title}): {url}")
else:
url = f"https://www.fernsehserien.de/{slug}/episodenguide/{nr}/{serie_id}"
logging.debug(f"Generierte URL für Episodengruppe: {url}")
logging.debug(f"Generierte URL für Episodengruppe ({title}): {url}")
return url
except Exception as e:
@@ -754,11 +820,103 @@ class SerienChecker(QMainWindow):
if current:
slug = current.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten
if slug in self.series:
# Aktualisiere das Staffel-Dropdown
self.season_combo.clear()
self.season_combo.addItem("Alle Staffeln")
# Hole verfügbare Staffeln
staffel_links = self.get_available_seasons(slug)
# Sortiere: Erst normale Staffeln (nach Nummer), dann andere (alphabetisch)
sorted_links = sorted(staffel_links,
key=lambda x: (x[0] != 'staffel', # Staffeln zuerst
int(x[1]) if x[0] == 'staffel' else 0, # Nach Staffelnummer
x[3].lower())) # Dann alphabetisch nach Titel
for typ, nr, serie_id, title in sorted_links:
self.season_combo.addItem(title, (typ, nr, serie_id))
self.refresh_selected_series()
else:
logging.warning(f"Serie {slug} nicht gefunden!")
else:
self.episodes_table.setRowCount(0)
self.season_combo.clear()
self.season_combo.addItem("Alle Staffeln")
def get_section_episodes(self, soup, section_id):
"""Extrahiert Episoden aus einer bestimmten Sektion"""
episodes = []
# Finde die Sektion anhand der ID
section = soup.find(id=section_id)
if section:
logging.debug(f"Sektion {section_id} gefunden")
# Suche nach allen Episoden in dieser Sektion
# Suche zuerst in der Tabelle
episode_table = section.find('table', class_='episode-output')
if episode_table:
logging.debug("Episodentabelle gefunden")
for row in episode_table.find_all('tr'):
# Überspringe Header-Zeilen
if row.find('th'):
continue
cells = row.find_all('td')
if len(cells) >= 4: # Datum, Folge, Titel, ...
try:
# Extrahiere das Datum
datum_cell = cells[0]
datum = datum_cell.text.strip()
# Extrahiere die Folgennummer
folge_cell = cells[1]
folge_text = folge_cell.text.strip()
folge_match = re.search(r'(\d+)', folge_text)
if folge_match:
folge = int(folge_match.group(1))
else:
continue
# Extrahiere den Titel
titel_cell = cells[2]
titel = titel_cell.text.strip()
if not titel:
titel = "Noch kein Titel"
# Verwende die Jahreszahl als Staffelnummer
staffel = int(section_id) if section_id.isdigit() else 1
episodes.append({
'date': datum,
'staffel': staffel,
'folge': folge,
'titel': titel
})
logging.debug(f"Episode gefunden: Staffel {staffel}, Folge {folge}, Titel: {titel}")
except (ValueError, AttributeError) as e:
logging.debug(f"Fehler beim Parsen einer Zeile: {str(e)}")
continue
else:
logging.debug("Keine Episodentabelle gefunden")
# Wenn keine Tabelle gefunden wurde, suche nach einzelnen Episoden-Sektionen
if not episodes:
episode_sections = section.find_all('section', {'itemprop': 'episode'})
for episode in episode_sections:
staffel, folge, titel = self.get_episode_info(episode)
if all([staffel, folge, titel]):
datum = self.get_premiere_date(episode)
episodes.append({
'date': datum,
'staffel': staffel,
'folge': folge,
'titel': titel
})
else:
logging.debug(f"Sektion {section_id} nicht gefunden")
return episodes
def refresh_selected_series(self):
"""Aktualisiert die Episodenliste für die ausgewählte Serie"""
@@ -776,18 +934,32 @@ class SerienChecker(QMainWindow):
logging.debug(f"Aktualisiere Serie: {selected_data['name']}")
try:
# Bestimme die Staffel-URL basierend auf den Einstellungen
settings = selected_data.get('staffel_setting', {})
mode = settings.get('mode', "Neuste Staffel")
staffel_nr = settings.get('staffel') if mode == "Bestimmte Staffel" else None
# Hole die ausgewählte Staffel aus dem Dropdown
current_index = self.season_combo.currentIndex()
if current_index <= 0: # "Alle Staffeln" oder keine Auswahl
# Verwende die Einstellungen aus der Konfiguration
settings = selected_data.get('staffel_setting', {})
mode = settings.get('mode', "Neuste Staffel")
staffel_nr = settings.get('staffel') if mode == "Bestimmte Staffel" else None
url = self.get_staffel_url(slug, staffel_nr)
else:
# Verwende die ausgewählte Staffel
dropdown_data = self.season_combo.itemData(current_index)
if dropdown_data:
typ, nr, serie_id = dropdown_data
if typ == 'staffel':
url = f"https://www.fernsehserien.de/{slug}/episodenguide/staffel-{nr}/{serie_id}"
elif typ == 'section':
# Bei lokalen Ankern die Hauptseite verwenden und nach der Sektion suchen
url = f"https://www.fernsehserien.de/{slug}/episodenguide"
else:
url = f"https://www.fernsehserien.de/{slug}/episodenguide/{nr}/{serie_id}"
else:
url = None
url = self.get_staffel_url(slug, staffel_nr)
if not url:
self.episodes_table.setRowCount(1)
if mode == "Bestimmte Staffel":
error_msg = f"Staffel {staffel_nr} wurde nicht gefunden!"
else:
error_msg = "Keine Staffeln gefunden!"
error_msg = "Keine Staffeln gefunden!"
self.episodes_table.setItem(0, 0, QTableWidgetItem(error_msg))
for i in range(1, 4):
self.episodes_table.setItem(0, i, QTableWidgetItem(""))
@@ -795,33 +967,35 @@ class SerienChecker(QMainWindow):
return
episodes = []
page = 1
# Prüfe ob die Serie als "ohne klassische Staffeln" markiert ist
no_seasons = selected_data.get('no_seasons', False)
while True:
current_url = f"{url}/{page}" if page > 1 and no_seasons else url
logging.debug(f"Hole Episoden von: {current_url}")
try:
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
try:
response = requests.get(current_url)
if response.status_code == 404:
logging.debug(f"Seite {page} nicht gefunden, beende Suche")
break
# Bei normalen Staffeln nur eine Seite laden
if not no_seasons:
logging.debug("Serie hat klassische Staffeln, lade nur aktuelle Seite")
response.raise_for_status() # Prüfe auf andere HTTP-Fehler
soup = BeautifulSoup(response.text, 'html.parser')
# Prüfe ob die Seite Episoden enthält
# Wenn es eine spezifische Sektion ist, suche nur in dieser
if current_index > 0:
dropdown_data = self.season_combo.itemData(current_index)
if dropdown_data and dropdown_data[0] == 'section':
section_id = dropdown_data[1]
episodes = self.get_section_episodes(soup, section_id)
logging.debug(f"Suche Episoden in Sektion: {section_id}")
else:
# Normale Episodensuche für Staffeln
page_episodes = soup.find_all('section', {'itemprop': 'episode'})
for episode in page_episodes:
staffel, folge, titel = self.get_episode_info(episode)
if all([staffel, folge, titel]):
datum = self.get_premiere_date(episode)
episodes.append({
'date': datum,
'staffel': staffel,
'folge': folge,
'titel': titel
})
else:
# Bei "Alle Staffeln" alle Episoden laden
page_episodes = soup.find_all('section', {'itemprop': 'episode'})
if not page_episodes:
logging.debug(f"Keine Episoden auf Seite {page} gefunden, beende Suche")
break
for episode in page_episodes:
staffel, folge, titel = self.get_episode_info(episode)
if all([staffel, folge, titel]):
@@ -833,14 +1007,13 @@ class SerienChecker(QMainWindow):
'titel': titel
})
# Bei normalen Staffeln nach der ersten Seite aufhören
if not no_seasons:
break
page += 1
except requests.RequestException as e:
logging.error(f"Fehler beim Abrufen von Seite {page}: {str(e)}")
break
except requests.RequestException as e:
logging.error(f"Fehler beim Abrufen der Episoden: {str(e)}")
self.episodes_table.setRowCount(1)
self.episodes_table.setItem(0, 0, QTableWidgetItem(f"Fehler: {str(e)}"))
for i in range(1, 4):
self.episodes_table.setItem(0, i, QTableWidgetItem(""))
return
if not episodes:
self.episodes_table.setRowCount(1)
@@ -883,6 +1056,11 @@ class SerienChecker(QMainWindow):
def show_debug_log(self):
self.log_window.show()
def on_season_selected(self, index):
"""Wird aufgerufen, wenn eine andere Staffel im Dropdown ausgewählt wird"""
if self.series_list.currentItem():
self.refresh_selected_series()
if __name__ == '__main__':
app = QApplication(sys.argv)