|
24 | 24 | from camel.logger import get_logger |
25 | 25 | from camel.toolkits import FunctionTool |
26 | 26 | from camel.toolkits.base import BaseToolkit |
27 | | -from camel.utils import MCPServer, api_keys_required |
| 27 | +from camel.utils import MCPServer |
28 | 28 |
|
29 | 29 | logger = get_logger(__name__) |
30 | 30 |
|
@@ -1049,57 +1049,98 @@ def _get_people_service(self): |
1049 | 1049 | except Exception as e: |
1050 | 1050 | raise ValueError(f"Failed to build People service: {e}") from e |
1051 | 1051 |
|
1052 | | - @api_keys_required( |
1053 | | - [ |
1054 | | - (None, "GOOGLE_CLIENT_ID"), |
1055 | | - (None, "GOOGLE_CLIENT_SECRET"), |
1056 | | - ] |
1057 | | - ) |
1058 | 1052 | def _authenticate(self): |
1059 | | - r"""Authenticate with Google APIs.""" |
1060 | | - client_id = os.environ.get('GOOGLE_CLIENT_ID') |
1061 | | - client_secret = os.environ.get('GOOGLE_CLIENT_SECRET') |
1062 | | - refresh_token = os.environ.get('GOOGLE_REFRESH_TOKEN') |
1063 | | - token_uri = os.environ.get( |
1064 | | - 'GOOGLE_TOKEN_URI', 'https://oauth2.googleapis.com/token' |
1065 | | - ) |
| 1053 | + r"""Authenticate with Google APIs using OAuth2. |
| 1054 | +
|
| 1055 | + Automatically saves and loads credentials from |
| 1056 | + ~/.camel/gmail_token.json to avoid repeated |
| 1057 | + browser logins. |
| 1058 | + """ |
| 1059 | + import json |
| 1060 | + from pathlib import Path |
1066 | 1061 |
|
| 1062 | + from dotenv import load_dotenv |
1067 | 1063 | from google.auth.transport.requests import Request |
1068 | 1064 | from google.oauth2.credentials import Credentials |
1069 | 1065 | from google_auth_oauthlib.flow import InstalledAppFlow |
1070 | 1066 |
|
1071 | | - # For first-time authentication |
1072 | | - if not refresh_token: |
1073 | | - client_config = { |
1074 | | - "installed": { |
1075 | | - "client_id": client_id, |
1076 | | - "client_secret": client_secret, |
1077 | | - "auth_uri": "https://accounts.google.com/o/oauth2/auth", |
1078 | | - "token_uri": token_uri, |
1079 | | - "redirect_uris": ["http://localhost"], |
1080 | | - } |
1081 | | - } |
| 1067 | + # Look for .env file in the project root (camel/) |
| 1068 | + env_file = Path(__file__).parent.parent.parent / '.env' |
| 1069 | + load_dotenv(env_file) |
1082 | 1070 |
|
1083 | | - flow = InstalledAppFlow.from_client_config(client_config, SCOPES) |
1084 | | - creds = flow.run_local_server(port=0) |
1085 | | - return creds |
1086 | | - else: |
1087 | | - # If we have a refresh token, use it to get credentials |
1088 | | - creds = Credentials( |
1089 | | - None, |
1090 | | - refresh_token=refresh_token, |
1091 | | - token_uri=token_uri, |
1092 | | - client_id=client_id, |
1093 | | - client_secret=client_secret, |
1094 | | - scopes=SCOPES, |
1095 | | - ) |
| 1071 | + client_id = os.environ.get('GOOGLE_CLIENT_ID') |
| 1072 | + client_secret = os.environ.get('GOOGLE_CLIENT_SECRET') |
1096 | 1073 |
|
1097 | | - # Refresh token if expired |
1098 | | - if creds.expired: |
1099 | | - creds.refresh(Request()) |
| 1074 | + token_file = Path.home() / '.camel' / 'gmail_token.json' |
| 1075 | + creds = None |
| 1076 | + |
| 1077 | + # COMPONENT 1: Load saved credentials |
| 1078 | + if token_file.exists(): |
| 1079 | + try: |
| 1080 | + with open(token_file, 'r') as f: |
| 1081 | + data = json.load(f) |
| 1082 | + creds = Credentials( |
| 1083 | + token=data.get('token'), |
| 1084 | + refresh_token=data.get('refresh_token'), |
| 1085 | + token_uri=data.get( |
| 1086 | + 'token_uri', 'https://oauth2.googleapis.com/token' |
| 1087 | + ), |
| 1088 | + client_id=client_id, |
| 1089 | + client_secret=client_secret, |
| 1090 | + scopes=SCOPES, |
| 1091 | + ) |
| 1092 | + except Exception as e: |
| 1093 | + logger.warning(f"Failed to load saved token: {e}") |
| 1094 | + creds = None |
1100 | 1095 |
|
| 1096 | + # COMPONENT 2: Refresh if expired |
| 1097 | + if creds and creds.expired and creds.refresh_token: |
| 1098 | + try: |
| 1099 | + creds.refresh(Request()) |
| 1100 | + logger.info("Access token refreshed") |
| 1101 | + return creds |
| 1102 | + except Exception as e: |
| 1103 | + logger.warning(f"Token refresh failed: {e}") |
| 1104 | + creds = None |
| 1105 | + |
| 1106 | + # COMPONENT 3: Return if valid |
| 1107 | + if creds and creds.valid: |
1101 | 1108 | return creds |
1102 | 1109 |
|
| 1110 | + # COMPONENT 4: Browser OAuth (first-time or invalid credentials) |
| 1111 | + client_config = { |
| 1112 | + "installed": { |
| 1113 | + "client_id": client_id, |
| 1114 | + "client_secret": client_secret, |
| 1115 | + "auth_uri": "https://accounts.google.com/o/oauth2/auth", |
| 1116 | + "token_uri": "https://oauth2.googleapis.com/token", |
| 1117 | + "redirect_uris": ["http://localhost"], |
| 1118 | + } |
| 1119 | + } |
| 1120 | + |
| 1121 | + flow = InstalledAppFlow.from_client_config(client_config, SCOPES) |
| 1122 | + creds = flow.run_local_server(port=0) |
| 1123 | + |
| 1124 | + # Save new credentials |
| 1125 | + token_file.parent.mkdir(parents=True, exist_ok=True) |
| 1126 | + with open(token_file, 'w') as f: |
| 1127 | + json.dump( |
| 1128 | + { |
| 1129 | + 'token': creds.token, |
| 1130 | + 'refresh_token': creds.refresh_token, |
| 1131 | + 'token_uri': creds.token_uri, |
| 1132 | + 'scopes': creds.scopes, |
| 1133 | + }, |
| 1134 | + f, |
| 1135 | + ) |
| 1136 | + try: |
| 1137 | + os.chmod(token_file, 0o600) |
| 1138 | + except Exception: |
| 1139 | + pass |
| 1140 | + logger.info(f"Credentials saved to {token_file}") |
| 1141 | + |
| 1142 | + return creds |
| 1143 | + |
1103 | 1144 | def _create_message( |
1104 | 1145 | self, |
1105 | 1146 | to_list: List[str], |
|
0 commit comments