
- Fixed Sabbath School parser to handle API data with extra line breaks - Cleaned up project structure and removed nested directories - Organized output to single directory structure - Removed YouTube link from contact section for cleaner layout - Improved parser robustness for multi-line content between labels - Added proper .gitignore for Rust project
674 lines
29 KiB
Python
674 lines
29 KiB
Python
#!/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"<p>[Could not parse scripture reference: {scripture_ref}]</p><p>{scripture_ref} KJV</p>"
|
|
|
|
# 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"<p>[Verse {verse} not found]</p><p>{scripture_ref} KJV</p>"
|
|
return f"<p>{scripture_text}</p><p>{scripture_ref} KJV</p>"
|
|
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"<p>[Verse {verse} not found]</p><p>{scripture_ref} KJV</p>"
|
|
# Return with HTML formatting
|
|
return f"<p>{scripture_text}</p><p>{scripture_ref} KJV</p>"
|
|
|
|
return f"<p>[Scripture not found: {scripture_ref}]</p><p>{scripture_ref} KJV</p>"
|
|
|
|
except FileNotFoundError:
|
|
return f"<p>[KJV.json file not found]</p><p>{scripture_ref} KJV</p>"
|
|
except Exception as e:
|
|
return f"<p>[Error reading scripture: {e}]</p><p>{scripture_ref} KJV</p>"
|
|
"""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:<br>
|
|
{song_leader}<br><br>
|
|
|
|
Lesson Study:<br>
|
|
{ss_teacher}<br><br>
|
|
|
|
Leadership:<br>
|
|
{ss_leader}<br><br>
|
|
|
|
Mission Story:<br>
|
|
{mission_story}<br><br>
|
|
|
|
Closing Hymn:<br>
|
|
{ss_leader}"""
|
|
|
|
# Format Divine Worship as HTML with br tags for tighter spacing
|
|
divine_worship_info = f"""Announcements:<br>
|
|
{announcements_person}<br><br>
|
|
|
|
Call To Worship:<br>
|
|
{responsive_reading}<br><br>
|
|
|
|
Opening Hymn:<br>
|
|
{opening_song}<br><br>
|
|
|
|
Prayer & Praises:<br>
|
|
{announcements_person}<br><br>
|
|
|
|
Prayer Song:<br>
|
|
#671 "As We Come To You in Prayer"<br><br>
|
|
|
|
Offering:<br>
|
|
{bulletin_data.get('offering_focus', 'Local Church Budget')}<br>
|
|
{offering_person}<br><br>
|
|
|
|
Children's Story:<br>
|
|
{childrens_story_person}<br><br>
|
|
|
|
Special Music:<br>
|
|
{special_music}<br><br>
|
|
|
|
Scripture Reading:<br>
|
|
{scripture_reading}<br>
|
|
{scripture_reader}<br><br>
|
|
|
|
Sermon:<br>
|
|
{parsed_sermon.get('sermon_title', 'TBA')}<br><br>
|
|
|
|
{speaker}<br><br>
|
|
|
|
Closing Hymn:<br>
|
|
{closing_song}"""
|
|
|
|
# Scripture reading field - get actual text from KJV.json
|
|
scripture_full_text = get_scripture_text(scripture_reading) if scripture_reading != 'TBA' else f"TBA\n\n{scripture_reading}"
|
|
|
|
# Create PocketBase record
|
|
api_url = f"{POCKETBASE_URL}/api/collections/bulletins/records"
|
|
headers = {'Authorization': token}
|
|
|
|
record_data = {
|
|
"date": f"{bulletin_data.get('date')} 00:00:00",
|
|
"title": parsed_sermon.get('sermon_title', 'Saturday Worship Service'), # Proper Saturday default
|
|
"url": "",
|
|
"pdf_url": "",
|
|
"is_active": True,
|
|
"sabbath_school": sabbath_school_info,
|
|
"divine_worship": divine_worship_info,
|
|
"scripture_reading": scripture_full_text,
|
|
"sunset": f"Sunset Tonight: {bulletin_data.get('sunset_time') or 'TBA'}\n\nSunset Next Friday: {bulletin_data.get('next_friday_sunset') or 'TBA'}"
|
|
}
|
|
|
|
print(f"Creating bulletin: {record_data['title']}")
|
|
response = requests.post(api_url, headers=headers, json=record_data)
|
|
|
|
if response.status_code in [200, 201]:
|
|
result = response.json()
|
|
print(f"SUCCESS! Created bulletin ID: {result.get('id')}")
|
|
return True
|
|
else:
|
|
print(f"FAILED! Status: {response.status_code}")
|
|
print(f"Error: {response.text}")
|
|
return False
|
|
|
|
def main():
|
|
target_saturday = get_upcoming_saturday()
|
|
target_saturday_str = target_saturday.strftime("%Y-%m-%d")
|
|
print(f"--- Creating bulletin for Saturday: {target_saturday_str} ---")
|
|
|
|
# Get file paths
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
project_root = os.path.dirname(script_dir)
|
|
schedule_csv_file = os.path.join(project_root, "Quarterly schedule2021 - 2025.csv")
|
|
|
|
# Get sermon details from user
|
|
print("\n--- Enter Sermon Details ---")
|
|
print("Paste sermon details and press Enter twice when done:")
|
|
sermon_lines = []
|
|
empty_line_count = 0
|
|
|
|
while True:
|
|
try:
|
|
line = input()
|
|
if not line.strip():
|
|
empty_line_count += 1
|
|
if empty_line_count >= 2:
|
|
break
|
|
else:
|
|
empty_line_count = 0
|
|
sermon_lines.append(line)
|
|
except EOFError:
|
|
break
|
|
|
|
sermon_text = "\n".join(sermon_lines).strip()
|
|
if not sermon_text:
|
|
print("No sermon details provided. Exiting.")
|
|
return
|
|
|
|
parsed_sermon = parse_sermon_details_from_text(sermon_text)
|
|
|
|
# Parse CSV
|
|
print(f"\nReading schedule from CSV...")
|
|
weekly_schedule = parse_schedule_csv(schedule_csv_file, target_saturday)
|
|
if not weekly_schedule:
|
|
print("Could not get weekly schedule. Exiting.")
|
|
return
|
|
|
|
# Parse conference chart for offering and sunset
|
|
conference_chart_file = os.path.join(project_root, "2025 Offering and Sunset Times Chart.txt")
|
|
print(f"\nReading conference chart from: {conference_chart_file}")
|
|
|
|
# Use Springfield as default location (you can change this)
|
|
sunset_location = "springfield"
|
|
conference_data = parse_conference_chart_txt(conference_chart_file, target_saturday, sunset_location)
|
|
|
|
# Get next Friday's sunset too
|
|
next_friday = target_saturday + datetime.timedelta(days=6) # 6 days after Saturday = next Friday
|
|
# But sunset chart only has Saturdays, so get the closest Saturday (June 21)
|
|
next_saturday = target_saturday + datetime.timedelta(days=7) # Next Saturday
|
|
next_friday_data = parse_conference_chart_txt(conference_chart_file, next_saturday, sunset_location)
|
|
|
|
print(f"Found conference data for {target_saturday}: {conference_data}")
|
|
print(f"Found conference data for next Saturday {next_saturday} (for Friday sunset): {next_friday_data}")
|
|
|
|
# If next Saturday data failed, try to get it manually from the chart
|
|
if not next_friday_data.get('sunset_time') and conference_data.get('sunset_time'):
|
|
# Use a slightly later time as approximation
|
|
current_time = conference_data.get('sunset_time', '8:29 pm')
|
|
if ':' in current_time:
|
|
time_part = current_time.replace(' pm', '').replace(' am', '')
|
|
hour, minute = map(int, time_part.split(':'))
|
|
# Add a few minutes for next Friday
|
|
minute += 2
|
|
if minute >= 60:
|
|
hour += 1
|
|
minute -= 60
|
|
next_friday_data['sunset_time'] = f"{hour}:{minute:02d} pm"
|
|
print(f"Approximated next Friday sunset: {next_friday_data['sunset_time']}")
|
|
|
|
# Create bulletin data
|
|
bulletin_data = {
|
|
"date": target_saturday_str,
|
|
"title": parsed_sermon.get("sermon_title", "Saturday Worship Service"), # Use proper Saturday default
|
|
"offering_focus": conference_data.get("offering_focus"),
|
|
"sunset_time": conference_data.get("sunset_time"),
|
|
"next_friday_sunset": next_friday_data.get("sunset_time"),
|
|
"cover_image_path": None
|
|
}
|
|
|
|
# Create events data
|
|
events_data = []
|
|
|
|
# Sabbath School
|
|
ss_desc_parts = []
|
|
if weekly_schedule.get("S. S. LEADER"): ss_desc_parts.append(f"Leader: {weekly_schedule['S. S. LEADER']}")
|
|
if weekly_schedule.get("S. S. TEACHER"): ss_desc_parts.append(f"Teacher: {weekly_schedule['S. S. TEACHER']}")
|
|
if weekly_schedule.get("MISSION STORY"): ss_desc_parts.append(f"Mission Story: {weekly_schedule['MISSION STORY']}")
|
|
|
|
events_data.append({
|
|
"title": "Sabbath School",
|
|
"description": ", ".join(ss_desc_parts),
|
|
"start_time": f"{target_saturday_str} {DEFAULT_SABBATH_SCHOOL_START_TIME}",
|
|
"end_time": f"{target_saturday_str} {DEFAULT_SABBATH_SCHOOL_END_TIME}",
|
|
"type": "recurring"
|
|
})
|
|
|
|
# Divine Worship
|
|
dw_desc_parts = []
|
|
if weekly_schedule.get("SERMON SPEAKER"): dw_desc_parts.append(f"Speaker: {weekly_schedule['SERMON SPEAKER']}")
|
|
if weekly_schedule.get("Song LEADER"): dw_desc_parts.append(f"Song Leader: {weekly_schedule['Song LEADER']}")
|
|
if weekly_schedule.get("SCRIPTURE"):
|
|
dw_desc_parts.append(f"Announcements: {weekly_schedule['SCRIPTURE']}")
|
|
dw_desc_parts.append(f"Prayer & Praises: {weekly_schedule['SCRIPTURE']}")
|
|
dw_desc_parts.append(f"Scripture Reading: {weekly_schedule['SCRIPTURE']} ({parsed_sermon.get('scripture_reading', 'TBA')})")
|
|
if weekly_schedule.get("OFFERING"): dw_desc_parts.append(f"Offering: {weekly_schedule['OFFERING']}")
|
|
if parsed_sermon.get("opening_song"): dw_desc_parts.append(f"Opening Song: {parsed_sermon['opening_song']}")
|
|
if parsed_sermon.get("closing_song"): dw_desc_parts.append(f"Closing Song: {parsed_sermon['closing_song']}")
|
|
if weekly_schedule.get("SPECIAL MUSIC"): dw_desc_parts.append(f"Special Music: {weekly_schedule['SPECIAL MUSIC']}")
|
|
|
|
events_data.append({
|
|
"title": "Divine Worship",
|
|
"description": ". ".join(dw_desc_parts),
|
|
"start_time": f"{target_saturday_str} {DEFAULT_DIVINE_WORSHIP_START_TIME}",
|
|
"end_time": f"{target_saturday_str} {DEFAULT_DIVINE_WORSHIP_END_TIME}",
|
|
"type": "recurring"
|
|
})
|
|
|
|
# Create in PocketBase
|
|
print("\n--- Creating bulletin in PocketBase ---")
|
|
if create_bulletin_in_pocketbase(bulletin_data, events_data, weekly_schedule, parsed_sermon):
|
|
print("\nDone! Check your PocketBase - bulletin created successfully!")
|
|
else:
|
|
print("\nFailed to create bulletin.")
|
|
|
|
if __name__ == "__main__":
|
|
main() |