From d5da0f710e5541e4eacef41e2d73816c01057ba6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:25:25 +0000 Subject: [PATCH 1/3] Initial plan From 013edb952f7d2a950ceda15185ac8b57e40e803e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:29:29 +0000 Subject: [PATCH 2/3] Add station selection feature to SetupWizard and scrape function Co-authored-by: nmemmert <67961292+nmemmert@users.noreply.github.com> --- journey_fm_app.py | 37 +++++++++++- main.py | 146 +++++++++++++++++++++++++--------------------- 2 files changed, 114 insertions(+), 69 deletions(-) diff --git a/journey_fm_app.py b/journey_fm_app.py index d6f76b9..e4417d2 100644 --- a/journey_fm_app.py +++ b/journey_fm_app.py @@ -59,6 +59,9 @@ class Config: def save_config(self, config): """Save config to QSettings""" for key, value in config.items(): + # Handle list values (convert to comma-separated string) + if isinstance(value, list): + value = ','.join(value) self.set(key, value) class SetupWizard(QDialog): @@ -109,6 +112,22 @@ class SetupWizard(QDialog): form_group.setLayout(form_layout) layout.addWidget(form_group) + # Station Selection + station_group = QGroupBox("Station Selection") + station_layout = QVBoxLayout() + + station_layout.addWidget(QLabel("Select which stations to monitor:")) + self.journey_fm_checkbox = QCheckBox("Journey FM (myjourneyfm.com)") + self.journey_fm_checkbox.setChecked(True) + station_layout.addWidget(self.journey_fm_checkbox) + + self.spirit_fm_checkbox = QCheckBox("Spirit FM (spiritfm.com)") + self.spirit_fm_checkbox.setChecked(True) + station_layout.addWidget(self.spirit_fm_checkbox) + + station_group.setLayout(station_layout) + layout.addWidget(station_group) + # Scheduling options sched_group = QGroupBox("Scheduling") sched_layout = QVBoxLayout() @@ -147,14 +166,30 @@ class SetupWizard(QDialog): self.server_input.setText(config.get('SERVER_IP', '')) self.playlist_input.setText(config.get('PLAYLIST_NAME', 'Journey FM Recently Played')) + # Load station selections + selected_stations = config.get('SELECTED_STATIONS', 'journey_fm,spirit_fm') + if isinstance(selected_stations, str): + selected_stations = selected_stations.split(',') + else: + selected_stations = ['journey_fm', 'spirit_fm'] + self.journey_fm_checkbox.setChecked('journey_fm' in selected_stations) + self.spirit_fm_checkbox.setChecked('spirit_fm' in selected_stations) + def get_config(self): + selected_stations = [] + if self.journey_fm_checkbox.isChecked(): + selected_stations.append('journey_fm') + if self.spirit_fm_checkbox.isChecked(): + selected_stations.append('spirit_fm') + return { 'PLEX_TOKEN': self.token_input.text().strip(), 'SERVER_IP': self.server_input.text().strip(), 'PLAYLIST_NAME': self.playlist_input.text().strip(), 'AUTO_UPDATE': self.auto_update.isChecked(), 'UPDATE_INTERVAL': self.interval_spin.value(), - 'UPDATE_UNIT': self.interval_unit.currentText() + 'UPDATE_UNIT': self.interval_unit.currentText(), + 'SELECTED_STATIONS': ','.join(selected_stations) } class LogViewer(QWidget): diff --git a/main.py b/main.py index eb2f2e4..d2ab5dd 100644 --- a/main.py +++ b/main.py @@ -32,8 +32,11 @@ PLEX_TOKEN = 'pU-m3HWYUZU6iXJFhJyA' # Your Plex.tv token SERVER_IP = '172.16.16.106' # Your local server IP PLAYLIST_NAME = 'Journey FM Recently Played' -def scrape_recently_played(): +def scrape_recently_played(selected_stations=None): """Scrape recently played songs from Journey FM and Spirit FM""" + if selected_stations is None: + selected_stations = ['journey_fm', 'spirit_fm'] + options = Options() options.add_argument("--headless=new") options.add_argument("--no-sandbox") @@ -58,80 +61,82 @@ def scrape_recently_played(): seen = set() # Journey FM - uses for title, normal text for artist - try: - print("Scraping Journey FM...") - driver.get('https://www.myjourneyfm.com/recently-played/') - time.sleep(5) - - # Click "View More" button if exists + if 'journey_fm' in selected_stations: try: - more_button = driver.find_element(By.ID, "moreSongs") - more_button.click() + print("Scraping Journey FM...") + driver.get('https://www.myjourneyfm.com/recently-played/') time.sleep(5) - except: - pass - - soup = BeautifulSoup(driver.page_source, 'html.parser') - - # Find all song items using the proper class structure - song_items = soup.find_all('div', class_='rp-item') - - for item in song_items: + + # Click "View More" button if exists try: - title_elem = item.find('h5', class_='song-title') - artist_elem = item.find('p', class_='song-artist') - - if title_elem and artist_elem: - title = title_elem.get_text().strip() - artist = artist_elem.get_text().strip() + more_button = driver.find_element(By.ID, "moreSongs") + more_button.click() + time.sleep(5) + except: + pass + + soup = BeautifulSoup(driver.page_source, 'html.parser') + + # Find all song items using the proper class structure + song_items = soup.find_all('div', class_='rp-item') + + for item in song_items: + try: + title_elem = item.find('h5', class_='song-title') + artist_elem = item.find('p', class_='song-artist') + + if title_elem and artist_elem: + title = title_elem.get_text().strip() + artist = artist_elem.get_text().strip() + + if title and artist: + key = (title.lower(), artist.lower()) + if key not in seen: + all_songs.append({'title': title, 'artist': artist, 'source': 'Journey FM'}) + seen.add(key) + print(f" Found: {title} by {artist}") + except Exception as e: + continue + + except Exception as e: + print(f"Error scraping Journey FM: {e}") + + # Spirit FM - loads songs from iframe text file + if 'spirit_fm' in selected_stations: + try: + print("Scraping Spirit FM...") + driver.get('https://spiritfm.com/ajax/now_playing_history.txt') + time.sleep(3) + + # Parse the plain text format: "Mon 01:45PM Artist - Title" + soup = BeautifulSoup(driver.page_source, 'html.parser') + text_content = soup.get_text() + + # Split into lines and parse each + for line in text_content.split('\n'): + line = line.strip() + if not line: + continue + + # Match pattern: Day HH:MM(AM/PM) Artist - Title + match = re.match(r'^\w+\s+\d+:\d+[AP]M\s+(.+?)\s+-\s+(.+)$', line) + if match: + artist = match.group(1).strip() + title = match.group(2).strip() + + # Clean up HTML entities + artist = artist.replace('&', '&') + title = title.replace('&', '&') if title and artist: key = (title.lower(), artist.lower()) if key not in seen: - all_songs.append({'title': title, 'artist': artist, 'source': 'Journey FM'}) + all_songs.append({'title': title, 'artist': artist, 'source': 'Spirit FM'}) seen.add(key) print(f" Found: {title} by {artist}") - except Exception as e: - continue - - except Exception as e: - print(f"Error scraping Journey FM: {e}") - - # Spirit FM - loads songs from iframe text file - try: - print("Scraping Spirit FM...") - driver.get('https://spiritfm.com/ajax/now_playing_history.txt') - time.sleep(3) - - # Parse the plain text format: "Mon 01:45PM Artist - Title" - soup = BeautifulSoup(driver.page_source, 'html.parser') - text_content = soup.get_text() - - # Split into lines and parse each - for line in text_content.split('\n'): - line = line.strip() - if not line: - continue - - # Match pattern: Day HH:MM(AM/PM) Artist - Title - match = re.match(r'^\w+\s+\d+:\d+[AP]M\s+(.+?)\s+-\s+(.+)$', line) - if match: - artist = match.group(1).strip() - title = match.group(2).strip() - - # Clean up HTML entities - artist = artist.replace('&', '&') - title = title.replace('&', '&') - - if title and artist: - key = (title.lower(), artist.lower()) - if key not in seen: - all_songs.append({'title': title, 'artist': artist, 'source': 'Spirit FM'}) - seen.add(key) - print(f" Found: {title} by {artist}") - - except Exception as e: - print(f"Error scraping Spirit FM: {e}") + + except Exception as e: + print(f"Error scraping Spirit FM: {e}") driver.quit() @@ -312,12 +317,17 @@ def setup_scheduler(params): def main(): global PLEX_TOKEN, SERVER_IP, PLAYLIST_NAME # Load or prompt for config + selected_stations = ['journey_fm', 'spirit_fm'] # default if os.path.exists('config.json'): with open('config.json', 'r') as f: config = json.load(f) PLEX_TOKEN = config.get('PLEX_TOKEN', PLEX_TOKEN) SERVER_IP = config.get('SERVER_IP', SERVER_IP) PLAYLIST_NAME = config.get('PLAYLIST_NAME', PLAYLIST_NAME) + # Load selected stations + selected_stations_str = config.get('SELECTED_STATIONS', 'journey_fm,spirit_fm') + if isinstance(selected_stations_str, str): + selected_stations = selected_stations_str.split(',') else: # If no config file, use defaults (GUI should handle this) pass @@ -328,8 +338,8 @@ def main(): # Initialize history database init_history_db() - # Scrape songs - songs = scrape_recently_played() + # Scrape songs with selected stations + songs = scrape_recently_played(selected_stations) # Connect to Plex via MyPlexAccount account = MyPlexAccount(token=PLEX_TOKEN) From f0d9b06ef4817f1f4b752ad6401c41f09c767e7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:31:36 +0000 Subject: [PATCH 3/3] Add validation for station selection to require at least one station Co-authored-by: nmemmert <67961292+nmemmert@users.noreply.github.com> --- journey_fm_app.py | 10 +++++++++- main.py | 5 +++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/journey_fm_app.py b/journey_fm_app.py index e4417d2..1d38d0c 100644 --- a/journey_fm_app.py +++ b/journey_fm_app.py @@ -155,7 +155,7 @@ class SetupWizard(QDialog): buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) - buttons.accepted.connect(self.accept) + buttons.accepted.connect(self.validate_and_accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) @@ -192,6 +192,14 @@ class SetupWizard(QDialog): 'SELECTED_STATIONS': ','.join(selected_stations) } + def validate_and_accept(self): + """Validate configuration before accepting""" + if not self.journey_fm_checkbox.isChecked() and not self.spirit_fm_checkbox.isChecked(): + QMessageBox.warning(self, "Validation Error", + "Please select at least one station to monitor.") + return + self.accept() + class LogViewer(QWidget): """Widget for viewing application logs""" def __init__(self, parent=None): diff --git a/main.py b/main.py index d2ab5dd..df5e50b 100644 --- a/main.py +++ b/main.py @@ -326,8 +326,9 @@ def main(): PLAYLIST_NAME = config.get('PLAYLIST_NAME', PLAYLIST_NAME) # Load selected stations selected_stations_str = config.get('SELECTED_STATIONS', 'journey_fm,spirit_fm') - if isinstance(selected_stations_str, str): - selected_stations = selected_stations_str.split(',') + if isinstance(selected_stations_str, str) and selected_stations_str: + selected_stations = [s for s in selected_stations_str.split(',') if s] + # If empty or invalid, keep default else: # If no config file, use defaults (GUI should handle this) pass