import os import requests import re import email_validator from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail from python_http_client.exceptions import HTTPError def fetch_pr_body(pr_url, github_token): print("🔍 Fetching PR body...") try: pr_resp = requests.get( pr_url, headers={"Authorization": f"Bearer {github_token}"} ) pr_resp.raise_for_status() except requests.exceptions.RequestException as e: print("❌ Failed to fetch PR body:", str(e)) raise return pr_resp.json() def extract_email_from_text(text): """Extract email from text using various patterns""" # Try PR template format: "**Email**: email@example.com" email_match = re.search(r"\*\*Email\*\*:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text) if email_match: return email_match.group(1) # Try other common email patterns email_match = re.search(r"[Ee]mail:\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})", text) if email_match: return email_match.group(1) # Try general email pattern email_match = re.search(r"\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b", text) if email_match: return email_match.group(1) return None def fetch_pr_comments(pr_url, github_token): """Fetch all comments on the PR""" # Convert PR URL to comments URL comments_url = pr_url.replace("/pulls/", "/issues/") + "/comments" try: comments_resp = requests.get( comments_url, headers={"Authorization": f"Bearer {github_token}"} ) comments_resp.raise_for_status() return comments_resp.json() except requests.exceptions.RequestException as e: print(f"⚠️ Failed to fetch PR comments: {e}") return [] def validate_email_address(email): """Validate email address format and deliverability""" try: # Validate and get normalized email valid_email = email_validator.validate_email(email) normalized_email = valid_email.email print(f"✅ Email validation passed: {normalized_email}") return normalized_email except email_validator.EmailNotValidError as e: print(f"❌ Email validation failed: {e}") return None def extract_email(pr_body, pr_url, github_token): """Extract and validate email from PR body and comments""" print("🔍 Searching for email in PR body...") # First check PR body email = extract_email_from_text(pr_body) if email: print(f"📧 Found email in PR body: {email}") validated_email = validate_email_address(email) if validated_email: return validated_email else: print("⚠️ Email in PR body is invalid, checking comments...") print("🔍 No valid email found in PR body, checking comments...") # Check PR comments comments = fetch_pr_comments(pr_url, github_token) for comment in comments: comment_body = comment.get("body", "") email = extract_email_from_text(comment_body) if email: print(f"📧 Found email in comment by {comment.get('user', {}).get('login', 'unknown')}: {email}") validated_email = validate_email_address(email) if validated_email: return validated_email else: print("⚠️ Email in comment is invalid, continuing search...") # No valid email found anywhere print("❌ No valid email found in PR body or comments. Skipping key issuance.") exit(0) def provision_api_key(provisioning_api_key): print("🔐 Creating OpenRouter key...") try: key_resp = requests.post( "https://openrouter.ai/api/v1/keys/", headers={ "Authorization": f"Bearer {provisioning_api_key}", "Content-Type": "application/json" }, json={ "name": "Goose Contributor", "label": "goose-cookbook", "limit": 10.0 } ) key_resp.raise_for_status() except requests.exceptions.RequestException as e: print("❌ Failed to provision API key:", str(e)) raise return key_resp.json()["key"] def send_email(email, api_key, sendgrid_api_key): print("📤 Sending email via SendGrid...") try: sg = SendGridAPIClient(sendgrid_api_key) from_email = "Goose Team " subject = "🎉 Your Goose Contributor API Key" html_content = f"""

Thanks for contributing to the Goose Recipe Cookbook!

Here's your $10 OpenRouter API key:

{api_key}

Happy vibe-coding!
– The Goose Team 🪿

""" message = Mail( from_email=from_email, to_emails=email, subject=subject, html_content=html_content ) response = sg.send(message) print(f"✅ Email sent successfully! Status code: {response.status_code}") # Check for potential issues even on "success" if response.status_code >= 300: print(f"⚠️ Warning: Unexpected status code {response.status_code}") print(f"Response body: {response.body}") return False return True except HTTPError as e: # Specific SendGrid HTTP errors status_code = e.status_code error_body = e.body if status_code == 401: print("❌ SendGrid authentication failed - invalid API key") elif status_code == 403: print("❌ SendGrid authorization failed - API key lacks permissions") elif status_code == 429: print("❌ SendGrid rate limit exceeded - too many requests") elif status_code == 400: print(f"❌ SendGrid bad request - invalid email data: {error_body}") elif status_code >= 500: print(f"❌ SendGrid server error ({status_code}) - try again later") else: print(f"❌ SendGrid HTTP error {status_code}: {error_body}") print(f"Full error details: {e}") return False except ValueError as e: print(f"❌ Invalid email format or API key: {e}") return False except Exception as e: print(f"❌ Unexpected error sending email: {type(e).__name__}: {e}") return False def comment_on_pr(github_token, repo_full_name, pr_number, email): print("💬 Commenting on PR...") comment_url = f"https://api.github.com/repos/{repo_full_name}/issues/{pr_number}/comments" try: comment_resp = requests.post( comment_url, headers={ "Authorization": f"Bearer {github_token}", "Accept": "application/vnd.github+json" }, json={ "body": f"✅ $10 OpenRouter API key sent to `{email}`. Thanks for your contribution to the Goose Cookbook!" } ) comment_resp.raise_for_status() print("✅ Confirmation comment added to PR.") except requests.exceptions.RequestException as e: print("❌ Failed to comment on PR:", str(e)) raise def main(): # Load environment variables GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] PR_URL = os.environ["GITHUB_API_URL"] PROVISIONING_API_KEY = os.environ["PROVISIONING_API_KEY"] SENDGRID_API_KEY = os.environ["EMAIL_API_KEY"] pr_data = fetch_pr_body(PR_URL, GITHUB_TOKEN) pr_body = pr_data.get("body", "") pr_number = pr_data["number"] repo_full_name = pr_data["base"]["repo"]["full_name"] email = extract_email(pr_body, PR_URL, GITHUB_TOKEN) print(f"📬 Found email: {email}") try: api_key = provision_api_key(PROVISIONING_API_KEY) print("✅ API key generated!") if send_email(email, api_key, SENDGRID_API_KEY): comment_on_pr(GITHUB_TOKEN, repo_full_name, pr_number, email) except Exception as err: print(f"❌ An error occurred: {err}") if __name__ == "__main__": main()