import os import requests import io import datetime from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from PyPDF2 import PdfReader, PdfWriter import logging logger = logging.getLogger("qr-card-generator") def get_calendly_events_raw(api_token: str, start_time: str, end_time: str, event_type_name: str = None): """ Debug function to fetch raw Calendly data without formatting. """ headers = { 'Authorization': f'Bearer {api_token}', 'Content-Type': 'application/json' } # 1. Get current user info to get the user URI user_url = "https://api.calendly.com/users/me" user_response = requests.get(user_url, headers=headers) if not user_response.ok: raise Exception(f"Calendly API Error: {user_response.status_code}") user_data = user_response.json() user_uri = user_data['resource']['uri'] # 2. Get events for the user events_url = "https://api.calendly.com/scheduled_events" params = { 'user': user_uri, 'min_start_time': start_time, 'max_start_time': end_time, 'status': 'active' } events_response = requests.get(events_url, headers=headers, params=params) if not events_response.ok: raise Exception(f"Calendly API Error: {events_response.status_code}") events_data = events_response.json() events = events_data['collection'] raw_results = [] # 3. Get invitees for event in events: event_name = event.get('name', '') # Filter by event type if provided if event_type_name and event_type_name.lower() not in event_name.lower(): continue event_uri = event['uri'] event_uuid = event_uri.split('/')[-1] invitees_url = f"https://api.calendly.com/scheduled_events/{event_uuid}/invitees" invitees_response = requests.get(invitees_url, headers=headers) if not invitees_response.ok: continue invitees_data = invitees_response.json() for invitee in invitees_data['collection']: raw_results.append({ "event_name": event_name, "start_time": event['start_time'], "invitee_name": invitee['name'], "invitee_email": invitee['email'], "questions_and_answers": invitee.get('questions_and_answers', []) }) return raw_results def get_calendly_events(api_token: str, start_time: str, end_time: str, event_type_name: str = None): """ Fetches events from Calendly API for the current user within a time range. """ from zoneinfo import ZoneInfo raw_data = get_calendly_events_raw(api_token, start_time, end_time, event_type_name) formatted_data = [] for item in raw_data: # Parse start time from UTC start_dt = datetime.datetime.fromisoformat(item['start_time'].replace('Z', '+00:00')) # Convert to Europe/Berlin (CET/CEST) start_dt = start_dt.astimezone(ZoneInfo("Europe/Berlin")) # Format as HH:MM time_str = start_dt.strftime('%H:%M') name = item['invitee_name'] # Extract specific answers from the Calendly form num_children = "" additional_notes = "" has_consent = False questions_and_answers = item.get('questions_and_answers', []) for q_a in questions_and_answers: q_text = q_a.get('question', '').lower() a_text = q_a.get('answer', '') if "wie viele kinder" in q_text: num_children = a_text elif "nachricht" in q_text or "anmerkung" in q_text: additional_notes = a_text elif "schöne bilder" in q_text and "website veröffentlichen" in q_text: if "ja, gerne" in a_text.lower(): has_consent = True # Construct the final string: "[☑] Name, X Kinder // HH:MM Uhr (Notes)" # matching: Halime Türe, 1 Kind // 12:00 Uhr final_text = "" if has_consent: # We use a placeholder character or string that we will handle in overlay_text_on_pdf # because standard Helvetica doesn't support Unicode checkbox. final_text += "☑ " final_text += f"{name}" if num_children: final_text += f", {num_children}" final_text += f" // {time_str} Uhr" if additional_notes: final_text += f" ({additional_notes})" formatted_data.append(final_text) logger.info(f"Processed {len(formatted_data)} invitees.") return formatted_data def overlay_text_on_pdf(base_pdf_path: str, output_pdf_path: str, texts: list): # Target: # Element 1: X: 72mm, Y: 22mm + 9mm = 31mm # Element 2: X: 72mm, Y: 171mm + 9mm = 180mm """ # Convert mm to points (1 mm = 2.83465 points) mm_to_pt = 2.83465 # A4 dimensions in points (approx 595.27 x 841.89) page_width, page_height = A4 # User coordinates are from top-left. # ReportLab uses bottom-left as (0,0). # Element 1 (Top): X = 72mm, Y = 31mm (from top) -> Y = page_height - 31mm # Element 2 (Bottom): X = 72mm, Y = 180mm (from top) -> Y = page_height - 180mm x_pos = 72 * mm_to_pt y_pos_1 = page_height - (31 * mm_to_pt) y_pos_2 = page_height - (180 * mm_to_pt) reader = PdfReader(base_pdf_path) writer = PdfWriter() total_pages = len(reader.pages) max_capacity = total_pages * 2 if len(texts) > max_capacity: logger.warning(f"Not enough pages in base PDF. Have {len(texts)} invitees but only space for {max_capacity}. Truncating.") texts = texts[:max_capacity] # We need to process pairs of texts for each page text_pairs = [texts[i:i+2] for i in range(0, len(texts), 2)] for page_idx, pair in enumerate(text_pairs): if page_idx >= total_pages: break # Safety first # Create a new blank page in memory to draw the text packet = io.BytesIO() can = canvas.Canvas(packet, pagesize=A4) # Draw the text. # We handle the "☑" character manually since Helvetica might not support it. def draw_text_with_checkbox(can, x, y, text): current_x = x if text.startswith("☑ "): # Draw a checkbox manually size = 10 # Draw box (baseline adjustment to align with text) can.rect(x, y - 1, size, size) # Draw checkmark can.setLineWidth(1.5) can.line(x + 2, y + 3, x + 4.5, y + 0.5) can.line(x + 4.5, y + 0.5, x + 8.5, y + 7) can.setLineWidth(1) # Move text X position to the right current_x += size + 5 text = text[2:] # Remove the "☑ " from string can.setFont("Helvetica", 12) can.drawString(current_x, y, text) if len(pair) > 0: draw_text_with_checkbox(can, x_pos, y_pos_1, pair[0]) if len(pair) > 1: draw_text_with_checkbox(can, x_pos, y_pos_2, pair[1]) can.save() packet.seek(0) # Read the text PDF we just created new_pdf = PdfReader(packet) text_page = new_pdf.pages[0] # Get the specific page from the original PDF page_to_merge = reader.pages[page_idx] page_to_merge.merge_page(text_page) writer.add_page(page_to_merge) # If there are pages left in the base PDF that we didn't use, append them too? # Usually you'd want to keep them or discard them. We'll discard unused pages for now # to avoid empty cards, or you can change this loop to include them. with open(output_pdf_path, "wb") as output_file: writer.write(output_file) logger.info(f"Successfully generated overlaid PDF at {output_pdf_path}")