Created
October 2, 2025 11:10
-
-
Save 6d61726b760a/0aa22c9cdffaedd7029aa65b298854e9 to your computer and use it in GitHub Desktop.
PCO Chat Maker - Automated Chat Creation for PCO Plans
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| PCO Chat Maker - Automated Chat Creation for PCO Plans | |
| This script automates the creation of Planning Center Online (PCO) chat conversations | |
| for upcoming church services. It identifies the next service plans for specified | |
| service types, extracts team information, and creates chat conversations for | |
| team coordination. | |
| Features: | |
| - Fetches upcoming service plans for multiple service types | |
| - Identifies team members for Band, Vocals, and Production teams | |
| - Creates chat conversations for team coordination | |
| - Automatically names conversations with service date and time | |
| - Posts welcome message to newly created conversations | |
| Prerequisites: | |
| - PCO Services API access credentials | |
| - .env file with PCO_CLIENT_ID and PCO_SECRET | |
| - pypco library installed | |
| - python-dotenv library installed | |
| Usage: | |
| python pco_chatmaker.py | |
| or if you are using uv (https://docs.astral.sh/uv/) (recommended!) | |
| uv venv | |
| uv pip install pypco python-dotenv | |
| uv run pco_chatmaker.py | |
| Environment Variables Required - using a .env file: | |
| PCO_CLIENT_ID: Your PCO application client ID | |
| PCO_SECRET: Your PCO application secret/personal access token | |
| generate a secret/personal access token from | |
| https://developer.planning.center/docs/#/introduction/authentication | |
| Author: MarkV | |
| Date: October 2025 | |
| """ | |
| import pypco | |
| from datetime import datetime, timedelta | |
| import os | |
| import sys | |
| import json | |
| import uuid | |
| from dotenv import load_dotenv | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| # Get credentials from environment variables | |
| client_id = os.getenv("PCO_CLIENT_ID") | |
| secret = os.getenv("PCO_SECRET") | |
| # Validate required environment variables | |
| if not client_id or not secret: | |
| print("β Error: couldnt find PCO_CLIENT_ID and PCO_SECRET variables") | |
| sys.exit(1) | |
| # Get an instance of the PCO object using credentials from .env | |
| pco = pypco.PCO(client_id, secret) | |
| def main(): | |
| """ | |
| Main function that processes service types and creates chat conversations. | |
| This function: | |
| 1. Iterates through predefined service type IDs (Sunday AM & PM) | |
| 2. Fetches service type details and upcoming plans | |
| 3. Identifies team members for each plan | |
| 4. Creates chat conversations for team coordination | |
| 5. Posts welcome messages to the conversations | |
| Service Types Processed: | |
| - 1569527: Sunday AM Service | |
| - 1569528: Sunday PM Service | |
| Teams Targeted: | |
| - Band Team | |
| - Vocals Team | |
| - Production Team | |
| Raises: | |
| PCORequestException: If API requests fail | |
| ValueError: If required team IDs cannot be found | |
| """ | |
| # These IDs are specific to NL BNE | |
| service_type_ids = ['1569527', '1569528'] # Sunday AM, Sunday PM | |
| for service_type_id in service_type_ids: | |
| print(f"Processing service type ID: {service_type_id}") | |
| # Fetch the service type details and folder details | |
| service_type = pco.get(f'/services/v2/service_types/{service_type_id}') | |
| attrs = service_type['data']['attributes'] | |
| rels = service_type['data']['relationships'] | |
| service_name = attrs['name'] | |
| # Extract folder information for better service identification | |
| folder_name = "No Folder" | |
| if 'parent' in rels and rels['parent']['data']: | |
| parent_id = rels['parent']['data']['id'] | |
| # Fetch parent details to get folder name (e.g., "Brisbane") | |
| parent = pco.get(f'/services/v2/folders/{parent_id}') | |
| folder_name = parent['data']['attributes'].get('name', 'No Folder') | |
| print(f" Service Type: [{folder_name}] {service_name}") | |
| # Get the next upcoming plan for this service type | |
| plans = pco.get(f'/services/v2/service_types/{service_type_id}/plans', | |
| filter='future', | |
| order='sort_date', | |
| per_page=1) | |
| if len(plans['data']) == 0: | |
| print(f"No upcoming plans found for service type ID {service_type_id}") | |
| continue | |
| next_plan = plans['data'][0] | |
| plan_id = next_plan['id'] | |
| plan_name = next_plan['attributes']['title'] | |
| plan_sort_date = next_plan['attributes']['sort_date'] | |
| plan_url = next_plan['attributes']['planning_center_url'] | |
| # Format the date nicely if it exists | |
| if plan_sort_date: | |
| date_obj = datetime.fromisoformat(plan_sort_date.replace('Z', '+00:00')) | |
| formatted_date = date_obj.strftime('%A, %B %d, %Y at %I:%M %p') | |
| else: | |
| formatted_date = "No date set" | |
| print(f" next plan: {plan_name} (id: {plan_id}, date: {formatted_date})") | |
| print(f" plan url: {plan_url}") | |
| # ======================================== | |
| # TEAM IDENTIFICATION SECTION | |
| # ======================================== | |
| # Extract team IDs for the target teams (Band, Vocals, Production) | |
| # We need these IDs to create team-specific chat conversations | |
| # Initialize team ID variables | |
| vocals_team_id = None | |
| production_team_id = None | |
| band_team_id = None | |
| # Get team members assigned to this specific plan | |
| # Use team_members endpoint because plans don't have direct team relationships | |
| plan_team_members = pco.get(f'/services/v2/plans/{plan_id}/team_members', include='team') | |
| if len(plan_team_members['data']) == 0: | |
| print(f"No team members found for plan ID {plan_id}") | |
| continue | |
| # Extract unique teams from the team member assignments | |
| # Each team member has a relationship to their team | |
| teams_found = {} | |
| for member in plan_team_members['data']: | |
| team_id = member['relationships']['team']['data']['id'] | |
| # Find team details in the included data section | |
| for included in plan_team_members.get('included', []): | |
| if included['type'] == 'Team' and included['id'] == team_id: | |
| team_name = included['attributes']['name'] | |
| teams_found[team_id] = team_name | |
| break | |
| print(f" Found {len(teams_found)} teams in plan:") | |
| for team_id, team_name in teams_found.items(): | |
| print(f" π΅ {team_name} (ID: {team_id})") | |
| # Match teams to our target categories using flexible name matching | |
| # This handles variations like "Vocals", "Vocal", "Vocals Team", etc. | |
| team_name_lower = team_name.lower() | |
| if team_name_lower in ['vocals', 'vocal', 'vocals team']: | |
| vocals_team_id = team_id | |
| elif team_name_lower in ['production', 'production team']: | |
| production_team_id = team_id | |
| elif team_name_lower in ['band', 'bands', 'band team']: | |
| band_team_id = team_id | |
| # Validate that we found all required teams | |
| if not vocals_team_id or not production_team_id or not band_team_id: | |
| print(f"β οΈ Could not find all required team IDs for plan ID {plan_id}") | |
| missing_teams = [] | |
| if not vocals_team_id: missing_teams.append("Vocals") | |
| if not production_team_id: missing_teams.append("Production") | |
| if not band_team_id: missing_teams.append("Band") | |
| print(f" Missing teams: {', '.join(missing_teams)}") | |
| continue | |
| print(f"β Successfully identified all target teams:") | |
| print(f" π€ Vocals: {vocals_team_id}") | |
| print(f" ποΈ Production: {production_team_id}") | |
| print(f" πΈ Band: {band_team_id}") | |
| # ======================================== | |
| # CHAT CONVERSATION CREATION SECTION | |
| # ======================================== | |
| # Create a chat conversation for team coordination using the PCO Chat API. | |
| # This process involves: | |
| # 1. Getting a chat token for the teams and plan | |
| # 2. Creating the conversation | |
| # 3. Setting a custom title with date/service info | |
| # 4. Posting a welcome message | |
| print(f"π Getting chat token for plan ID {plan_id}") | |
| # Step 1: Request chat token from PCO Services API | |
| # The token links the conversation to specific teams and plan | |
| params = { | |
| 'fields[Chat]': 'payload', | |
| 'team_id': f"{production_team_id},{vocals_team_id},{band_team_id}", | |
| 'plan_id': plan_id | |
| } | |
| try: | |
| chat_get_result = pco.get("/~api/services/v2/chat", **params) | |
| token = chat_get_result['data']['attributes']['payload'] | |
| print(f"β Successfully obtained chat token") | |
| except Exception as e: | |
| print(f"β Failed to get chat token: {e}") | |
| continue | |
| # Step 2: Create the conversation using the PCO Chat API | |
| # The payload contains the token that associates the conversation with teams/plan | |
| create_payload = pco.template('Chat', { | |
| 'payload': f"{token}", # Token from previous step | |
| }) | |
| print(f"π¬ Creating chat conversation for plan ID {plan_id}") | |
| try: | |
| chat_post_result = pco.post('/~api/chat/v2/me/conversations', payload=create_payload) | |
| conversation_id = chat_post_result['data']['id'] | |
| print(f"β Created conversation with ID: {conversation_id}") | |
| except Exception as e: | |
| print(f"β Failed to create conversation: {e}") | |
| continue | |
| # Step 3: Update conversation title with meaningful service information | |
| # Default title is usually just "Production" - we want something more descriptive | |
| print(f"π·οΈ Updating conversation title for ID {conversation_id}") | |
| # Generate a user-friendly title with date and service type | |
| # Format: "5 Oct 9 AM Worship" (concise but informative) | |
| conversation_title = date_obj.strftime('%-d %b %-I %p Worship') | |
| print(f" New title: '{conversation_title}'") | |
| # Prepare update payload with new title | |
| update_payload = { | |
| "data": { | |
| "type": "Conversation", | |
| "id": conversation_id, | |
| "attributes": { | |
| "title": conversation_title | |
| } | |
| } | |
| } | |
| try: | |
| update_result = pco.patch(f'/chat/v2/me/conversations/{conversation_id}', payload=update_payload) | |
| print(f"β Successfully updated conversation title") | |
| except Exception as e: | |
| print(f"β οΈ Failed to update title: {e} (conversation still created)") | |
| # Don't stop if title update fails - conversation is still usable | |
| # Step 4: Post welcome message to kick off team communication | |
| # This helps teams understand the purpose of the conversation | |
| print(f"π Posting welcome message to conversation {conversation_id}") | |
| message_body = { | |
| "data": { | |
| "type": "Message", | |
| "attributes": { | |
| # Clear, friendly message explaining the conversation's purpose | |
| "text": "Hi Team! This conversation has been created automagically to facilitate planning for this service!", | |
| "attachments": [], # No file attachments for this message | |
| "idempotent_key": str(uuid.uuid4()) # Prevents duplicate messages if retried | |
| } | |
| } | |
| } | |
| try: | |
| message_result = pco.post(f'/chat/v2/me/conversations/{conversation_id}/messages', payload=message_body) | |
| print(f"β Posted welcome message successfully") | |
| except Exception as e: | |
| print(f"β οΈ Failed to post welcome message: {e} (conversation still created)") | |
| # Don't stop if message posting fails - conversation is still usable | |
| # ======================================== | |
| # COMPLETION SUMMARY | |
| # ======================================== | |
| print(f"π Successfully created chat for {conversation_title}") | |
| print(f" Conversation ID: {conversation_id}") | |
| print(f" Teams included: Band, Vocals, Production") | |
| print(f" Plan: {plan_name}") | |
| # Add separator between service types for better readability | |
| if service_type_id != service_type_ids[-1]: # Not the last service type | |
| print("\n" + "="*60 + "\n") | |
| if __name__ == "__main__": | |
| """ | |
| Script entry point with error handling and logging. | |
| Executes the main function and provides user-friendly error messages | |
| for common failure scenarios. | |
| """ | |
| try: | |
| print("π PCO Chat Maker Starting...") | |
| print("=" * 50) | |
| main() | |
| print("=" * 50) | |
| print("β PCO Chat Maker Completed Successfully!") | |
| except KeyboardInterrupt: | |
| print("\nβ οΈ Process interrupted by user") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"\nβ Error occurred: {e}") | |
| print("\nTroubleshooting Tips:") | |
| print("1. Check your .env file has PCO_CLIENT_ID and PCO_SECRET") | |
| print("2. Verify your PCO API credentials are valid") | |
| print("3. Ensure you have internet connectivity") | |
| print("4. Check that the service type IDs are correct") | |
| sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment