Skip to content

Instantly share code, notes, and snippets.

@6d61726b760a
Created October 2, 2025 11:10
Show Gist options
  • Select an option

  • Save 6d61726b760a/0aa22c9cdffaedd7029aa65b298854e9 to your computer and use it in GitHub Desktop.

Select an option

Save 6d61726b760a/0aa22c9cdffaedd7029aa65b298854e9 to your computer and use it in GitHub Desktop.
PCO Chat Maker - Automated Chat Creation for PCO Plans
#!/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