#!/usr/bin/env python3 import csv import datetime import json import os import re import requests import getpass from typing import Dict, List, Optional # PocketBase config POCKETBASE_URL = "https://pocketbase.rockvilletollandsda.church" ADMIN_EMAIL = "av@rockvilletollandsda.org" # Get password from environment or prompt ADMIN_PASSWORD = os.environ.get('POCKETBASE_PASSWORD') if not ADMIN_PASSWORD: ADMIN_PASSWORD = getpass.getpass("Enter PocketBase admin password: ") # Default times DEFAULT_SABBATH_SCHOOL_START_TIME = "09:30:00" DEFAULT_SABBATH_SCHOOL_END_TIME = "10:45:00" DEFAULT_DIVINE_WORSHIP_START_TIME = "11:00:00" DEFAULT_DIVINE_WORSHIP_END_TIME = "12:30:00" def get_upcoming_saturday() -> datetime.date: """Get next Saturday date""" today = datetime.date.today() days_until_saturday = (5 - today.weekday() + 7) % 7 return today + datetime.timedelta(days=days_until_saturday) def parse_sermon_details_from_text(text: str) -> Dict[str, str]: """Parse sermon details from text input""" details = {} print(f"DEBUG: Raw sermon input text: '{text}'") # Your exact input format: # Sermon Title: "A Revelation of Jesus Christ" Scripture Reading: Revelation 1:8 # Opening Song: 623 - I Will Follow Thee Closing Song: 330 - Take My Life and Let It Be # Responsive Reading: 800 - Union with Christ # Fixed regex patterns for your exact format patterns = [ (r'Sermon Title:\s*"([^"]+)"', 'sermon_title'), (r'Scripture Reading:\s*([^A-Za-z]*[A-Za-z][^A-Za-z0-9]*[0-9:]+[^A-Za-z]*)', 'scripture_reading'), (r'Opening Song:\s*([0-9]+\s*-[^C]*?)(?=\s*Closing Song)', 'opening_song'), (r'Closing Song:\s*([0-9]+\s*-[^R]*?)(?=\s*Responsive Reading)', 'closing_song'), (r'Responsive Reading:\s*([0-9]+\s*-[^$]*)', 'responsive_reading') ] # Simpler approach - split by keywords if 'Sermon Title:' in text: # Look for Sermon Title: "anything in quotes" - handle smart quotes too title_match = re.search(r'Sermon Title:\s*["\'""]([^"\'""]+)["\'""]', text) if title_match: details['sermon_title'] = title_match.group(1).strip() print(f"DEBUG: Found sermon_title: '{details['sermon_title']}'") else: # Try without quotes title_match = re.search(r'Sermon Title:\s*([^S]+?)(?=\s+Scripture Reading)', text) if title_match: details['sermon_title'] = title_match.group(1).strip().strip('"').strip('"').strip('"') print(f"DEBUG: Found sermon_title (no quotes): '{details['sermon_title']}'") else: print(f"DEBUG: Sermon Title found in text but regex failed") print(f"DEBUG: Text around Sermon Title: '{text[text.find('Sermon Title:'):text.find('Sermon Title:')+100]}'") else: print(f"DEBUG: 'Sermon Title:' not found in text") if 'Scripture Reading:' in text: scripture_match = re.search(r'Scripture Reading:\s*([^\n\r]+?)(?=\s+Opening Song|$)', text) if scripture_match: details['scripture_reading'] = scripture_match.group(1).strip() print(f"DEBUG: Found scripture_reading: '{details['scripture_reading']}'") if 'Opening Song:' in text: opening_match = re.search(r'Opening Song:\s*([^\n\r]+?)(?=\s+Closing Song|$)', text) if opening_match: song = opening_match.group(1).strip() # Format as #XXX "Title" if ' - ' in song: num, title = song.split(' - ', 1) details['opening_song'] = f"#{num.strip()} \"{title.strip()}\"" else: details['opening_song'] = song print(f"DEBUG: Found opening_song: '{details['opening_song']}'") if 'Closing Song:' in text: closing_match = re.search(r'Closing Song:\s*([^\n\r]+?)(?=\s+Responsive Reading|$)', text) if closing_match: song = closing_match.group(1).strip() # Format as #XXX "Title" if ' - ' in song: num, title = song.split(' - ', 1) details['closing_song'] = f"#{num.strip()} \"{title.strip()}\"" else: details['closing_song'] = song print(f"DEBUG: Found closing_song: '{details['closing_song']}'") if 'Responsive Reading:' in text: responsive_match = re.search(r'Responsive Reading:\s*([^\n\r]+?)(?=\s*$)', text) if responsive_match: reading = responsive_match.group(1).strip() # Format as Hymnal XXX "Title" if ' - ' in reading: num, title = reading.split(' - ', 1) details['responsive_reading'] = f"Hymnal {num.strip()} \"{title.strip()}\"" else: details['responsive_reading'] = reading print(f"DEBUG: Found responsive_reading: '{details['responsive_reading']}'") return details def parse_schedule_csv(csv_filepath: str, target_date: datetime.date) -> Optional[Dict[str, str]]: """Parse CSV for target date schedule""" try: with open(csv_filepath, 'r', encoding='utf-8') as f: reader = csv.reader(f) lines = list(reader) except FileNotFoundError: print(f"ERROR: Schedule CSV file not found at {csv_filepath}") return None for i, row in enumerate(lines): if not any(row): continue if "DATE" in row and "SERMON" in row: header = [cell.replace("\\n", " ").strip() for cell in row] sub_header = [] if i + 1 < len(lines): sub_header = [cell.replace("\\n", " ").strip() for cell in lines[i+1]] # Look for data rows after this header for j in range(i+2, len(lines)): data_row = lines[j] if not data_row or not data_row[0].strip(): continue date_str = data_row[0].replace("\\n", " ").strip() try: day_str, month_str = date_str.split() month_num = datetime.datetime.strptime(month_str.strip(), "%b").month current_row_date = datetime.date(target_date.year, month_num, int(day_str.strip())) except ValueError: continue if current_row_date == target_date: combined_header = [] for idx, h in enumerate(header): sh = sub_header[idx] if idx < len(sub_header) else "" if sh and sh not in h: combined_header.append(f"{h} {sh}".strip()) else: combined_header.append(h.strip()) values = [data_row[k].replace("\\n", " ").strip() if k < len(data_row) else "" for k in range(len(combined_header))] return dict(zip(combined_header, values)) return None def parse_scripture_reference(ref: str) -> tuple: """Parse scripture reference like 'Revelation 1:8' into book, chapter, verse""" try: # Handle cases like "Revelation 1:8" or "1 John 2:3-5" parts = ref.strip().split() # Find where the chapter:verse part starts chapter_verse_idx = -1 for i, part in enumerate(parts): if ':' in part: chapter_verse_idx = i break if chapter_verse_idx == -1: return None, None, None # Book name is everything before the chapter:verse book = ' '.join(parts[:chapter_verse_idx]) # Parse chapter:verse chapter_verse = parts[chapter_verse_idx] if ':' in chapter_verse: chapter, verse_part = chapter_verse.split(':', 1) # Handle verse ranges like "8-10" if '-' in verse_part: start_verse, end_verse = verse_part.split('-', 1) return book, int(chapter), (int(start_verse), int(end_verse)) else: return book, int(chapter), int(verse_part) return None, None, None except: return None, None, None def get_scripture_text(scripture_ref: str) -> str: """Get scripture text from KJV.json file""" try: kjv_path = os.path.join(os.path.dirname(__file__), "KJV.json") with open(kjv_path, 'r') as f: kjv_data = json.load(f) book, chapter, verse = parse_scripture_reference(scripture_ref) if not book or not chapter or not verse: return f"
[Could not parse scripture reference: {scripture_ref}]
{scripture_ref} KJV
" # KJV.json structure might be different - let's handle both formats if isinstance(kjv_data, dict): # Format: {"Genesis": {"1": ["In the beginning...", "And the earth..."]}} book_data = kjv_data.get(book) if book_data and str(chapter) in book_data: verses = book_data[str(chapter)] if isinstance(verse, tuple): start_verse, end_verse = verse selected_verses = verses[start_verse-1:end_verse] scripture_text = ' '.join(selected_verses) else: if verse <= len(verses): scripture_text = verses[verse-1] # Remove reference from beginning if it exists (like "Revelation 1:8 ") scripture_text = re.sub(r'^[A-Za-z0-9\s]+\s+\d+:\d+\s+', '', scripture_text).strip() else: return f"[Verse {verse} not found]
{scripture_ref} KJV
" return f"{scripture_text}
{scripture_ref} KJV
" else: # Format: [{"name": "Genesis", "chapters": [["In the beginning...", "And the earth..."]]}] for book_entry in kjv_data: if book_entry.get('name', '').lower() == book.lower(): chapters = book_entry.get('chapters', []) if chapter <= len(chapters): chapter_verses = chapters[chapter - 1] if isinstance(verse, tuple): start_verse, end_verse = verse verses_text = [] for v in range(start_verse, end_verse + 1): if v <= len(chapter_verses): verses_text.append(chapter_verses[v - 1]) scripture_text = ' '.join(verses_text) else: if verse <= len(chapter_verses): scripture_text = chapter_verses[verse - 1] # Remove reference from beginning if it exists (like "Revelation 1:8 ") scripture_text = re.sub(r'^[A-Za-z0-9\s]+\s+\d+:\d+\s+', '', scripture_text).strip() else: return f"[Verse {verse} not found]
{scripture_ref} KJV
" # Return with HTML formatting return f"{scripture_text}
{scripture_ref} KJV
" return f"[Scripture not found: {scripture_ref}]
{scripture_ref} KJV
" except FileNotFoundError: return f"[KJV.json file not found]
{scripture_ref} KJV
" except Exception as e: return f"[Error reading scripture: {e}]
{scripture_ref} KJV
" """Authenticate with PocketBase""" auth_url = f"{POCKETBASE_URL}/api/collections/_superusers/auth-with-password" auth_data = { 'identity': ADMIN_EMAIL, 'password': ADMIN_PASSWORD } response = requests.post(auth_url, json=auth_data) response.raise_for_status() return response.json().get('token') def parse_conference_chart_txt(txt_filepath: str, target_date: datetime.date, location: str) -> Dict[str, Optional[str]]: """Parse the conference chart TXT for offering and sunset time.""" try: with open(txt_filepath, 'r', encoding='utf-8') as f: lines = f.readlines() except FileNotFoundError: print(f"ERROR: Conference chart TXT file not found at {txt_filepath}") return {"offering_focus": None, "sunset_time": None} except Exception as e: print(f"ERROR: Could not read conference chart TXT file: {e}") return {"offering_focus": None, "sunset_time": None} # Mapping of locations to column indices (Springfield is index 1) location_columns = { "south lancaster": 0, "springfield": 1, "stoneham": 2, "bridgeport": 3, "new haven": 4, "new london": 5, "providence": 6, } location_col_idx = location_columns.get(location.lower()) if location_col_idx is None: print(f"ERROR: Location '{location}' not found. Using Springfield as default.") location_col_idx = 1 # Springfield target_data = {"offering_focus": None, "sunset_time": None} current_month_str = None print(f"DEBUG: Looking for date {target_date}") for i, line in enumerate(lines): line = line.strip() if not line: continue # Look for month headers like "June 7" month_match = re.match(r"^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d+)", line, re.IGNORECASE) if month_match: current_month_str = month_match.group(1) print(f"DEBUG: Found month: {current_month_str}") continue # Look for day lines like "14. Women's Ministries 8:26 8:29..." or "21 Local Church Budget 8:29 8:31..." if current_month_str: day_match = re.match(r"^(\d+)\.?\s+(.+)", line) if day_match: day_str = day_match.group(1) rest_of_line = day_match.group(2).strip() # Split into offering and times - look for where times start (first time pattern) time_start_match = re.search(r'\d:\d{2}', rest_of_line) if time_start_match: time_start_pos = time_start_match.start() offering_str = rest_of_line[:time_start_pos].strip() times_str = rest_of_line[time_start_pos:].strip() else: offering_str = rest_of_line times_str = "" try: month_num = datetime.datetime.strptime(current_month_str, "%B").month current_line_date = datetime.date(target_date.year, month_num, int(day_str)) print(f"DEBUG: Checking date {current_line_date} vs target {target_date}") if current_line_date == target_date: print(f"DEBUG: MATCH! Found line: '{line}'") print(f"DEBUG: Offering: '{offering_str}'") print(f"DEBUG: Times: '{times_str}'") target_data["offering_focus"] = offering_str # Extract times - look for patterns like "8:26" times = re.findall(r'\d:\d{2}', times_str) print(f"DEBUG: Extracted times: {times}") if location_col_idx < len(times): sunset_time = times[location_col_idx] target_data["sunset_time"] = f"{sunset_time} pm" print(f"DEBUG: {location} sunset: {target_data['sunset_time']}") return target_data except ValueError as e: print(f"DEBUG: Could not parse date from {current_month_str} {day_str}: {e}") continue print(f"ERROR: Could not find data for date {target_date.strftime('%Y-%m-%d')} in conference chart") return target_data def authenticate_pocketbase(): """Authenticate with PocketBase""" auth_url = f"{POCKETBASE_URL}/api/collections/_superusers/auth-with-password" auth_data = { 'identity': ADMIN_EMAIL, 'password': ADMIN_PASSWORD } response = requests.post(auth_url, json=auth_data) response.raise_for_status() return response.json().get('token') def create_bulletin_in_pocketbase(bulletin_data, events_data, weekly_schedule, parsed_sermon): """Create bulletin record in PocketBase""" print("Authenticating with PocketBase...") token = authenticate_pocketbase() # Extract data for PocketBase fields sabbath_school_info = "" divine_worship_info = "" scripture_reading = "" # Extract people from weekly schedule and prompt for missing values ss_leader = weekly_schedule.get("S. S. LEADER", "").strip() if not ss_leader: ss_leader = input("S.S. Leader not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" ss_teacher = weekly_schedule.get("S. S. TEACHER", "").strip() if not ss_teacher: ss_teacher = input("S.S. Teacher not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" mission_story = weekly_schedule.get("MISSION STORY", "").strip() if not mission_story: mission_story = input("Mission Story person not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" song_leader = weekly_schedule.get("Song LEADER", "").strip() if not song_leader: song_leader = input("Song Leader not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" announcements_person = weekly_schedule.get("SCRIPTURE", "").strip() if not announcements_person: announcements_person = input("Announcements person not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" offering_person = weekly_schedule.get("OFFERING", "").strip() if not offering_person: offering_person = input("Offering person not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" special_music = weekly_schedule.get("SPECIAL MUSIC", "").strip() if not special_music: special_music = input("Special Music person not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" speaker = weekly_schedule.get("SERMON SPEAKER", "").strip() if not speaker: speaker = input("Sermon Speaker not found in CSV. Enter name (or press Enter for TBA): ").strip() or "TBA" print(f"DEBUG: Parsed sermon details: {parsed_sermon}") print(f"DEBUG: Sermon title from parsing: '{parsed_sermon.get('sermon_title', 'NOT FOUND')}'") # Pastor Joseph Piresson special logic is_pastor_speaking = "pastor joseph piresson" in speaker.lower() # If Pastor is speaking, he also does scripture reading and children's story if is_pastor_speaking: print("Pastor Joseph Piresson is speaking - assigning him scripture reading and children's story") scripture_reader = speaker childrens_story_person = speaker else: scripture_reader = announcements_person childrens_story_person = announcements_person opening_song = parsed_sermon.get('opening_song', '').strip() if not opening_song: opening_song = input("Opening Song not found in sermon details. Enter song (or press Enter for TBA): ").strip() or "TBA" closing_song = parsed_sermon.get('closing_song', '').strip() if not closing_song: closing_song = input("Closing Song not found in sermon details. Enter song (or press Enter for TBA): ").strip() or "TBA" scripture_reading = parsed_sermon.get('scripture_reading', '').strip() if not scripture_reading: scripture_reading = input("Scripture Reading not found in sermon details. Enter reference (or press Enter for TBA): ").strip() or "TBA" responsive_reading = parsed_sermon.get('responsive_reading', '').strip() if not responsive_reading: responsive_reading = input("Responsive Reading not found in sermon details. Enter reading (or press Enter for TBA): ").strip() or "TBA" print(f"DEBUG: SS Leader: {ss_leader}") print(f"DEBUG: SS Teacher: {ss_teacher}") print(f"DEBUG: Mission Story: {mission_story}") print(f"DEBUG: Song Leader: {song_leader}") print(f"DEBUG: Announcements: {announcements_person}") print(f"DEBUG: Offering: {offering_person}") print(f"DEBUG: Special Music: {special_music}") print(f"DEBUG: Speaker: {speaker}") print(f"DEBUG: Opening Song: {parsed_sermon.get('opening_song', 'TBA')}") print(f"DEBUG: Closing Song: {parsed_sermon.get('closing_song', 'TBA')}") print(f"DEBUG: Scripture: {parsed_sermon.get('scripture_reading', 'TBA')}") # Check what keys are actually in the weekly_schedule print(f"DEBUG: Weekly schedule keys: {list(weekly_schedule.keys())}") print(f"DEBUG: Parsed sermon keys: {list(parsed_sermon.keys())}") # Format Sabbath School as HTML with br tags for tighter spacing sabbath_school_info = f"""Song Service: