| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- # -*- coding: utf-8 -*-
- # Part of Odoo. See LICENSE file for full copyright and licensing details.
- import logging
- from odoo import models, _
- from odoo.exceptions import UserError
- _logger = logging.getLogger(__name__)
- class GoogleCalendarService(models.AbstractModel):
- _name = 'google.calendar.service'
- _description = 'Google Calendar Service'
- def _get_credentials(self, user=None):
- """Obtener credenciales de configuración para el usuario especificado"""
- if not user:
- user = self.env.user
-
- config = self.env['ir.config_parameter'].sudo()
-
- enabled = config.get_param('google_api.enabled', 'False')
- if not enabled or enabled == 'False':
- raise UserError(_('Google API Integration is not enabled'))
-
- client_id = config.get_param('google_api.client_id', '')
- client_secret = config.get_param('google_api.client_secret', '')
-
- if not client_id or not client_secret:
- raise UserError(_('Google API credentials not configured'))
-
- # Get access token from user settings
- user_settings = user.res_users_settings_id
- if not user_settings or not user_settings._google_authenticated():
- raise UserError(_('User %s is not authenticated with Google. Please connect your account first.') % user.name)
-
- access_token = user_settings._get_google_access_token()
- if not access_token:
- raise UserError(_('Could not obtain valid access token for user %s. Please reconnect your Google account.') % user.name)
-
- return {
- 'client_id': client_id,
- 'client_secret': client_secret,
- 'access_token': access_token
- }
- def _do_request(self, endpoint, method='GET', params=None, json_data=None, headers=None):
- """Make a request to Google Calendar API"""
- import requests
-
- base_url = 'https://www.googleapis.com/calendar/v3'
- url = f"{base_url}{endpoint}"
-
- if headers is None:
- headers = {}
-
- try:
- # Get credentials (includes access token)
- credentials = self._get_credentials()
-
- # Add authorization header
- headers['Authorization'] = f'Bearer {credentials["access_token"]}'
-
- if method == 'GET':
- response = requests.get(url, params=params, headers=headers)
- elif method == 'POST':
- response = requests.post(url, params=params, json=json_data, headers=headers)
- elif method == 'PUT':
- response = requests.put(url, params=params, json=json_data, headers=headers)
- elif method == 'DELETE':
- response = requests.delete(url, params=params, headers=headers)
- else:
- raise UserError(_('Unsupported HTTP method: %s') % method)
-
- response.raise_for_status()
-
- if response.status_code == 204: # No content
- return None
-
- return response.json()
-
- except requests.exceptions.RequestException as e:
- _logger.error(f"Google Calendar API request failed: {str(e)}")
- raise UserError(_('Google Calendar API Error: %s') % str(e))
- def get_meetings_with_recordings(self, days_back=15):
- """Get Google Meet meetings with recordings from the last N days"""
- try:
- from datetime import datetime, timedelta
-
- # Calculate date range - search for PAST meetings (not future)
- end_date = datetime.now()
- start_date = end_date - timedelta(days=days_back)
-
- _logger.info(f"Searching for Google Meet events with recordings from {start_date} to {end_date}")
-
- # Get calendar events with conference data (Google Meet events)
- # Use timeMin and timeMax to search for PAST events only
- time_min = start_date.isoformat() + 'Z'
- time_max = end_date.isoformat() + 'Z'
-
- # Get all events with pagination to ensure we get everything
- all_events = []
- page_token = None
-
- while True:
- params = {
- 'timeMin': time_min,
- 'timeMax': time_max,
- 'singleEvents': 'true',
- 'orderBy': 'startTime',
- 'maxResults': 2500, # Maximum allowed by Google Calendar API
- 'fields': 'items(id,summary,start,end,attendees,conferenceData,hangoutLink),nextPageToken'
- }
-
- if page_token:
- params['pageToken'] = page_token
-
- response = self._do_request('/calendars/primary/events', params=params)
-
- if 'items' in response:
- all_events.extend(response['items'])
-
- # Check if there are more pages
- page_token = response.get('nextPageToken')
- if not page_token:
- break
-
- events = {'items': all_events}
- _logger.info(f"Retrieved {len(all_events)} total events from Google Calendar API")
-
- # Filter out future events - only process events that have already ended
- from datetime import timezone
- current_time = datetime.now(timezone.utc)
- past_events = []
-
- for event in events.get('items', []):
- end_time_str = event['end'].get('dateTime', event['end'].get('date'))
- if 'T' in end_time_str:
- event_end = datetime.fromisoformat(end_time_str.replace('Z', '+00:00'))
- else:
- # For date-only events, assume they end at 23:59 UTC
- event_end = datetime.fromisoformat(end_time_str + 'T23:59:59+00:00')
-
- # Only include events that have already ended
- if event_end < current_time:
- past_events.append(event)
- else:
- _logger.info(f"Skipping future event: {event.get('summary', 'Unknown')} (ends: {event_end})")
-
- _logger.info(f"Found {len(past_events)} past events out of {len(events.get('items', []))} total events")
-
- events = {'items': past_events}
-
- meetings_with_recordings = []
-
- for event in events.get('items', []):
- # Check if it's a Google Meet event
- if self._is_google_meet_event(event):
- event_title = event.get('summary', 'Sin título')
- hangout_link = event.get('hangoutLink')
-
- # Get meeting date and time
- start_time = event['start'].get('dateTime', event['start'].get('date'))
- end_time = event['end'].get('dateTime', event['end'].get('date'))
-
- # Format the date for display
- from datetime import datetime
- if 'T' in start_time:
- meeting_date = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
- formatted_date = meeting_date.strftime('%Y-%m-%d %H:%M')
- else:
- meeting_date = datetime.fromisoformat(start_time)
- formatted_date = meeting_date.strftime('%Y-%m-%d')
-
- _logger.info(f"Found Google Meet event: {event_title} ({formatted_date})")
-
- # Try to find ALL files associated with this meeting
- meeting_files = self._find_recordings_for_meeting(event, event_title)
-
- if meeting_files:
- participants = self._extract_participants(event)
-
- meeting_data = {
- 'id': event['id'],
- 'title': event_title,
- 'start_time': event['start'].get('dateTime', event['start'].get('date')),
- 'end_time': event['end'].get('dateTime', event['end'].get('date')),
- 'participants': participants,
- 'recording_files': meeting_files,
- 'hangout_link': hangout_link,
- 'calendar_event': event
- }
- meetings_with_recordings.append(meeting_data)
- _logger.info(f"✅ Found meeting with files: {event_title} ({formatted_date}) - Participants: {len(participants)}, Files: {len(meeting_files)})")
- else:
- _logger.info(f"❌ No files found for meeting: {event_title}")
-
- _logger.info(f"Total meetings with recordings found: {len(meetings_with_recordings)}")
- return meetings_with_recordings
-
- except Exception as e:
- _logger.error(f"Failed to get meetings with recordings: {str(e)}")
- return []
- def get_meetings_with_recordings_for_date(self, target_date):
- """Get Google Meet meetings with recordings from a specific date
-
- Args:
- target_date: datetime.date object representing the target date
- """
- try:
- from datetime import datetime, timedelta
-
- # Convert target_date to datetime range for that specific day
- start_date = datetime.combine(target_date, datetime.min.time())
- end_date = datetime.combine(target_date, datetime.max.time())
-
- _logger.info(f"Searching for Google Meet events with recordings for specific date: {target_date} ({start_date} to {end_date})")
-
- # Get calendar events with conference data (Google Meet events)
- # Use timeMin and timeMax to search for events on the specific date
- time_min = start_date.isoformat() + 'Z'
- time_max = end_date.isoformat() + 'Z'
-
- # Get all events with pagination to ensure we get everything
- all_events = []
- page_token = None
-
- while True:
- params = {
- 'timeMin': time_min,
- 'timeMax': time_max,
- 'singleEvents': 'true',
- 'orderBy': 'startTime',
- 'maxResults': 2500, # Maximum allowed by Google Calendar API
- 'fields': 'items(id,summary,start,end,attendees,conferenceData,hangoutLink),nextPageToken'
- }
-
- if page_token:
- params['pageToken'] = page_token
-
- response = self._do_request('/calendars/primary/events', params=params)
-
- if 'items' in response:
- all_events.extend(response['items'])
-
- # Check if there are more pages
- page_token = response.get('nextPageToken')
- if not page_token:
- break
-
- events = {'items': all_events}
- _logger.info(f"Retrieved {len(all_events)} total events from Google Calendar API for date {target_date}")
-
- # Filter out future events - only process events that have already ended
- from datetime import timezone
- current_time = datetime.now(timezone.utc)
- past_events = []
-
- for event in events.get('items', []):
- end_time_str = event['end'].get('dateTime', event['end'].get('date'))
- if 'T' in end_time_str:
- event_end = datetime.fromisoformat(end_time_str.replace('Z', '+00:00'))
- else:
- # For date-only events, assume they end at 23:59 UTC
- event_end = datetime.fromisoformat(end_time_str + 'T23:59:59+00:00')
-
- # Only include events that have already ended
- if event_end < current_time:
- past_events.append(event)
- else:
- _logger.info(f"Skipping future event: {event.get('summary', 'Unknown')} (ends: {event_end})")
-
- _logger.info(f"Found {len(past_events)} past events out of {len(events.get('items', []))} total events for date {target_date}")
-
- events = {'items': past_events}
-
- meetings_with_recordings = []
-
- for event in events.get('items', []):
- # Check if it's a Google Meet event
- if self._is_google_meet_event(event):
- event_title = event.get('summary', 'Sin título')
- hangout_link = event.get('hangoutLink')
-
- # Get meeting date and time
- start_time = event['start'].get('dateTime', event['start'].get('date'))
- end_time = event['end'].get('dateTime', event['end'].get('date'))
-
- # Format the date for display
- from datetime import datetime
- if 'T' in start_time:
- meeting_date = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
- formatted_date = meeting_date.strftime('%Y-%m-%d %H:%M')
- else:
- meeting_date = datetime.fromisoformat(start_time)
- formatted_date = meeting_date.strftime('%Y-%m-%d')
-
- _logger.info(f"Found Google Meet event: {event_title} ({formatted_date})")
-
- # Try to find ALL files associated with this meeting
- meeting_files = self._find_recordings_for_meeting(event, event_title)
-
- if meeting_files:
- participants = self._extract_participants(event)
-
- meeting_data = {
- 'id': event['id'],
- 'title': event_title,
- 'start_time': event['start'].get('dateTime', event['start'].get('date')),
- 'end_time': event['end'].get('dateTime', event['end'].get('date')),
- 'participants': participants,
- 'recording_files': meeting_files,
- 'hangout_link': hangout_link,
- 'calendar_event': event
- }
- meetings_with_recordings.append(meeting_data)
- _logger.info(f"✅ Found meeting with files: {event_title} ({formatted_date}) - Participants: {len(participants)}, Files: {len(meeting_files)})")
- else:
- _logger.info(f"❌ No files found for meeting: {event_title}")
-
- _logger.info(f"Total meetings with recordings found for date {target_date}: {len(meetings_with_recordings)}")
- return meetings_with_recordings
-
- except Exception as e:
- _logger.error(f"Failed to get meetings with recordings for date {target_date}: {str(e)}")
- return []
- def _find_recordings_for_meeting(self, event, event_title):
- """Find ALL files associated with a specific meeting (videos, documents, etc.)"""
- try:
- from datetime import datetime, timedelta
-
- drive_service = self.env['google.drive.service']
-
- # Search for video files that might be recordings for this meeting
- # Use the meeting title and date to find relevant files
- start_date = event['start'].get('dateTime', event['start'].get('date'))
-
- # Convert to datetime for search
- if 'T' in start_date:
- meeting_date = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
- else:
- meeting_date = datetime.fromisoformat(start_date)
-
- # Search for files created around the meeting time (±1 day)
- search_start = (meeting_date - timedelta(days=1)).isoformat()
- search_end = (meeting_date + timedelta(days=1)).isoformat()
-
- # Search for ALL file types that might be associated with the meeting
- # This includes videos, documents, presentations, transcripts, etc.
- files = drive_service._do_request(
- '/drive/v3/files',
- params={
- 'q': f"createdTime > '{search_start}' and createdTime < '{search_end}' and trashed=false",
- 'fields': 'files(id,name,createdTime,size,mimeType)',
- 'orderBy': 'createdTime desc',
- 'pageSize': 50 # Increased to get more files
- }
- )
-
- meeting_files = []
- event_title_clean = self._clean_title_for_matching(event_title)
-
- for file in files.get('files', []):
- file_name_clean = self._clean_title_for_matching(file['name'])
- mime_type = file.get('mimeType', '')
-
- # Check if file name contains meeting title or vice versa
- if (event_title_clean in file_name_clean or
- file_name_clean in event_title_clean or
- self._titles_match(file['name'], event_title)):
-
- meeting_files.append(file['id'])
-
- # Log with file type indicator
- file_type = "📹 Video"
- if 'video/' in mime_type:
- file_type = "📹 Video"
- elif 'application/pdf' in mime_type:
- file_type = "📄 PDF"
- elif 'application/vnd.google-apps' in mime_type:
- file_type = "📊 Google Doc"
- elif 'text/' in mime_type:
- file_type = "📝 Text"
- elif 'image/' in mime_type:
- file_type = "🖼️ Image"
- else:
- file_type = "📁 File"
-
- _logger.info(f"{file_type} Found for meeting '{event_title}': {file['name']} ({mime_type})")
-
- _logger.info(f"📊 Total files found for meeting '{event_title}': {len(meeting_files)}")
- return meeting_files
-
- except Exception as e:
- _logger.error(f"Error finding recordings for meeting {event_title}: {str(e)}")
- return []
- def _clean_title_for_matching(self, title):
- """Clean title for better matching"""
- import re
-
- # Remove common suffixes and prefixes
- title = title.lower()
- title = re.sub(r' - recording', '', title)
- title = re.sub(r' - grabacion', '', title)
- title = re.sub(r'recording', '', title)
- title = re.sub(r'grabacion', '', title)
-
- # Remove date/time patterns
- title = re.sub(r'\d{4}/\d{2}/\d{2}', '', title)
- title = re.sub(r'\d{2}:\d{2}', '', title)
- title = re.sub(r'cst', '', title)
-
- # Clean up extra spaces
- title = ' '.join(title.split())
-
- return title
- def _titles_match(self, file_name, event_title):
- """Check if file name matches calendar event title"""
- # Remove common suffixes from file name
- file_clean = file_name.lower()
- file_clean = file_clean.replace(' - recording', '').replace(' - grabacion', '')
- file_clean = file_clean.replace('recording', '').replace('grabacion', '')
-
- # Remove date/time patterns
- import re
- file_clean = re.sub(r'\d{4}/\d{2}/\d{2}', '', file_clean)
- file_clean = re.sub(r'\d{2}:\d{2}', '', file_clean)
- file_clean = re.sub(r'cst', '', file_clean)
-
- # Clean up extra spaces
- file_clean = ' '.join(file_clean.split())
-
- # Check if event title is contained in cleaned file name
- return event_title.lower() in file_clean or file_clean in event_title.lower()
- def _get_parent_folder_info(self, parent_ids, drive_service):
- """Get information about parent folders to understand context"""
- if not parent_ids:
- return {}
-
- try:
- parent_info = drive_service._do_request(
- f'/drive/v3/files/{parent_ids[0]}',
- params={
- 'fields': 'id,name,parents'
- }
- )
- return parent_info
- except:
- return {}
- def _is_google_meet_event(self, event):
- """Check if event is a Google Meet event"""
- # Check for conference data
- conference_data = event.get('conferenceData', {})
- if conference_data.get('conferenceId'):
- return True
-
- # Check for hangout link
- if event.get('hangoutLink'):
- return True
-
- # Check if attendees have Google Meet links
- attendees = event.get('attendees', [])
- for attendee in attendees:
- if 'hangout.google.com' in str(attendee):
- return True
-
- return False
- def _extract_participants(self, event):
- """Extract participant emails from event"""
- participants = []
- attendees = event.get('attendees', [])
-
- for attendee in attendees:
- email = attendee.get('email')
- if email and not email.endswith('@resource.calendar.google.com'):
- participants.append(email)
-
- return participants
- def _get_meeting_recordings(self, event):
- """Get recording files for a meeting"""
- # This is a simplified implementation
- # In a real scenario, you would need to:
- # 1. Get the meeting ID from the event
- # 2. Call Google Drive API to search for recording files
- # 3. Filter files that belong to this specific meeting
-
- try:
- # For now, we'll search for any recording files in the user's Drive
- # that might be related to meetings
- drive_service = self.env['google.drive.service']
-
- # Search for video files created in the last few days
- from datetime import datetime, timedelta
- cutoff_date = (datetime.now() - timedelta(days=2)).isoformat()
-
- files = drive_service._do_request(
- '/drive/v3/files',
- params={
- 'q': f"mimeType contains 'video/' and createdTime > '{cutoff_date}' and trashed=false",
- 'fields': 'files(id,name,createdTime,parents)',
- 'orderBy': 'createdTime desc',
- 'pageSize': 50
- }
- )
-
- recording_files = []
- for file in files.get('files', []):
- # Simple heuristic: if file name contains meeting-related keywords
- file_name = file['name'].lower()
- if any(keyword in file_name for keyword in ['meet', 'meeting', 'reunion', 'grabacion', 'recording']):
- recording_files.append(file['id'])
-
- return recording_files
-
- except Exception as e:
- _logger.error(f"Failed to get meeting recordings: {str(e)}")
- return []
- def get_calendar_list(self):
- """Get list of available calendars"""
- try:
- calendars = self._do_request(
- '/users/me/calendarList'
- )
-
- return calendars.get('items', [])
-
- except Exception as e:
- _logger.error(f"Failed to get calendar list: {str(e)}")
- raise UserError(_('Failed to get calendar list: %s') % str(e))
|