import sys import json import requests import re import logging from bs4 import BeautifulSoup from datetime import datetime from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QLabel, QMessageBox, QSpinBox, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView, QDialog, QDialogButtonBox, QTextEdit, QGroupBox, QToolBar, QSplitter, QFormLayout, QListWidgetItem, QCheckBox) from PyQt5.QtCore import Qt, QTimer # Logging Konfiguration logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') class LogWindow(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Debug Log") self.setGeometry(100, 100, 800, 400) layout = QVBoxLayout(self) self.log_text = QTextEdit() self.log_text.setReadOnly(True) layout.addWidget(self.log_text) close_button = QPushButton("Schließen") close_button.clicked.connect(self.close) layout.addWidget(close_button) def append_log(self, message): self.log_text.append(message) class DebugHandler(logging.Handler): def __init__(self, log_window): super().__init__() self.log_window = log_window def emit(self, record): msg = self.format(record) self.log_window.append_log(msg) class NewSeriesDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.setup_ui() def setup_ui(self): self.setWindowTitle("Neue Serie hinzufügen") self.setMinimumWidth(400) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) layout = QVBoxLayout(self) # Einstellungen settings_group = QGroupBox("Serie hinzufügen") settings_layout = QFormLayout() # Serien-Eingabe self.slug_input = QLineEdit() self.slug_input.setPlaceholderText("Serien-URL oder Slug") settings_layout.addRow("Serie:", self.slug_input) # Staffel-Einstellungen self.staffel_mode = QComboBox() self.staffel_mode.addItems(["Neuste Staffel", "Alle Staffeln", "Bestimmte Staffel"]) self.staffel_mode.currentIndexChanged.connect(self.on_staffel_mode_changed) settings_layout.addRow("Staffel-Modus:", self.staffel_mode) self.staffel_spin = QSpinBox() self.staffel_spin.setMinimum(1) self.staffel_spin.setMaximum(100) self.staffel_spin.setEnabled(False) settings_layout.addRow("Staffel:", self.staffel_spin) # Datumspräferenz self.date_pref = QComboBox() self.date_pref.addItems(["Bevorzuge Erstausstrahlung", "Bevorzuge TV", "Bevorzuge Streaming", "Bevorzuge deutsche Synchro"]) settings_layout.addRow("Datum Präferenz:", self.date_pref) # Checkbox für Serien ohne klassische Staffeln self.no_seasons_check = QCheckBox("Serie ohne klassische Staffeln") self.no_seasons_check.setToolTip("Aktivieren für Serien mit fortlaufender Nummerierung oder Jahresformat") settings_layout.addRow("", self.no_seasons_check) settings_group.setLayout(settings_layout) layout.addWidget(settings_group) # Dialog Buttons buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def on_staffel_mode_changed(self, index): """Wird aufgerufen, wenn sich der Staffel-Modus ändert""" self.staffel_spin.setEnabled(self.staffel_mode.currentText() == "Bestimmte Staffel") def get_series_data(self): """Extrahiert die Seriendaten aus den Eingabefeldern""" input_text = self.slug_input.text().strip() if not input_text: QMessageBox.warning(self, "Fehler", "Bitte geben Sie eine Serie ein.") return None # Extrahiere Slug aus URL wenn nötig if "/" in input_text: try: # Prüfe ob es eine fernsehserien.de URL ist if "fernsehserien.de" not in input_text: QMessageBox.warning(self, "Fehler", "Die URL muss von fernsehserien.de sein.") return None # Extrahiere den Slug (Teil nach dem letzten /) slug = input_text.rstrip("/").split("/")[-1] if slug == "episodenguide": # Wenn die URL auf /episodenguide endet, nimm den Teil davor slug = input_text.rstrip("/").split("/")[-2] except Exception as e: QMessageBox.warning(self, "Fehler", f"Ungültige URL: {str(e)}") return None else: slug = input_text # Hole den Seriennamen von fernsehserien.de try: url = f"https://www.fernsehserien.de/{slug}/episodenguide" response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') title_elem = soup.find('h1') if title_elem: series_name = title_elem.text.strip() else: QMessageBox.warning(self, "Fehler", "Serie nicht gefunden.") return None except Exception as e: QMessageBox.warning(self, "Fehler", f"Fehler beim Abrufen der Serie: {str(e)}") return None return { 'name': series_name, 'slug': slug, 'staffel_setting': { 'mode': self.staffel_mode.currentText(), 'staffel': self.staffel_spin.value() }, 'date_preference': self.date_pref.currentText(), 'no_seasons': self.no_seasons_check.isChecked() } class SeriesEditDialog(QDialog): def __init__(self, series_data, parent=None): super().__init__(parent) self.series_data = series_data.copy() self.parent = parent self.setup_ui() def setup_ui(self): self.setWindowTitle("Serien verwalten") self.setMinimumWidth(500) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) layout = QVBoxLayout(self) # Serien Liste und Einstellungen nebeneinander content_layout = QHBoxLayout() # Linke Seite - Serien Liste und Info left_layout = QVBoxLayout() list_group = QGroupBox("Gespeicherte Serien") list_layout = QVBoxLayout() self.series_list = QListWidget() self.series_list.itemClicked.connect(self.on_series_selected) list_layout.addWidget(self.series_list) # Buttons für die Liste button_layout = QHBoxLayout() add_button = QPushButton("Neue Serie") add_button.clicked.connect(self.add_series) delete_button = QPushButton("Löschen") delete_button.clicked.connect(self.delete_series) button_layout.addWidget(add_button) button_layout.addWidget(delete_button) list_layout.addLayout(button_layout) list_group.setLayout(list_layout) left_layout.addWidget(list_group) # Info Label info_label = QLabel('v1.1 | © Akamaru | Source auf PonyGit') info_label.setOpenExternalLinks(True) # Erlaubt das Öffnen des Links info_label.setTextFormat(Qt.RichText) # Aktiviert HTML-Formatierung left_layout.addWidget(info_label) content_layout.addLayout(left_layout) # Rechte Seite - Einstellungen self.settings_group = QGroupBox("Einstellungen") settings_layout = QFormLayout() # Serien-Eingabe self.slug_input = QLineEdit() self.slug_input.setPlaceholderText("Serien-URL oder Slug") self.slug_input.setEnabled(False) # Deaktiviert, da nur zum Anzeigen settings_layout.addRow("Serie:", self.slug_input) # Staffel-Einstellungen self.staffel_mode = QComboBox() self.staffel_mode.addItems(["Neuste Staffel", "Alle Staffeln", "Bestimmte Staffel"]) self.staffel_mode.currentIndexChanged.connect(self.on_staffel_mode_changed) settings_layout.addRow("Staffel-Modus:", self.staffel_mode) self.staffel_spin = QSpinBox() self.staffel_spin.setMinimum(1) self.staffel_spin.setMaximum(100) self.staffel_spin.setEnabled(False) settings_layout.addRow("Staffel:", self.staffel_spin) # Datumspräferenz self.date_pref = QComboBox() self.date_pref.addItems(["Bevorzuge Erstausstrahlung", "Bevorzuge TV", "Bevorzuge Streaming", "Bevorzuge deutsche Synchro"]) settings_layout.addRow("Datum Präferenz:", self.date_pref) # Checkbox für Serien ohne klassische Staffeln self.no_seasons_check = QCheckBox("Serie ohne klassische Staffeln") self.no_seasons_check.setToolTip("Aktivieren für Serien mit fortlaufender Nummerierung oder Jahresformat") settings_layout.addRow("", self.no_seasons_check) # Speichern Button für Einstellungen save_button = QPushButton("Einstellungen speichern") save_button.clicked.connect(self.save_settings) settings_layout.addRow("", save_button) self.settings_group.setLayout(settings_layout) content_layout.addWidget(self.settings_group) # Füge das Content-Layout zum Hauptlayout hinzu layout.addLayout(content_layout) # Dialog Buttons buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) # Initialisiere die Liste und deaktiviere die Einstellungen self.update_series_list() self.settings_group.setEnabled(False) def accept(self): """Schließt den Dialog""" super().accept() # Schließe den Dialog einfach def save_settings(self): """Speichert die Einstellungen für die aktuelle Serie""" current_item = self.series_list.currentItem() if not current_item: QMessageBox.warning(self, "Fehler", "Bitte wählen Sie eine Serie zum Bearbeiten aus!") return slug = current_item.data(Qt.UserRole) if slug not in self.series_data: QMessageBox.warning(self, "Fehler", "Serie nicht gefunden!") return # Aktualisiere die Einstellungen self.series_data[slug].update({ 'staffel_setting': { 'mode': self.staffel_mode.currentText(), 'staffel': self.staffel_spin.value() }, 'date_preference': self.date_pref.currentText(), 'no_seasons': self.no_seasons_check.isChecked() }) QMessageBox.information(self, "Erfolg", "Einstellungen wurden gespeichert!") def add_series(self): """Öffnet den Dialog zum Hinzufügen einer neuen Serie""" dialog = NewSeriesDialog(self) if dialog.exec_() == QDialog.Accepted: series_data = dialog.get_series_data() if series_data: slug = series_data['slug'] self.series_data[slug] = series_data self.update_series_list() # Wähle die neue Serie aus for i in range(self.series_list.count()): if self.series_list.item(i).data(Qt.UserRole) == slug: self.series_list.setCurrentRow(i) break QMessageBox.information(self, "Erfolg", "Serie wurde hinzugefügt!") def on_staffel_mode_changed(self, index): """Wird aufgerufen, wenn sich der Staffel-Modus ändert""" self.staffel_spin.setEnabled(self.staffel_mode.currentText() == "Bestimmte Staffel") def update_series_list(self): """Aktualisiert die Liste der Serien""" self.series_list.clear() # Sortiere nach Namen sorted_series = sorted(self.series_data.items(), key=lambda x: x[1]['name'].lower()) for slug, data in sorted_series: item = QListWidgetItem(data['name']) item.setData(Qt.UserRole, slug) # Speichere den Slug als Zusatzdaten self.series_list.addItem(item) def on_series_selected(self, item): """Wird aufgerufen, wenn eine Serie ausgewählt wird""" self.settings_group.setEnabled(bool(item)) if item: slug = item.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten data = self.series_data.get(slug, {}) self.slug_input.setText(data.get('slug', slug)) # Staffel-Einstellungen laden staffel_setting = data.get('staffel_setting', {}) mode = staffel_setting.get('mode', "Neuste Staffel") staffel = staffel_setting.get('staffel', 1) index = self.staffel_mode.findText(mode) if index >= 0: self.staffel_mode.setCurrentIndex(index) self.staffel_spin.setValue(staffel) # Datumspräferenz laden date_pref = data.get('date_preference', "Bevorzuge Erstausstrahlung") index = self.date_pref.findText(date_pref) if index >= 0: self.date_pref.setCurrentIndex(index) # Keine Staffeln Checkbox laden self.no_seasons_check.setChecked(data.get('no_seasons', False)) def delete_series(self): """Löscht die ausgewählte Serie""" current_item = self.series_list.currentItem() if not current_item: QMessageBox.warning(self, "Fehler", "Bitte wählen Sie eine Serie zum Löschen aus!") return slug = current_item.data(Qt.UserRole) name = self.series_data[slug]['name'] reply = QMessageBox.question( self, "Serie löschen", f"Möchten Sie die Serie '{name}' wirklich löschen?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: del self.series_data[slug] self.update_series_list() self.slug_input.clear() QMessageBox.information(self, "Erfolg", f"Serie '{name}' wurde gelöscht!") class SerienChecker(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Serien Checker") self.setGeometry(100, 100, 1000, 800) # Initialisiere series als leeres Dictionary self.series = {} # Log-Fenster erstellen self.log_window = LogWindow(self) # Zentral-Widget und Layout central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # Toolbar toolbar = QToolBar() self.addToolBar(toolbar) # Serien verwalten Button manage_button = QPushButton("Serien verwalten") manage_button.clicked.connect(self.manage_series) toolbar.addWidget(manage_button) # Debug Log Button debug_button = QPushButton("Debug Log") debug_button.clicked.connect(self.show_debug_log) toolbar.addWidget(debug_button) # Serien und Episoden Layout content_layout = QHBoxLayout() # Linke Seite - 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) # Rechte Seite - Episoden episodes_group = QGroupBox("Episoden") episodes_layout = QVBoxLayout() self.episodes_table = QTableWidget() self.episodes_table.setColumnCount(4) self.episodes_table.setHorizontalHeaderLabels(["Datum", "Staffel", "Folge", "Titel"]) header = self.episodes_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.Stretch) episodes_layout.addWidget(self.episodes_table) # Aktualisieren Button refresh_button = QPushButton("Aktualisieren") refresh_button.clicked.connect(self.refresh_selected_series) episodes_layout.addWidget(refresh_button) episodes_group.setLayout(episodes_layout) content_layout.addWidget(episodes_group) # Füge das Content-Layout zum Hauptlayout hinzu layout.addLayout(content_layout) # Timer für automatische Aktualisierung (alle 30 Minuten) self.timer = QTimer() self.timer.timeout.connect(self.refresh_selected_series) self.timer.start(30 * 60 * 1000) # Lade die Konfiguration und aktualisiere die Liste self.load_config() self.update_series_list() self.show() def manage_series(self): dialog = SeriesEditDialog(self.series, self) if dialog.exec_() == QDialog.Accepted: self.series = dialog.series_data self.save_config() self.update_series_list() self.refresh_all() def load_config(self): """Lädt die Konfiguration""" try: with open('series_config.json', 'r', encoding='utf-8') as f: config = json.load(f) if isinstance(config, dict) and 'series' in config: self.series = config['series'] else: # Alte Konfiguration kompatibel machen self.series = {} for slug, data in config.items(): self.series[slug] = { 'name': data.get('name', slug), 'staffel_setting': { 'mode': data.get('settings', {}).get('mode', "Neuste Staffel"), 'staffel': data.get('settings', {}).get('staffel', 1) }, 'date_preference': "Bevorzuge Erstausstrahlung" } except FileNotFoundError: logging.debug("Keine Konfigurationsdatei gefunden, verwende leeres Dictionary") self.series = {} def save_config(self): """Speichert die Konfiguration""" with open('series_config.json', 'w', encoding='utf-8') as f: json.dump({'series': self.series}, f, indent=4, ensure_ascii=False) def update_series_list(self): """Aktualisiert die Liste der Serien""" self.series_list.clear() # Sortiere nach Namen sorted_series = sorted(self.series.items(), key=lambda x: x[1]['name'].lower()) for slug, data in sorted_series: item = QListWidgetItem(data['name']) item.setData(Qt.UserRole, slug) # Speichere den Slug als Zusatzdaten self.series_list.addItem(item) def parse_date(self, date_str): try: return datetime.strptime(date_str.split()[0], "%d.%m.%Y") except: return None def get_premiere_date(self, episode): """Extrahiert das erste deutsche Ausstrahlungsdatum (TV oder Streaming) basierend auf der Präferenz""" logging.debug("Suche nach Premierendaten") try: tv_date = None streaming_date = None synchro_date = None # Suche nach allen deutschen Premieren for ea_angabe in episode.find_all('ea-angabe'): titel_elem = ea_angabe.find('ea-angabe-titel') if not titel_elem: continue titel = titel_elem.text.strip() logging.debug(f"Gefundene Premiere: {titel}") # Prüfe auf deutsche Daten datum_elem = ea_angabe.find('ea-angabe-datum') if datum_elem: date_str = datum_elem.text.strip() if '. ' in date_str: date_str = date_str.split('. ', 1)[1] parsed_date = self.parse_date(date_str) if parsed_date: if "TV-Premiere" in titel and "Deutsche" in titel: tv_date = (parsed_date, date_str) logging.debug(f"Deutsche TV-Premiere gefunden: {date_str}") elif "Streaming-Premiere" in titel and "Deutsche" in titel: streaming_date = (parsed_date, date_str) logging.debug(f"Deutsche Streaming-Premiere gefunden: {date_str}") elif "deutschen Synchronfassung" in titel: synchro_date = (parsed_date, date_str) logging.debug(f"Deutsche Synchro-Premiere gefunden: {date_str}") # Hole die Datumspräferenz für diese Serie current_series = self.series_list.currentItem() if current_series: slug = current_series.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten pref = self.series[slug].get('date_preference', "Bevorzuge Erstausstrahlung") else: pref = "Bevorzuge Erstausstrahlung" # Prüfe zuerst auf Synchro-Präferenz if pref == "Bevorzuge deutsche Synchro": if synchro_date: result = synchro_date[1] logging.debug(f"Synchro-Premiere gewählt: {result}") return result else: logging.debug("Keine Synchro-Premiere gefunden, zeige TBA") return "TBA" # Andere Präferenzen if pref == "Bevorzuge TV" and tv_date: result = tv_date[1] logging.debug(f"TV-Premiere gewählt: {result}") elif pref == "Bevorzuge Streaming" and streaming_date: result = streaming_date[1] logging.debug(f"Streaming-Premiere gewählt: {result}") else: # Bevorzuge Erstausstrahlung dates = [] if tv_date: dates.append(tv_date) if streaming_date: dates.append(streaming_date) if synchro_date: dates.append(synchro_date) if dates: result = min(dates, key=lambda x: x[0])[1] logging.debug(f"Frühestes Datum gewählt: {result}") else: logging.warning("Keine deutschen Premierendaten gefunden!") return "TBA" return result except Exception as e: logging.error(f"Fehler beim Extrahieren des Premierendatums: {str(e)}") return "TBA" def on_date_preference_changed(self, series_slug): """Wird aufgerufen, wenn sich die Datumspräferenz einer Serie ändert""" logging.debug(f"Datumspräferenz für Serie {series_slug} wurde geändert") self.refresh_selected_series() def get_episode_info(self, episode): """Extrahiert Folgeninformationen aus einem Episode-Element""" try: logging.debug("Versuche Episodeninformationen zu extrahieren") # Suche nach Staffel und Folge in der Episodennummer (z.B. "7.01") header = episode.find('h3', class_='episode-output-titel') if header: episode_link = header.find('a') if episode_link: # URL enthält die Episodennummer (z.B. "7x01") href = episode_link.get('href', '') match = re.search(r'(\d+)x(\d+)-', href) if match: staffel = int(match.group(1)) folge = int(match.group(2)) logging.debug(f"Gefundene Staffel/Folge aus URL: {staffel}/{folge}") else: # Alternative: Suche in der Episoden-Zeile episode_info = episode.find('div', {'itemprop': 'episodeNumber'}) if episode_info and episode_info.text: # Prüfe auf "Folge X" Format folge_match = re.search(r'Folge (\d+)', episode_info.text) if folge_match: folge = int(folge_match.group(1)) # Bei Serien ohne Staffeln setzen wir Staffel auf 1 staffel = 1 logging.debug(f"Gefundene Folge aus 'Folge X' Format: {folge}") else: try: folge = int(episode_info.text) # Staffel aus übergeordnetem Element staffel_info = episode.find_previous('h2', class_='header-2015') if staffel_info: staffel_match = re.search(r'Staffel (\d+)', staffel_info.text) if staffel_match: staffel = int(staffel_match.group(1)) logging.debug(f"Gefundene Staffel/Folge aus Text: {staffel}/{folge}") except ValueError: logging.debug(f"Konnte Folge nicht aus Text extrahieren: {episode_info.text}") # Suche nach deutschem Titel if episode_link: # Prüfe zuerst, ob ein deutscher Titel existiert title_spans = episode_link.find_all('span') title = "Noch kein Titel" for span in title_spans: # Überspringe Folgen-Nummer if span.text.isdigit(): continue # Überspringe englischen Titel if span.get('class') and 'episode-output-originaltitel' in span.get('class'): continue # Überspringe Platzhalter für fehlenden Titel if span.get('title') == "Titel unbekannt": continue # Wenn wir hier sind und der span itemprop="name" hat, ist es der deutsche Titel if span.get('itemprop') == 'name' and span.text.strip() not in ['–', '-']: title = span.text.strip() logging.debug(f"Gefundener deutscher Titel: {title}") break logging.debug(f"Finaler Titel: {title}") else: title = "Noch kein Titel" logging.debug("Kein Link gefunden, verwende Standardtitel") if not all([staffel, folge]): logging.warning(f"Unvollständige Daten: Staffel={staffel}, Folge={folge}") return None, None, None return staffel, folge, title except Exception as e: 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}") 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') if not link or not link.get('href'): continue href = link['href'] # 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}") 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)) if not staffel_links: logging.warning("Keine Staffeln oder Episodengruppen gefunden!") return None if staffel_nr: # Suche nach der gewünschten Staffel (nur für normale Staffeln) for typ, nr, serie_id 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}") return url logging.warning(f"Staffel {staffel_nr} nicht gefunden!") return None else: # Nehme den neuesten Eintrag if not staffel_links: return None # 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 newest = max(staffel_links, key=sort_key) typ, nr, serie_id = newest 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}") else: url = f"https://www.fernsehserien.de/{slug}/episodenguide/{nr}/{serie_id}" logging.debug(f"Generierte URL für Episodengruppe: {url}") return url except Exception as e: logging.error(f"Fehler beim Generieren der Staffel-URL: {str(e)}") return None def on_series_selected(self, current, previous): """Wird aufgerufen, wenn eine Serie in der Liste ausgewählt wird""" if current: slug = current.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten if slug in self.series: self.refresh_selected_series() else: logging.warning(f"Serie {slug} nicht gefunden!") else: self.episodes_table.setRowCount(0) def refresh_selected_series(self): """Aktualisiert die Episodenliste für die ausgewählte Serie""" current_item = self.series_list.currentItem() if not current_item: logging.warning("Keine Serie ausgewählt!") return slug = current_item.data(Qt.UserRole) # Hole den Slug aus den Zusatzdaten if slug not in self.series: logging.warning(f"Serie {slug} nicht gefunden!") return selected_data = self.series[slug] 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 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!" self.episodes_table.setItem(0, 0, QTableWidgetItem(error_msg)) for i in range(1, 4): self.episodes_table.setItem(0, i, QTableWidgetItem("")) logging.error(error_msg) 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(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 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]): datum = self.get_premiere_date(episode) episodes.append({ 'date': datum, 'staffel': staffel, 'folge': folge, '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 if not episodes: self.episodes_table.setRowCount(1) error_msg = "Keine Episoden gefunden!" self.episodes_table.setItem(0, 0, QTableWidgetItem(error_msg)) for i in range(1, 4): self.episodes_table.setItem(0, i, QTableWidgetItem("")) logging.warning(error_msg) return # Sortiere nach Datum (wenn verfügbar) und Staffel/Folge episodes.sort(key=lambda x: ( datetime.strptime(x['date'], '%d.%m.%Y') if x['date'] != 'TBA' else datetime.max, x['staffel'], x['folge'] )) # Zeige alle gefundenen Episoden an # episodes = episodes[:20] # Alte Begrenzung entfernt # Aktualisiere die Tabelle self.episodes_table.setRowCount(len(episodes)) for row, episode in enumerate(episodes): self.episodes_table.setItem(row, 0, QTableWidgetItem(episode['date'])) self.episodes_table.setItem(row, 1, QTableWidgetItem(str(episode['staffel']))) self.episodes_table.setItem(row, 2, QTableWidgetItem(str(episode['folge']))) self.episodes_table.setItem(row, 3, QTableWidgetItem(episode['titel'])) except Exception as e: logging.error(f"Fehler beim Aktualisieren der Serie: {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("")) def refresh_all(self): self.update_series_list() if self.series_list.selectedItems(): self.refresh_selected_series() def show_debug_log(self): self.log_window.show() if __name__ == '__main__': app = QApplication(sys.argv) window = SerienChecker() window.show() sys.exit(app.exec_())