Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google calendar #438

Merged
merged 20 commits into from
Jun 24, 2023
1 change: 1 addition & 0 deletions gui/pages/api/DashboardService.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const updatePermissions = (permissionId, data) => {
return api.put(`/agentexecutionpermissions/update/status/${permissionId}`, data)
}


export const authenticateGoogleCred = (toolKitId) => {
return api.get(`/google/get_google_creds/toolkit_id/${toolKitId}`);
}
Expand Down
54 changes: 53 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import inspect
import os
from datetime import timedelta

import requests
Expand All @@ -12,9 +14,13 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import pickle
import superagi
from datetime import datetime, timedelta
from superagi.agent.agent_prompt_builder import AgentPromptBuilder
from superagi.config.config import get_config
from superagi.controllers.agent_template import router as agent_template_router
from superagi.controllers.agent_workflow import router as agent_workflow_router
from superagi.controllers.agent import router as agent_router
from superagi.controllers.agent_config import router as agent_config_router
from superagi.controllers.agent_execution import router as agent_execution_router
Expand All @@ -39,6 +45,8 @@
from superagi.models.organisation import Organisation
from superagi.models.types.login_request import LoginRequest
from superagi.models.user import User
from superagi.tools.base_tool import BaseTool
from superagi.models.tool_config import ToolConfig

app = FastAPI()

Expand Down Expand Up @@ -109,7 +117,6 @@ def create_access_token(email, Authorize: AuthJWT = Depends()):
access_token = Authorize.create_access_token(subject=email, expires_time=expires)
return access_token


# callback to get your configuration
@AuthJWT.load_config
def get_config():
Expand Down Expand Up @@ -268,6 +275,45 @@ def login(request: LoginRequest, Authorize: AuthJWT = Depends()):
# access_token = Authorize.create_access_token(subject=user_email)
# return access_token

@app.get('/oauth-calendar')
async def google_auth_calendar(code: str = Query(...), Authorize: AuthJWT = Depends()):
client_id = db.session.query(ToolConfig).filter(ToolConfig.key == "GOOGLE_CLIENT_ID").first()
client_id = client_id.value
client_secret = db.session.query(ToolConfig).filter(ToolConfig.key == "GOOGLE_CLIENT_SECRET").first()
client_secret = client_secret.value
token_uri = 'https://oauth2.googleapis.com/token'
scope = 'https://www.googleapis.com/auth/calendar'
params = {
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': "http://localhost:3000/api/oauth-calendar",
'scope': scope,
'grant_type': 'authorization_code',
'code': code,
'access_type': 'offline'
}
response = requests.post(token_uri, data=params)
response = response.json()
expire_time = datetime.utcnow() + timedelta(seconds=response['expires_in'])
expire_time = expire_time - timedelta(minutes=5)
response['expiry'] = expire_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
root_dir = superagi.config.config.get_config('RESOURCES_OUTPUT_ROOT_DIR')
file_name = "credential_token.pickle"
final_path = file_name
if root_dir is not None:
root_dir = root_dir if root_dir.startswith("/") else os.getcwd() + "/" + root_dir
root_dir = root_dir if root_dir.endswith("/") else root_dir + "/"
final_path = root_dir + file_name
else:
final_path = os.getcwd() + "/" + file_name
try:
with open(final_path, mode="wb") as file:
pickle.dump(response, file)
except Exception as err:
return f"Error: {err}"
frontend_url = superagi.config.config.get_config("FRONTEND_URL", "http://localhost:3000")
return RedirectResponse(frontend_url)

@app.get('/github-login')
def github_login():
"""GitHub login"""
Expand Down Expand Up @@ -349,6 +395,12 @@ async def root(Authorize: AuthJWT = Depends()):
except:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")

@app.get("/google/get_google_creds/toolkit_id/{toolkit_id}")
def get_google_calendar_tool_configs(toolkit_id: int):
google_calendar_config = db.session.query(ToolConfig).filter(ToolConfig.tool_kit_id == toolkit_id,ToolConfig.key == "GOOGLE_CLIENT_ID").first()
return {
"client_id": google_calendar_config.value
}

@app.get("/validate-open-ai-key/{open_ai_key}")
async def root(open_ai_key: str, Authorize: AuthJWT = Depends()):
Expand Down
59 changes: 59 additions & 0 deletions superagi/helper/calendar_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from datetime import datetime, timedelta, timezone

import pytz


class CalendarDate:
def create_event_dates(self, service, start_date, start_time, end_date, end_time):
local_tz = pytz.timezone(self._get_time_zone(service))
start_datetime, end_datetime = self._localize_daterange(start_date, end_date, start_time, end_time, local_tz)
date_utc = {
"start_datetime_utc": self._datetime_to_string(start_datetime, "%Y-%m-%dT%H:%M:%S.%fZ"),
"end_datetime_utc": self._datetime_to_string(end_datetime, "%Y-%m-%dT%H:%M:%S.%fZ"),
"timeZone": self._get_time_zone(service)
}
return date_utc

def get_date_utc(self, start_date, end_date, start_time, end_time, service):
local_tz = pytz.timezone(self._get_time_zone(service))
start_datetime, end_datetime = self._localize_daterange(start_date, end_date, start_time, end_time, local_tz)
date_utc = {
"start_datetime_utc": self._datetime_to_string(start_datetime, "%Y-%m-%dT%H:%M:%S.%fZ"),
"end_datetime_utc": self._datetime_to_string(end_datetime, "%Y-%m-%dT%H:%M:%S.%fZ")
}
return date_utc

def _get_time_zone(self, service):
calendar = service.calendars().get(calendarId='primary').execute()
time_detail = calendar['timeZone']
return time_detail

def _convert_to_utc(self, date_time, local_tz):
local_datetime = local_tz.localize(date_time)
gmt_tz = pytz.timezone("GMT")
return local_datetime.astimezone(gmt_tz)

def _string_to_datetime(self, date_str, date_format):
return datetime.strptime(date_str, date_format) if date_str else None

def _localize_daterange(self, start_date, end_date, start_time, end_time, local_tz):
start_datetime = self._string_to_datetime(start_date, "%Y-%m-%d") if start_date != 'None' else datetime.now(
timezone.utc)
end_datetime = self._string_to_datetime(end_date,
"%Y-%m-%d") if end_date != 'None' else start_datetime + timedelta(
days=30) - timedelta(microseconds=1)
time_obj_start = self._string_to_datetime(start_time, "%H:%M:%S")
time_obj_end = self._string_to_datetime(end_time, "%H:%M:%S")
start_datetime = start_datetime.replace(hour=time_obj_start.hour, minute=time_obj_start.minute,
second=time_obj_start.second,
microsecond=0) if time_obj_start else start_datetime.replace(hour=0,
minute=0,
second=0,
microsecond=0)
end_datetime = end_datetime.replace(hour=time_obj_end.hour, minute=time_obj_end.minute,
second=time_obj_end.second) if time_obj_end else end_datetime.replace(
hour=23, minute=59, second=59, microsecond=999999)
return self._convert_to_utc(start_datetime, local_tz), self._convert_to_utc(end_datetime, local_tz)

def _datetime_to_string(self, date_time, date_format):
return date_time.strftime(date_format) if date_time else None
60 changes: 60 additions & 0 deletions superagi/helper/google_calendar_creds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pickle
import os
import json
from datetime import datetime
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import Flow
from google.auth.transport.requests import Request
from superagi.config.config import get_config
from googleapiclient.discovery import build
from sqlalchemy.orm import sessionmaker
from superagi.models.db import connect_db
from superagi.models.tool_config import ToolConfig
from superagi.resource_manager.manager import ResourceManager

class GoogleCalendarCreds:

def get_credentials(self, toolkit_id):
file_name = "credential_token.pickle"
root_dir = get_config('RESOURCES_OUTPUT_ROOT_DIR')
file_path = file_name
if root_dir is not None:
root_dir = root_dir if root_dir.startswith("/") else os.getcwd() + "/" + root_dir
root_dir = root_dir if root_dir.endswith("/") else root_dir + "/"
file_path = root_dir + file_name
else:
file_path = os.getcwd() + "/" + file_name
if os.path.exists(file_path):
engine = connect_db()
Session = sessionmaker(bind=engine)
session = Session()
resource_manager: ResourceManager = None
with open(file_path,'rb') as file:
creds = pickle.load(file)
if isinstance(creds, str):
creds = json.loads(creds)
expire_time = datetime.strptime(creds["expiry"], "%Y-%m-%dT%H:%M:%S.%fZ")
google_creds = session.query(ToolConfig).filter(ToolConfig.toolkit_id == toolkit_id).all()
client_id = ""
client_secret = ""
for credentials in google_creds:
credentials = credentials.__dict__
if credentials["key"] == "GOOGLE_CLIENT_ID":
client_id = credentials["value"]
if credentials["key"] == "GOOGLE_CLIENT_SECRET":
client_secret = credentials["value"]
Tarraann marked this conversation as resolved.
Show resolved Hide resolved
creds = Credentials.from_authorized_user_info(info={
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": creds["refresh_token"],
"scopes": "https://www.googleapis.com/auth/calendar"
})
if expire_time < datetime.utcnow():
creds.refresh(Request())
creds_json = creds.to_json()
resource_manager.write_file(file_name, creds_json)
else:
return {"success": False}
service = build('calendar','v3',credentials=creds)
return {"success": True, "service": service}

19 changes: 18 additions & 1 deletion superagi/resource_manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from superagi.helper.s3_helper import S3Helper
from superagi.lib.logger import logger
import os

import csv

class ResourceManager:
def __init__(self, session: Session, agent_id: int = None):
Expand Down Expand Up @@ -58,5 +58,22 @@ def write_file(self, file_name: str, content):
except Exception as err:
return f"Error: {err}"

def write_csv_file(self, file_name: str, csv_data):
if self.agent_id is not None:
final_path = ResourceHelper.get_agent_resource_path(file_name, self.agent_id)
else:
final_path = ResourceHelper.get_resource_path(file_name)

try:
with open(final_path, mode="w") as file:
writer = csv.writer(file, lineterminator="\n")
for row in csv_data:
writer.writerows(row)
self.write_to_s3(file_name, final_path)
logger.info(f"{file_name} - File written successfully")
return f"{file_name} - File written successfully"
except Exception as err:
return f"Error: {err}"

def get_agent_resource_path(self, file_name: str):
return ResourceHelper.get_agent_resource_path(file_name, self.agent_id)
62 changes: 62 additions & 0 deletions superagi/tools/google_calendar/create_calendar_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import Any, Type
from pydantic import BaseModel, Field
from superagi.tools.base_tool import BaseTool
from superagi.helper.google_calendar_creds import GoogleCalendarCreds
from superagi.helper.calendar_date import CalendarDate

class CreateEventCalendarInput(BaseModel):
event_name: str = Field(..., description="Name of the event/meeting to be scheduled, if not given craete a name depending on description.")
description: str = Field(..., description="Description of the event/meeting to be scheduled.")
start_date: str = Field(..., description="Start date of the event to be scheduled in format 'yyyy-mm-dd', if no value is given keep the default value as 'None'.")
start_time: str = Field(..., description="Start time of the event to be scheduled in format 'hh:mm:ss', if no value is given keep the default value as 'None'.")
end_date: str = Field(..., description="End Date of the event to be scheduled in format 'yyyy-mm-dd', if no value is given keep the default value as 'None'.")
end_time: str = Field(..., description="End Time of the event to be scheduled in format 'hh:mm:ss', if no value is given keep the default value as 'None'.")
attendees: list = Field(..., description="List of attendees email ids to be invited for the event.")
location: str = Field(..., description="Geographical location of the event. if no value is given keep the default value as 'None'")

class CreateEventCalendarTool(BaseTool):
name: str = "Create Google Calendar Event"
args_schema: Type[BaseModel] = CreateEventCalendarInput
description: str = "Create an event for Google Calendar"

def _execute(self, event_name: str, description: str, attendees: list, start_date: str = 'None', start_time: str = 'None', end_date: str = 'None', end_time: str = 'None', location: str = 'None'):
toolkit_id = self.toolkit_config.toolkit_id
service = GoogleCalendarCreds().get_credentials(toolkit_id)
if service["success"]:
service = service["service"]
else:
return f"Kindly connect to Google Calendar"
date_utc = CalendarDate().create_event_dates(service, start_date, start_time, end_date, end_time)
attendees_list = []
for attendee in attendees:
email_id = {
"email": attendee
}
attendees_list.append(email_id)
event = {
"summary": event_name,
"description": description,
"start": {
"dateTime": date_utc["start_datetime_utc"],
"timeZone": date_utc["timeZone"]
},
"end": {
"dateTime": date_utc["end_datetime_utc"],
"timeZone": date_utc["timeZone"]
},
"attendees": attendees_list
}
if location != "None":
event["location"] = location
else:
event["conferenceData"] = {
"createRequest": {
"requestId": f"meetSample123",
"conferenceSolutionKey": {
"type": "hangoutsMeet"
},
},
}
event = service.events().insert(calendarId="primary", body=event, conferenceDataVersion=1).execute()
output_str = f"Event {event_name} at {date_utc['start_datetime_utc']} created successfully, link for the event {event.get('htmlLink')}"
return output_str
28 changes: 28 additions & 0 deletions superagi/tools/google_calendar/delete_calendar_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Any, Type
from pydantic import BaseModel, Field
from superagi.tools.base_tool import BaseTool
from superagi.helper.google_calendar_creds import GoogleCalendarCreds

class DeleteCalendarEventInput(BaseModel):
event_id: str = Field(..., description="The id of event to be deleted from Google Calendar. default value is None")

class DeleteCalendarEventTool(BaseTool):
name: str = "Delete Google Calendar Event"
args_schema: Type[BaseModel] = DeleteCalendarEventInput
description: str = "Delete an event from Google Calendar"

def _execute(self, event_id: str):
toolkit_id = self.toolkit_config.toolkit_id
service = GoogleCalendarCreds().get_credentials(toolkit_id)
if service["success"]:
service = service["service"]
else:
return f"Kindly connect to Google Calendar"
if event_id == "None":
return f"Add Event ID to delete an event from Google Calendar"
else:
result = service.events().delete(
calendarId = "primary",
eventId = event_id
).execute()
return f"Event Successfully deleted from your Google Calendar"
43 changes: 43 additions & 0 deletions superagi/tools/google_calendar/event_details_calendar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import base64
from typing import Any, Type
from pydantic import BaseModel, Field
from superagi.tools.base_tool import BaseTool
from superagi.helper.google_calendar_creds import GoogleCalendarCreds

class EventDetailsCalendarInput(BaseModel):
event_id: str = Field(..., description="The id of event to be fetched from Google Calendar. if no value is given keep default value is None")

class EventDetailsCalendarTool(BaseTool):
name: str = "Fetch Google Calendar Event"
args_schema: Type[BaseModel] = EventDetailsCalendarInput
description: str = "Fetch an event from Google Calendar"

def _execute(self, event_id: str):
toolkit_id = self.toolkit_config.toolkit_id
service = GoogleCalendarCreds().get_credentials(toolkit_id)
if service["success"]:
service = service["service"]
else:
return f"Kindly connect to Google Calendar"
if event_id == "None":
return f"Add Event ID to fetch details of an event from Google Calendar"
else:
decoded_id = base64.b64decode(event_id)
eid = decoded_id.decode("utf-8")
eid = eid.split(" ", 1)[0]
Tarraann marked this conversation as resolved.
Show resolved Hide resolved
result = service.events().get(
calendarId = "primary",
eventId = eid
).execute()
if "summary" in result:
summary = result['summary']
if result['start'] and result['end']:
start_date = result['start']['dateTime']
end_date = result['end']['dateTime']
attendees = []
if "attendees" in result:
for attendee in result['attendees']:
attendees.append(attendee['email'])
attendees_str = ','.join(attendees)
output_str = f"Event details for the event id '{event_id}' is - \nSummary : {summary}\nStart Date and Time : {start_date}\nEnd Date and Time : {end_date}\nAttendees : {attendees_str}"
return output_str
Loading