Skip to content

Commit 04003bb

Browse files
zwfxxxxxAlexCXC
andauthored
automation rule add google calendar function (#804)
* automation rule add google calendar function * update error log * update * update * update create event * add calendar operations * del dtable-web function for calendar * update * update get_google_calendar_list * update * update fill field value * update calendar can_do_action * google calendar logs to dtable-events.log * fix send google calendar params --------- Co-authored-by: Alex Happy <1223408988@qq.com>
1 parent a976cd5 commit 04003bb

File tree

4 files changed

+614
-2
lines changed

4 files changed

+614
-2
lines changed

dtable_events/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
from dtable_events.utils.sql_generator import filter2sql, statistic2sql, linkRecords2sql, SQLGeneratorOptionInvalidError, \
99
DateTimeQueryInvalidError, ColumnFilterInvalidError, BaseSQLGenerator
1010
from dtable_events.utils.dtable_db_api import convert_db_rows
11+
from dtable_events.utils.google_calendar_manager import get_google_calendar_list
1112
from dtable_events.virus_scanner import *

dtable_events/automations/actions.py

Lines changed: 275 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
import os
77
from copy import deepcopy
8-
from datetime import datetime, date, timedelta
8+
from datetime import datetime, date, timedelta, timezone
99
from queue import Full
1010
from urllib.parse import unquote, urlparse, parse_qs
1111
from uuid import UUID
@@ -40,6 +40,7 @@
4040
has_user_filter, is_user_filter
4141
from dtable_events.utils.universal_app_api import UniversalAppAPI
4242
from dtable_events.utils.email_sender import EmailSender
43+
from dtable_events.utils.google_calendar_manager import CalendarManager
4344

4445
PER_DAY = 'per_day'
4546
PER_WEEK = 'per_week'
@@ -4073,6 +4074,255 @@ def do_action(self):
40734074
return
40744075

40754076

4077+
class GoogleCalendar(BaseAction):
4078+
def __init__(self, auto_rule, action_type, data, config, function_type):
4079+
super().__init__(auto_rule, action_type, data)
4080+
self.config = config or {}
4081+
self.function_type = function_type
4082+
self.account_id = self.config.get('account_id')
4083+
self.calendar_id = self.config.get('calendar_id')
4084+
self.col_key_dict = {col.get('key'): col for col in self.auto_rule.table_info['columns']}
4085+
4086+
def update_event(self):
4087+
event_id_key = self.config.get('event_id_key')
4088+
4089+
# Get event_id from the specified column
4090+
event_id = self._get_field_value_by_column_key(event_id_key)
4091+
if not event_id:
4092+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar update event failed: event ID not found.')
4093+
return
4094+
4095+
try:
4096+
event_data = self._prepare_event_data()
4097+
if not event_data:
4098+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar update event failed: unable to prepare event data from table row')
4099+
return
4100+
4101+
# Add event_id for update operation
4102+
event_data['event_id'] = event_id
4103+
4104+
manager = CalendarManager(self.account_id, db_session=self.auto_rule.db_session)
4105+
result = manager.update_event(event_data)
4106+
4107+
if result.get('error_msg'):
4108+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar update event failed: {result["error_msg"]}')
4109+
else:
4110+
auto_rule_logger.info(f'rule {self.auto_rule.rule_id} GoogleCalendar update event success')
4111+
4112+
except Exception as e:
4113+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar update event failed with error: {e}')
4114+
4115+
def create_event(self):
4116+
try:
4117+
event_data = self._prepare_event_data()
4118+
if not event_data:
4119+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar create event failed: unable to prepare event data from table row')
4120+
return
4121+
4122+
manager = CalendarManager(self.account_id, db_session=self.auto_rule.db_session)
4123+
result = manager.create_event(event_data)
4124+
4125+
if result.get('error_msg'):
4126+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar create event failed: {result["error_msg"]}')
4127+
elif result.get('id'):
4128+
event_id = result.get('id')
4129+
self._save_event_id_to_column(event_id)
4130+
4131+
except Exception as e:
4132+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar create event failed with error: {e}')
4133+
4134+
def _prepare_event_data(self):
4135+
try:
4136+
start_date_column_key = self.config.get('start_date_column_key')
4137+
end_date_column_key = self.config.get('end_date_column_key')
4138+
4139+
start_time = self._get_field_value_by_column_key(start_date_column_key)
4140+
end_time = self._get_field_value_by_column_key(end_date_column_key)
4141+
4142+
event_data = {
4143+
'summary': self._get_field_value('event_title'),
4144+
'start': self._format_datetime_object(start_time),
4145+
'end': self._format_datetime_object(end_time),
4146+
'calendar_id': self.calendar_id,
4147+
'description': self._get_field_value('event_description'),
4148+
'location': self._get_field_value('location'),
4149+
'attendees': self._format_attendees(self.config.get('attendees', [])),
4150+
}
4151+
4152+
if not self.config.get('dont_send_notifications'):
4153+
event_data['sendUpdates'] = 'all'
4154+
if self.config.get('guests_can_invite') is not None:
4155+
event_data['guestsCanInviteOthers'] = self.config.get('guests_can_invite')
4156+
if self.config.get('guests_can_modify') is not None:
4157+
event_data['guestsCanModify'] = self.config.get('guests_can_modify')
4158+
if self.config.get('guests_can_see_list') is not None:
4159+
event_data['guestsCanSeeOtherGuests'] = self.config.get('guests_can_see_list')
4160+
4161+
if self.config.get('video_conferencing'):
4162+
event_data['conferenceDataVersion'] = 1
4163+
event_data['conferenceData'] = {
4164+
'createRequest': {
4165+
'requestId': f"dtable_event_{self.auto_rule.rule_id}_{int(time.time())}",
4166+
'conferenceSolutionKey': {
4167+
'type': 'hangoutsMeet'
4168+
}
4169+
}
4170+
}
4171+
4172+
return event_data
4173+
4174+
except Exception as e:
4175+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar prepare event data failed: {e}')
4176+
return None
4177+
4178+
def _get_field_value_by_column_key(self, column_key):
4179+
if not column_key:
4180+
return None
4181+
4182+
sql_row = self.auto_rule.get_sql_row()
4183+
if not sql_row:
4184+
return None
4185+
4186+
return sql_row.get(column_key)
4187+
4188+
def _get_field_value(self, field_name):
4189+
field_value = self.config.get(field_name)
4190+
if field_value and not isinstance(field_value, str):
4191+
return ''
4192+
blanks = set(re.findall(r'\{([^{]*?)\}', field_value))
4193+
col_name_dict = {col['name']: col for col in self.auto_rule.table_info['columns']}
4194+
sql_row = self.auto_rule.get_sql_row()
4195+
return fill_msg_blanks_with_sql_row(field_value, blanks, col_name_dict, sql_row, self.auto_rule.db_session)
4196+
4197+
def _format_datetime_object(self, datetime_str):
4198+
4199+
if not datetime_str:
4200+
return None
4201+
4202+
try:
4203+
if isinstance(datetime_str, str):
4204+
dt = datetime.fromisoformat(datetime_str.replace('Z', '+00:00'))
4205+
else:
4206+
dt = datetime_str
4207+
4208+
if dt.tzinfo is None:
4209+
dt = dt.replace(tzinfo=timezone.utc)
4210+
4211+
return {
4212+
'dateTime': dt.isoformat(),
4213+
'timeZone': 'UTC' if dt.tzinfo == timezone.utc else str(dt.tzinfo)
4214+
}
4215+
4216+
except Exception as e:
4217+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar datetime formatting failed: {e}')
4218+
return None
4219+
4220+
def _format_attendees(self, attendees):
4221+
if not attendees:
4222+
return []
4223+
4224+
formatted_attendees = []
4225+
try:
4226+
# Convert usernames to contact emails
4227+
attendee_emails = self._convert_usernames_to_emails(attendees)
4228+
4229+
for attendee in attendee_emails:
4230+
formatted_attendees.append({
4231+
'email': attendee,
4232+
'responseStatus': 'needsAction'
4233+
})
4234+
4235+
except Exception as e:
4236+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar attendees formatting failed: {e}')
4237+
4238+
return formatted_attendees
4239+
4240+
def _convert_usernames_to_emails(self, attendees):
4241+
"""Convert usernames to contact emails."""
4242+
return_emails = []
4243+
usernames = []
4244+
for item in attendees:
4245+
if not is_valid_email(item):
4246+
continue
4247+
if '@auth.local' in item:
4248+
usernames.append(item)
4249+
continue
4250+
if item in return_emails:
4251+
continue
4252+
return_emails.append(item)
4253+
if usernames:
4254+
sql = '''
4255+
SELECT `contact_email` FROM `profile_profile`
4256+
WHERE `user` IN :usernames AND `contact_email` IS NOT NULL AND `contact_email` != ''
4257+
'''
4258+
try:
4259+
results = self.auto_rule.db_session.execute(text(sql), {'usernames': usernames})
4260+
except Exception as e:
4261+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar failed to query user contact emails: {e}')
4262+
else:
4263+
return_emails.extend([item.contact_email for item in results])
4264+
return return_emails
4265+
4266+
def _save_event_id_to_column(self, event_id):
4267+
"""Save event_id to the configured event_id column"""
4268+
try:
4269+
table_name = self.auto_rule.table_info['name']
4270+
row_id = self.data['row_id']
4271+
event_id_key = self.config.get('event_id_key')
4272+
4273+
event_id_column = self.col_key_dict.get(event_id_key)
4274+
if not event_id_column:
4275+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar event_id column not found')
4276+
return
4277+
4278+
column_name = event_id_column.get('name')
4279+
update_data = {column_name: event_id}
4280+
self.auto_rule.dtable_server_api.update_row(table_name, row_id, update_data)
4281+
4282+
except Exception as e:
4283+
auto_rule_logger.error(f'rule {self.auto_rule.rule_id} GoogleCalendar failed to save event_id: {e}')
4284+
4285+
def can_do_action(self):
4286+
if not self.account_id or not self.calendar_id:
4287+
return False
4288+
4289+
event_id_key = self.config.get('event_id_key')
4290+
start_date_column_key = self.config.get('start_date_column_key')
4291+
end_date_column_key = self.config.get('end_date_column_key')
4292+
4293+
if not event_id_key or not start_date_column_key or not end_date_column_key:
4294+
return False
4295+
4296+
event_id_column = self.col_key_dict.get(event_id_key)
4297+
start_date_column = self.col_key_dict.get(start_date_column_key)
4298+
end_date_column = self.col_key_dict.get(end_date_column_key)
4299+
4300+
if not event_id_column or event_id_column.get('type') != ColumnTypes.TEXT:
4301+
return False
4302+
if not start_date_column or start_date_column.get('type') != ColumnTypes.DATE:
4303+
return False
4304+
if not end_date_column or end_date_column.get('type') != ColumnTypes.DATE:
4305+
return False
4306+
4307+
start_time = self._get_field_value_by_column_key(start_date_column_key)
4308+
end_time = self._get_field_value_by_column_key(end_date_column_key)
4309+
if not start_time or not end_time:
4310+
return False
4311+
return True
4312+
4313+
def do_action(self):
4314+
if not self.can_do_action():
4315+
return
4316+
if self.function_type == 'create_event':
4317+
self.create_event()
4318+
elif self.function_type == 'update_event':
4319+
self.update_event()
4320+
else:
4321+
auto_rule_logger.warning('google calendar action %s not supported', self.action_type)
4322+
return
4323+
4324+
4325+
40764326
class RuleInvalidException(Exception):
40774327
"""
40784328
Exception which indicates rule need to be set is_valid=Fasle
@@ -4446,6 +4696,10 @@ def can_condition_trigger_action(self, action):
44464696
if run_condition == PER_UPDATE:
44474697
return True
44484698
return False
4699+
elif action_type == 'google_calendar':
4700+
if self.run_condition == PER_UPDATE:
4701+
return True
4702+
return False
44494703
return False
44504704

44514705
def do_actions(self, db_session, with_test=False):
@@ -4673,6 +4927,26 @@ def do_actions(self, db_session, with_test=False):
46734927
config = config_map.get(ai_function, {}).get('config')
46744928
RunAI(self, action_info.get('type'), self.data, ai_function, config).do_action()
46754929

4930+
elif action_info.get('type') == 'google_calendar':
4931+
function_type = action_info.get('action_type')
4932+
config = {
4933+
'account_id': action_info.get('account_id'),
4934+
'calendar_id': action_info.get('calendar_id'),
4935+
'dont_send_notifications': action_info.get('dont_send_notifications', False),
4936+
'start_date_column_key': action_info.get('start_date_column_key'),
4937+
'end_date_column_key': action_info.get('end_date_column_key'),
4938+
'event_description': action_info.get('event_description', ''),
4939+
'event_title': action_info.get('event_title'),
4940+
'guests_can_invite': action_info.get('guests_can_invite', False),
4941+
'guests_can_modify': action_info.get('guests_can_modify', False),
4942+
'guests_can_see_list': action_info.get('guests_can_see_list', False),
4943+
'location': action_info.get('location', ''),
4944+
'video_conferencing': action_info.get('video_conferencing', False),
4945+
'attendees': action_info.get('attendees', []),
4946+
'event_id_key': action_info.get('event_id_key'),
4947+
}
4948+
GoogleCalendar(self, action_info.get('type'), self.data, config, function_type).do_action()
4949+
46764950
except RuleInvalidException as e:
46774951
auto_rule_logger.warning('auto rule %s with data %s, invalid error: %s', self.rule_id, self.data, e)
46784952
self.task_run_success = False

dtable_events/automations/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
logger = logging.getLogger(__name__)
1212

13-
ENCRYPT_KEYS = ['password', 'webhook_url', 'api_key', 'secret_key', 'repo_api_token', 'client_secret']
13+
ENCRYPT_KEYS = ['password', 'webhook_url', 'api_key', 'secret_key', 'repo_api_token', 'client_secret', 'access_token', 'refresh_token']
1414
def _encrypt_detail(detail):
1515
detail_clone = deepcopy(detail)
1616
cryptor = AESPasswordHasher()

0 commit comments

Comments
 (0)