Skip to content

Add youtube#213

Closed
Arkteus wants to merge 1750 commits into
mainfrom
add-youtube
Closed

Add youtube#213
Arkteus wants to merge 1750 commits into
mainfrom
add-youtube

Conversation

@Arkteus

@Arkteus Arkteus commented Nov 2, 2025

Copy link
Copy Markdown
Contributor

This pull request introduces comprehensive support for Google push notifications (webhooks) for Gmail, Google Calendar, and YouTube, moving away from polling for these services. It adds new helper utilities for managing Google "watches," updates the Celery beat schedule to reflect webhook usage, and expands OAuth scopes and configuration options to support these features. Additionally, it includes improvements to Gmail and Calendar helpers for more flexible API usage.

Key changes:

Google Webhook (Push Notification) Support

  • Added a new module google_webhook_helper.py with functions to create, stop, and renew push notification "watches" for Gmail, Calendar, and YouTube, including PubSubHubbub support for YouTube. This enables the backend to receive real-time updates from Google services instead of relying on polling.
  • Introduced new configuration options in settings/base.py to enable/disable webhooks for Gmail, Calendar, and YouTube, specify webhook URLs, and set watch renewal intervals.
  • Added scheduled Celery tasks for renewing Google watches and ensuring YouTube subscriptions remain active. Polling tasks for Calendar and YouTube are now commented out to reflect webhook activation.
  • Updated logging in the Celery schedule to indicate when webhooks are active and polling is disabled for Google services.

OAuth and API Enhancements

  • Expanded Google OAuth scopes in settings/base.py to include YouTube read-only and force-ssl access, required for YouTube webhooks and API interactions.
  • Added BACKEND_URL configuration for webhook callback endpoints.

Helper Improvements

  • Enhanced the Calendar helper's list_upcoming_events function to accept a calendar_id parameter, supporting non-primary calendars. [1] [2] [3]
  • Added a new get_history function to the Gmail helper for retrieving changes since a specific history ID, supporting efficient processing of Gmail push notifications.

Other Changes

  • Removed the Twitch polling schedule from Celery, likely due to webhook activation or deprecation.

These changes collectively modernize the backend's integration with Google services, improving efficiency and responsiveness by leveraging push notifications instead of periodic polling.

Arkteus and others added 30 commits October 30, 2025 15:55
@Arkteus Arkteus requested a review from Copilot November 2, 2025 13:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds comprehensive YouTube integration to the automation platform, enabling users to trigger automations based on YouTube activity and perform actions on videos. The implementation includes webhook-based real-time notifications via YouTube PubSubHubbub, polling fallback, and three reaction types.

Key changes:

  • Added YouTube service with 3 actions (new video, channel stats, search videos) and 3 reactions (post comment, add to playlist, rate video)
  • Implemented YouTube PubSubHubbub webhook support for real-time notifications
  • Extended Google OAuth scopes to include YouTube API access
  • Created reusable Google webhook infrastructure supporting Gmail, Calendar, and YouTube

Reviewed Changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
mobile/lib/utils/service_token_mapper.dart Maps YouTube service to Google OAuth token
frontend/src/pages/ServiceDetail.tsx Adds YouTube to Google OAuth services and displays webhook banner
frontend/src/pages/Debug.tsx Simplifies external event trigger message
frontend/src/components/GoogleWebhookBanner.tsx New informational banner for Google webhook services
backend/users/oauth_views.py Auto-triggers Google webhook setup after OAuth connection
backend/scripts/test_google_webhooks.py New test script for webhook infrastructure validation
backend/automations/webhooks.py Adds YouTube PubSubHubbub verification and event ID extraction
backend/automations/validators.py Defines YouTube action/reaction schemas and compatibility rules
backend/automations/urls.py Registers dedicated webhook endpoints for Google services
backend/automations/tasks.py Implements YouTube polling, webhook management, and reaction execution
backend/automations/serializers.py Adds YouTube logo and OAuth mapping
backend/automations/models.py New GoogleWebhookWatch model for tracking push notification subscriptions
backend/automations/migrations/0012_google_webhook_watch.py Database migration for GoogleWebhookWatch model
backend/automations/management/commands/init_services.py Registers YouTube service with actions and reactions
backend/automations/helpers/youtube_helper.py YouTube Data API v3 helper functions
backend/automations/helpers/google_webhook_helper.py Google webhook creation and management utilities
backend/automations/helpers/gmail_helper.py Adds history API support for Gmail webhooks
backend/automations/helpers/calendar_helper.py Adds calendar_id parameter for multi-calendar support
backend/automations/google_webhook_views.py New dedicated webhook receivers for Gmail, Calendar, YouTube
backend/area_project/settings/base.py Adds YouTube OAuth scopes and webhook configuration
backend/area_project/celery.py Configures periodic tasks for webhook renewal
.env.example Minor formatting fix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +295 to +298

if video_id:
# Include published timestamp for uniqueness
return f"youtube_video_{video_id}_{published}"

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event ID generation includes the full published timestamp which can contain special characters (colons, plus signs) that may cause issues in IDs. The timestamp should be sanitized or hashed to ensure the event ID is URL-safe and database-friendly. Consider using published.replace(':', '').replace('+', '') or hashing the timestamp.

Suggested change
if video_id:
# Include published timestamp for uniqueness
return f"youtube_video_{video_id}_{published}"
# Sanitize published timestamp to remove problematic characters
sanitized_published = published.replace(':', '').replace('+', '')
if video_id:
# Include sanitized published timestamp for uniqueness
return f"youtube_video_{video_id}_{sanitized_published}"

Copilot uses AI. Check for mistakes.
return HttpResponse("OK", status=200)

# Process history changes
for history_item in history:

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code iterates over history directly but get_history() returns a dict with a 'history' key containing the list of changes. This should be for history_item in history.get('history', []): to properly access the history items.

Suggested change
for history_item in history:
for history_item in history.get('history', []):

Copilot uses AI. Check for mistakes.
"""
try:
# Parse XML (YouTube Atom feeds are trusted sources from youtube.com)
root = ET.fromstring(xml_string) # noqa: S314 # nosec B314

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the nosec comment indicates awareness of the security concern, using ET.fromstring() on untrusted XML can still be vulnerable to XML entity expansion attacks. Consider using defusedxml.ElementTree.fromstring() instead, which is specifically designed to prevent XML attacks, even from sources that might seem trusted.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +62
watch_request = {
"labelIds": ["INBOX"],
"topicName": webhook_url,
}

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gmail watch API expects 'topicName' to be a Cloud Pub/Sub topic (e.g., 'projects/myproject/topics/mytopic'), not an HTTP webhook URL. For direct HTTPS push endpoints, Gmail requires domain verification and the watch should be created without topicName or use the appropriate Pub/Sub integration. This configuration will likely fail when called.

Copilot uses AI. Check for mistakes.
"description": "Comment text (supports variables: {video_title}, {channel_name})",
},
},
"required": ["comment_text"],

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The youtube_post_comment reaction requires comment_text but not video_id, even though video_id is essential for posting a comment. While the code attempts to get video_id from trigger_data as a fallback, this should be explicitly required to prevent runtime errors when used with non-YouTube triggers. Consider adding 'video_id' to the required list or making the default value validation more robust.

Suggested change
"required": ["comment_text"],
"required": ["video_id", "comment_text"],

Copilot uses AI. Check for mistakes.
Comment on lines +3797 to +3803
video_id = reaction_config.get("video_id") or trigger_data.get("video_id")
comment_text = reaction_config.get("comment_text")

if not video_id or not comment_text:
raise ValueError(
"Video ID and comment text required for youtube_post_comment"
)

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation pattern is repeated identically across all three YouTube reactions (youtube_post_comment, youtube_add_to_playlist, youtube_rate_video). Consider extracting this into a helper function like get_video_id_from_config_or_trigger(reaction_config, trigger_data) to reduce code duplication and ensure consistent behavior.

Copilot uses AI. Check for mistakes.
]
constraints = [
models.UniqueConstraint(
fields=["user", "service", "resource_uri"],

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unique constraint includes resource_uri which has blank=True, default=''. This means multiple Gmail watches for the same user will all have an empty string for resource_uri, causing unique constraint violations. Consider either making resource_uri non-nullable with a meaningful default, or excluding it from the constraint and adding logic to prevent duplicate watches.

Suggested change
fields=["user", "service", "resource_uri"],
fields=["user", "service"],

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,299 @@
#!/usr/bin/env python3

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script uses #!/usr/bin/env python3 shebang but is documented to run with python scripts/test_google_webhooks.py. The shebang suggests it should be executable and run directly as ./scripts/test_google_webhooks.py. Either make the file executable (chmod +x) and update documentation, or remove the shebang line if it's meant to be run via python interpreter.

Suggested change
#!/usr/bin/env python3

Copilot uses AI. Check for mistakes.
user = areas[0].owner

# Save watch to database
watch, created = GoogleWebhookWatch.objects.update_or_create(

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable watch is not used.

Suggested change
watch, created = GoogleWebhookWatch.objects.update_or_create(
GoogleWebhookWatch.objects.update_or_create(

Copilot uses AI. Check for mistakes.
user = areas[0].owner

# Save watch to database
watch, created = GoogleWebhookWatch.objects.update_or_create(

Copilot AI Nov 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable created is not used.

Suggested change
watch, created = GoogleWebhookWatch.objects.update_or_create(
GoogleWebhookWatch.objects.update_or_create(

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants