Spaces:
No application file
No application file
| import os | |
| import secrets | |
| import time | |
| import json | |
| import base64 | |
| from flask_talisman import Talisman | |
| from flask import Flask, request, jsonify,render_template | |
| from slack_sdk import WebClient | |
| from slack_sdk.errors import SlackApiError | |
| from slack_bolt import App | |
| from slack_bolt.adapter.flask import SlackRequestHandler | |
| from slack_bolt.oauth.oauth_settings import OAuthSettings | |
| from dotenv import find_dotenv, load_dotenv | |
| from google.auth.transport.requests import Request | |
| from google.oauth2.credentials import Credentials | |
| from google_auth_oauthlib.flow import Flow | |
| from googleapiclient.discovery import build | |
| from flask_session import Session | |
| from msal import ConfidentialClientApplication | |
| import psycopg2 | |
| from apscheduler.schedulers.background import BackgroundScheduler | |
| from datetime import datetime, timedelta | |
| from collections import defaultdict | |
| import hashlib | |
| import re | |
| import logging | |
| from threading import Lock | |
| from urllib.parse import quote_plus | |
| from langchain.chains import LLMChain | |
| from langchain.prompts import ChatPromptTemplate | |
| from agents.all_agents import ( | |
| create_schedule_agent, create_update_agent, create_delete_agent, llm, | |
| create_schedule_group_agent, create_update_group_agent, create_schedule_channel_agent | |
| ) | |
| from all_tools import tools, calendar_prompt_tools | |
| from db import init_db | |
| # Load environment variables | |
| load_dotenv(find_dotenv()) | |
| os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' | |
| os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' | |
| os.environ['OAUTHLIB_IGNORE_SCOPE_CHANGE'] = '1' | |
| user_cache = {} | |
| user_cache_lock = Lock() # Example threading lock for cache | |
| preferences_cache = {} | |
| preferences_cache_lock = Lock() | |
| owner_id_cache = {} | |
| owner_id_lock = Lock() | |
| # Configuration | |
| SLACK_CLIENT_ID = os.getenv('SLACK_CLIENT_ID','') | |
| SLACK_CLIENT_SECRET = os.getenv('SLACK_CLIENT_SECRET','') | |
| SLACK_SIGNING_SECRET = os.getenv('SLACK_SIGNING_SECRET','') | |
| SLACK_SCOPES = [ | |
| "app_mentions:read", | |
| "channels:history", | |
| "chat:write", | |
| "users:read", | |
| "im:write", | |
| "groups:write", | |
| "mpim:write", | |
| "commands", | |
| "team:read", | |
| "channels:read", | |
| "groups:read", | |
| "im:read", | |
| "mpim:read", | |
| "groups:history", | |
| "im:history", | |
| "mpim:history" | |
| ] | |
| import requests | |
| SLACK_BOT_USER_ID = os.getenv("SLACK_BOT_USER_ID") | |
| ZOOM_REDIRECT_URI = "https://clear-muskox-grand.ngrok-free.app/zoom_callback" | |
| CLIENT_ID = "FiyFvBUSSeeXwjDv0tqg" # Zoom Client ID | |
| CLIENT_SECRET = "tygAN91Xd7Wo1YAH056wtbrXQ8I6UieA" # Zoom Client Secret | |
| ZOOM_TOKEN_API = "https://zoom.us/oauth/token" | |
| ZOOM_OAUTH_AUTHORIZE_API = os.getenv("ZOOM_OAUTH_AUTHORIZE_API", "https://zoom.us/oauth/authorize") | |
| OAUTH_REDIRECT_URI = os.getenv("OAUTH_REDIRECT_URI", "https://clear-muskox-grand.ngrok-free.app/oauth2callback") | |
| MICROSOFT_CLIENT_ID = "855e4571-d92a-4d51-802e-e712a879c00b" | |
| MICROSOFT_CLIENT_SECRET = os.getenv("MICROSOFT_CLIENT_SECRET") | |
| MICROSOFT_AUTHORITY = "https://login.microsoftonline.com/common" | |
| MICROSOFT_SCOPES = ["User.Read", "Calendars.ReadWrite"] | |
| MICROSOFT_REDIRECT_URI = os.getenv("MICROSOFT_REDIRECT_URI", "https://clear-muskox-grand.ngrok-free.app/microsoft_callback") | |
| # Initialize Flask app | |
| app = Flask(__name__) | |
| app.secret_key = secrets.token_hex(16) | |
| talisman = Talisman( | |
| app, | |
| content_security_policy={ | |
| 'default-src': "'self'", | |
| 'script-src': "'self'", | |
| 'object-src': "'none'" | |
| }, | |
| force_https=True, | |
| strict_transport_security=True, | |
| strict_transport_security_max_age=31536000, | |
| x_content_type_options=True, | |
| referrer_policy='no-referrer-when-downgrade' | |
| ) | |
| app.config['SESSION_TYPE'] = 'filesystem' | |
| Session(app) | |
| # Installation Store for OAuth | |
| import json | |
| import os | |
| import psycopg2 | |
| from datetime import datetime | |
| import json | |
| import os | |
| import psycopg2 | |
| from datetime import datetime | |
| from slack_sdk.oauth import InstallationStore | |
| # Custom JSON encoder to handle datetime objects | |
| import json | |
| import os | |
| import psycopg2 | |
| from psycopg2.extras import Json | |
| from datetime import datetime | |
| import logging | |
| from slack_sdk import WebClient | |
| from slack_sdk.oauth import InstallationStore | |
| from slack_sdk.oauth.installation_store.models.installation import Installation | |
| from slack_bolt.authorization import AuthorizeResult | |
| # Custom JSON encoder for datetime objects (used only if needed) | |
| class DateTimeEncoder(json.JSONEncoder): | |
| def default(self, obj): | |
| if isinstance(obj, datetime): | |
| return obj.isoformat() | |
| return super().default(obj) | |
| class DatabaseInstallationStore(InstallationStore): | |
| """A database-backed installation store for Slack Bolt using PostgreSQL. | |
| Assumes 'installation_data' is a jsonb column storing JSON data. | |
| """ | |
| def __init__(self): | |
| self._logger = logging.getLogger(__name__) | |
| def save(self, installation): | |
| try: | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| workspace_id = installation.team_id | |
| installed_at = datetime.fromtimestamp(installation.installed_at) if installation.installed_at else None | |
| installation_data = { | |
| "team_id": installation.team_id, | |
| "enterprise_id": installation.enterprise_id, | |
| "user_id": installation.user_id, | |
| "bot_token": installation.bot_token, | |
| "bot_id": installation.bot_id, | |
| "bot_user_id": installation.bot_user_id, | |
| "bot_scopes": installation.bot_scopes, | |
| "user_token": installation.user_token, | |
| "user_scopes": installation.user_scopes, | |
| "incoming_webhook_url": installation.incoming_webhook_url, | |
| "incoming_webhook_channel": installation.incoming_webhook_channel, | |
| "incoming_webhook_channel_id": installation.incoming_webhook_channel_id, | |
| "incoming_webhook_configuration_url": installation.incoming_webhook_configuration_url, | |
| "app_id": installation.app_id, | |
| "token_type": installation.token_type, | |
| "installed_at": installed_at.isoformat() if installed_at else None | |
| } | |
| current_time = datetime.now() | |
| cur.execute(''' | |
| INSERT INTO Installations (workspace_id, installation_data, updated_at) | |
| VALUES (%s, %s, %s) | |
| ON CONFLICT (workspace_id) DO UPDATE SET | |
| installation_data = %s, updated_at = %s | |
| ''', (workspace_id, Json(installation_data), current_time, Json(installation_data), current_time)) | |
| conn.commit() | |
| self._logger.info(f"Saved installation for workspace {workspace_id}") | |
| except Exception as e: | |
| self._logger.error(f"Failed to save installation for workspace {workspace_id}: {e}") | |
| raise | |
| finally: | |
| cur.close() | |
| conn.close() | |
| def find_installation(self, enterprise_id=None, team_id=None, user_id=None, is_enterprise_install=False): | |
| if not team_id: | |
| self._logger.warning("No team_id provided for find_installation") | |
| return None | |
| try: | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT installation_data FROM Installations WHERE workspace_id = %s', (team_id,)) | |
| row = cur.fetchone() | |
| if row: | |
| # For jsonb, row[0] is already a dict | |
| installation_data = row[0] | |
| installed_at = (datetime.fromisoformat(installation_data["installed_at"]) | |
| if installation_data.get("installed_at") else None) | |
| return Installation( | |
| app_id=installation_data["app_id"], | |
| enterprise_id=installation_data.get("enterprise_id"), | |
| team_id=installation_data["team_id"], | |
| bot_token=installation_data["bot_token"], | |
| bot_id=installation_data["bot_id"], | |
| bot_user_id=installation_data["bot_user_id"], | |
| bot_scopes=installation_data["bot_scopes"], | |
| user_id=installation_data["user_id"], | |
| user_token=installation_data.get("user_token"), | |
| user_scopes=installation_data.get("user_scopes"), | |
| incoming_webhook_url=installation_data.get("incoming_webhook_url"), | |
| incoming_webhook_channel=installation_data.get("incoming_webhook_channel"), | |
| incoming_webhook_channel_id=installation_data.get("incoming_webhook_channel_id"), | |
| incoming_webhook_configuration_url=installation_data.get("incoming_webhook_configuration_url"), | |
| token_type=installation_data["token_type"], | |
| installed_at=installed_at | |
| ) | |
| else: | |
| self._logger.info(f"No installation found for team_id {team_id}") | |
| return None | |
| except Exception as e: | |
| self._logger.error(f"Error retrieving installation for team_id {team_id}: {e}") | |
| return None | |
| finally: | |
| cur.close() | |
| conn.close() | |
| def find_bot(self, enterprise_id=None, team_id=None, is_enterprise_install=False): | |
| if not team_id: | |
| self._logger.warning("No team_id provided for find_bot") | |
| return None | |
| try: | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT installation_data FROM Installations WHERE workspace_id = %s', (team_id,)) | |
| row = cur.fetchone() | |
| if row: | |
| installation_data = row[0] | |
| return AuthorizeResult( | |
| enterprise_id=installation_data.get("enterprise_id"), | |
| team_id=installation_data["team_id"], | |
| bot_token=installation_data["bot_token"], | |
| bot_id=installation_data["bot_id"], | |
| bot_user_id=installation_data["bot_user_id"] | |
| ) | |
| else: | |
| self._logger.info(f"No bot installation found for team_id {team_id}") | |
| return None | |
| except Exception as e: | |
| self._logger.error(f"Error retrieving bot for team_id {team_id}: {e}") | |
| return None | |
| finally: | |
| cur.close() | |
| conn.close() | |
| # Instantiate the store | |
| installation_store = DatabaseInstallationStore() | |
| def get_client_for_team(team_id): | |
| """ | |
| Get a Slack WebClient for a given team ID using the stored bot token. | |
| Args: | |
| team_id (str): The team ID (workspace ID) to look up. | |
| Returns: | |
| WebClient: Slack client instance or None if not found. | |
| """ | |
| installation = installation_store.find_installation(None, team_id) | |
| if installation: | |
| token = installation.bot_token # Use dot notation instead of subscripting | |
| return WebClient(token=token) | |
| return None | |
| # Initialize Slack Bolt app with OAuth settings | |
| oauth_settings = OAuthSettings( | |
| client_id=SLACK_CLIENT_ID, | |
| client_secret=SLACK_CLIENT_SECRET, | |
| scopes=SLACK_SCOPES, | |
| redirect_uri="https://clear-muskox-grand.ngrok-free.app/slack/oauth_redirect", | |
| installation_store=installation_store | |
| ) | |
| bolt_app = App(signing_secret=SLACK_SIGNING_SECRET, oauth_settings=oauth_settings) | |
| slack_handler = SlackRequestHandler(bolt_app) | |
| # Logging setup | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Google Calendar API Scopes | |
| SCOPES = [ | |
| 'https://www.googleapis.com/auth/calendar', | |
| 'https://www.googleapis.com/auth/calendar.readonly', | |
| 'https://www.googleapis.com/auth/userinfo.email', | |
| 'https://www.googleapis.com/auth/userinfo.profile' | |
| ] | |
| # Initialize Neon Postgres database | |
| init_db() | |
| # State Management Classes | |
| class StateManager: | |
| def __init__(self): | |
| self._states = {} | |
| self._lock = Lock() | |
| def create_state(self, user_id): | |
| with self._lock: | |
| state_token = secrets.token_urlsafe(32) | |
| self._states[state_token] = {"user_id": user_id, "timestamp": datetime.now(), "used": False} | |
| return state_token | |
| def validate_and_consume_state(self, state_token): | |
| with self._lock: | |
| if state_token not in self._states: | |
| return None | |
| state_data = self._states[state_token] | |
| if state_data["used"] or (datetime.now() - state_data["timestamp"]).total_seconds() > 600: | |
| del self._states[state_token] | |
| return None | |
| state_data["used"] = True | |
| return state_data["user_id"] | |
| def cleanup_expired_states(self): | |
| with self._lock: | |
| current_time = datetime.now() | |
| expired = [s for s, d in self._states.items() if (current_time - d["timestamp"]).total_seconds() > 600] | |
| for state in expired: | |
| del self._states[state] | |
| state_manager = StateManager() | |
| class EventDeduplicator: | |
| def __init__(self, expiration_minutes=5): | |
| self.processed_events = defaultdict(list) | |
| self.expiration_minutes = expiration_minutes | |
| def clean_expired_events(self): | |
| current_time = datetime.now() | |
| for event_id in list(self.processed_events.keys()): | |
| events = [(t, h) for t, h in self.processed_events[event_id] | |
| if current_time - t < timedelta(minutes=self.expiration_minutes)] | |
| if events: | |
| self.processed_events[event_id] = events | |
| else: | |
| del self.processed_events[event_id] | |
| def is_duplicate_event(self, event_payload): | |
| self.clean_expired_events() | |
| event_id = event_payload.get('event_id', '') | |
| payload_hash = hashlib.md5(str(event_payload).encode('utf-8')).hexdigest() | |
| if 'challenge' in event_payload: | |
| return False | |
| if event_id in self.processed_events and payload_hash in [h for _, h in self.processed_events[event_id]]: | |
| return True | |
| self.processed_events[event_id].append((datetime.now(), payload_hash)) | |
| return False | |
| event_deduplicator = EventDeduplicator() | |
| class SessionStore: | |
| def __init__(self): | |
| self._store = {} | |
| self._lock = Lock() | |
| def set(self, user_id, key, value): | |
| with self._lock: | |
| if user_id not in self._store: | |
| self._store[user_id] = {} | |
| self._store[user_id][key] = {"value": value, "expires_at": datetime.now() + timedelta(hours=1)} | |
| def get(self, user_id, key, default=None): | |
| with self._lock: | |
| if user_id not in self._store or key not in self._store[user_id]: | |
| return default | |
| session_data = self._store[user_id][key] | |
| if datetime.now() > session_data["expires_at"]: | |
| del self._store[user_id][key] | |
| return default | |
| return session_data["value"] | |
| def clear(self, user_id, key): | |
| with self._lock: | |
| if user_id in self._store and key in self._store[user_id]: | |
| del self._store[user_id][key] | |
| session_store = SessionStore() | |
| def store_in_session(user_id, key_type, data): | |
| session_store.set(user_id, key_type, data) | |
| def get_from_session(user_id, key_type, default=None): | |
| return session_store.get(user_id, key_type, default) | |
| # Global Caches (per workspace) | |
| user_cache = {} # {team_id: {user_id: user_data}} | |
| user_cache_lock = Lock() | |
| owner_id_cache = {} # {team_id: owner_id} | |
| owner_id_lock = Lock() | |
| preferences_cache = {} | |
| preferences_cache_lock = Lock() | |
| # Database Helper Functions | |
| def save_preference(team_id, user_id, zoom_config=None, calendar_tool=None): | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT zoom_config, calendar_tool FROM Preferences WHERE team_id = %s AND user_id = %s', (team_id, user_id)) | |
| existing = cur.fetchone() | |
| if existing: | |
| current_zoom_config, current_calendar_tool = existing | |
| new_zoom_config = zoom_config if zoom_config is not None else current_zoom_config | |
| new_calendar_tool = calendar_tool if calendar_tool is not None else current_calendar_tool | |
| cur.execute(''' | |
| UPDATE Preferences | |
| SET zoom_config = %s, calendar_tool = %s, updated_at = %s | |
| WHERE team_id = %s AND user_id = %s | |
| ''', (json.dumps(new_zoom_config) if new_zoom_config else None, | |
| new_calendar_tool, datetime.now(), team_id, user_id)) | |
| else: | |
| new_zoom_config = zoom_config or {"mode": "manual", "link": None} | |
| new_calendar_tool = calendar_tool or "google" | |
| cur.execute(''' | |
| INSERT INTO Preferences (team_id, user_id, zoom_config, calendar_tool, updated_at) | |
| VALUES (%s, %s, %s, %s, %s) | |
| ''', (team_id, user_id, json.dumps(new_zoom_config), new_calendar_tool, datetime.now())) | |
| conn.commit() | |
| cur.close() | |
| conn.close() | |
| with preferences_cache_lock: | |
| preferences_cache[(team_id, user_id)] = {"zoom_config": new_zoom_config, "calendar_tool": new_calendar_tool} | |
| def load_preferences(team_id, user_id): | |
| with preferences_cache_lock: | |
| if (team_id, user_id) in preferences_cache: | |
| return preferences_cache[(team_id, user_id)] | |
| try: | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT zoom_config, calendar_tool FROM Preferences WHERE team_id = %s AND user_id = %s', (team_id, user_id)) | |
| row = cur.fetchone() | |
| if row: | |
| zoom_config, calendar_tool = row | |
| # For jsonb, zoom_config is already a dict; no json.loads needed | |
| preferences = { | |
| "zoom_config": zoom_config if zoom_config else {"mode": "manual", "link": None}, | |
| "calendar_tool": calendar_tool or "none" | |
| } | |
| else: | |
| preferences = {"zoom_config": {"mode": "manual", "link": None}, "calendar_tool": "none"} | |
| cur.close() | |
| conn.close() | |
| except Exception as e: | |
| logger.error(f"Failed to load preferences for team {team_id}, user {user_id}: {e}") | |
| preferences = {"zoom_config": {"mode": "manual", "link": None}, "calendar_tool": "none"} | |
| with preferences_cache_lock: | |
| preferences_cache[(team_id, user_id)] = preferences | |
| return preferences | |
| def save_token(team_id, user_id, service, token_data): | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute(''' | |
| INSERT INTO Tokens (team_id, user_id, service, token_data, updated_at) | |
| VALUES (%s, %s, %s, %s, %s) | |
| ON CONFLICT (team_id, user_id, service) DO UPDATE SET token_data = %s, updated_at = %s | |
| ''', (team_id, user_id, service, json.dumps(token_data), datetime.now(), json.dumps(token_data), datetime.now())) | |
| conn.commit() | |
| cur.close() | |
| conn.close() | |
| def load_token(team_id, user_id, service): | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT token_data FROM Tokens WHERE team_id = %s AND user_id = %s AND service = %s', (team_id, user_id, service)) | |
| row = cur.fetchone() | |
| cur.close() | |
| conn.close() | |
| return row[0] if row else None | |
| # Utility Functions | |
| def initialize_workspace_cache(client, team_id): | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute('SELECT MAX(last_updated) FROM Users WHERE team_id = %s', (team_id,)) | |
| last_updated_row = cur.fetchone() | |
| last_updated = last_updated_row[0] if last_updated_row and last_updated_row[0] else None | |
| # Check if cache is fresh (e.g., less than 24 hours old) | |
| if last_updated and (datetime.now() - last_updated).total_seconds() < 86400: | |
| cur.execute('SELECT user_id, real_name, email, name, is_owner, workspace_name FROM Users WHERE team_id = %s', (team_id,)) | |
| rows = cur.fetchall() | |
| new_cache = {row[0]: {"real_name": row[1], "email": row[2], "name": row[3], "is_owner": row[4], "workspace_name": row[5]} for row in rows} | |
| with user_cache_lock: | |
| user_cache[team_id] = new_cache | |
| with owner_id_lock: | |
| owner_id_cache[team_id] = next((user_id for user_id, data in new_cache.items() if data['is_owner']), None) | |
| else: | |
| # Fetch user data from Slack and update database | |
| response = client.users_list() | |
| users = response["members"] | |
| workspace_name = client.team_info()["team"]["name"] # Get workspace name from Slack API | |
| new_cache = {} | |
| for user in users: | |
| user_id = user['id'] | |
| profile = user.get('profile', {}) | |
| real_name = profile.get('real_name', 'Unknown') | |
| name = user.get('name', '') | |
| email = f"{name}@gmail.com" # Placeholder; adjust as needed | |
| is_owner = user.get('is_owner', False) | |
| new_cache[user_id] = {"real_name": real_name, "email": email, "name": name, "is_owner": is_owner, "workspace_name": workspace_name} | |
| cur.execute(''' | |
| INSERT INTO Users (team_id, user_id, workspace_name, real_name, email, name, is_owner, last_updated) | |
| VALUES (%s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (team_id, user_id) DO UPDATE SET | |
| workspace_name = %s, real_name = %s, email = %s, name = %s, is_owner = %s, last_updated = %s | |
| ''', (team_id, user_id, workspace_name, real_name, email, name, is_owner, datetime.now(), | |
| workspace_name, real_name, email, name, is_owner, datetime.now())) | |
| conn.commit() | |
| with user_cache_lock: | |
| user_cache[team_id] = new_cache | |
| with owner_id_lock: | |
| owner_id_cache[team_id] = next((user_id for user_id, data in new_cache.items() if data['is_owner']), None) | |
| cur.close() | |
| conn.close() | |
| def get_all_users(team_id): | |
| with user_cache_lock: | |
| if team_id in user_cache: | |
| return {k: {"Slack Id": k, "real_name": v["real_name"], "email": v["email"], "name": v["name"]} | |
| for k, v in user_cache[team_id].items()} | |
| return {} | |
| def get_workspace_owner_id(client, team_id): | |
| with owner_id_lock: | |
| if team_id in owner_id_cache and owner_id_cache[team_id]: | |
| return owner_id_cache[team_id] | |
| initialize_workspace_cache(client, team_id) | |
| with owner_id_lock: | |
| return owner_id_cache.get(team_id) | |
| def get_channel_owner_id(client, channel_id): | |
| try: | |
| response = client.conversations_info(channel=channel_id) | |
| return response["channel"].get("creator") | |
| except SlackApiError as e: | |
| logger.error(f"Error fetching channel info: {e.response['error']}") | |
| return None | |
| def get_user_timezone(client, user_id): | |
| try: | |
| response = client.users_info(user=user_id) | |
| return response["user"].get("tz", "UTC") | |
| except SlackApiError as e: | |
| logger.error(f"Timezone error: {e.response['error']}") | |
| return "UTC" | |
| def get_team_id_from_owner_id(owner_id): | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| cur.execute("SELECT workspace_id FROM Installations WHERE installation_data->>'user_id' = %s", (owner_id,)) | |
| row = cur.fetchone() | |
| cur.close() | |
| conn.close() | |
| return row[0] if row else None | |
| # def get_client_for_team(team_id): | |
| # installation = installation_store.find_installation(None, team_id) | |
| # if installation: | |
| # print(installation) | |
| # token = installation['bot_token'] | |
| # return WebClient(token=token) | |
| # return None | |
| def get_owner_selected_calendar(client, team_id): | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if not owner_id: | |
| return None | |
| # Fixed: Pass both team_id and owner_id to load_preferences | |
| prefs = load_preferences(team_id, owner_id) | |
| return prefs.get("calendar_tool", "none") | |
| def get_zoom_link(client, team_id): | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if not owner_id: | |
| return None | |
| prefs = load_preferences(team_id,owner_id) | |
| return prefs.get('zoom_config', {}).get('link') | |
| def create_home_tab(client, team_id, user_id): | |
| logger.info(f"Creating home tab for user {user_id}, team {team_id}") | |
| # Get workspace owner ID | |
| workspace_owner_id = get_workspace_owner_id(client, team_id) | |
| if not workspace_owner_id: | |
| logger.warning(f"No workspace owner for team {team_id}") | |
| blocks = [ | |
| {"type": "header", "text": {"type": "plain_text", "text": "🤖 Welcome to AI Assistant!", "emoji": True}}, | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "Unable to determine workspace owner. Please contact support."}}, | |
| ] | |
| return {"type": "home", "blocks": blocks} | |
| # Determine if the user is the workspace owner | |
| is_owner = user_id == workspace_owner_id | |
| # Base blocks for all users | |
| blocks = [ | |
| {"type": "header", "text": {"type": "plain_text", "text": "🤖 Welcome to AI Assistant!", "emoji": True}} | |
| ] | |
| # Non-owner view | |
| if not is_owner: | |
| blocks.extend([ | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "I help manage schedules and meetings! Please wait for the workspace owner to configure the settings."}}, | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "Only the workspace owner can configure the calendar and Zoom settings."}} | |
| ]) | |
| return {"type": "home", "blocks": blocks} | |
| # Owner view: Add configuration options | |
| blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "I help manage schedules and meetings! Your settings are below."}}) | |
| blocks.append({"type": "divider"}) | |
| # Load preferences and tokens | |
| prefs = load_preferences(team_id, workspace_owner_id) | |
| selected_provider = prefs.get("calendar_tool", "none") | |
| zoom_config = prefs.get("zoom_config", {"mode": "manual", "link": None}) | |
| mode = zoom_config["mode"] | |
| calendar_token = load_token(team_id, workspace_owner_id, selected_provider) if selected_provider != "none" else None | |
| zoom_token = load_token(team_id, workspace_owner_id, "zoom") if mode == "automatic" else None | |
| logger.info(f"Preferences loaded: {prefs}, Calendar token: {calendar_token}, Zoom token: {zoom_token}") | |
| # Check Zoom token expiration | |
| zoom_token_expired = False | |
| if zoom_token and mode == "automatic": | |
| expires_at = zoom_token.get("expires_at", 0) | |
| current_time = time.time() | |
| zoom_token_expired = current_time >= expires_at | |
| # Configuration status | |
| calendar_provider_set = selected_provider != "none" | |
| calendar_configured = calendar_token is not None if calendar_provider_set else False | |
| zoom_configured = (zoom_token is not None and not zoom_token_expired) if mode == "automatic" else True | |
| # Setup prompt if configurations are incomplete | |
| if not calendar_provider_set or not calendar_configured or not zoom_configured: | |
| prompt_text = "To start using the app, please complete the following setups:" | |
| if not calendar_provider_set: | |
| prompt_text += "\n- Select a calendar provider." | |
| if calendar_provider_set and not calendar_configured: | |
| prompt_text += f"\n- Configure your {selected_provider.capitalize()} calendar." | |
| if mode == "automatic" and not zoom_configured: | |
| if zoom_token_expired: | |
| prompt_text += "\n- Your Zoom token has expired. Please refresh it." | |
| else: | |
| prompt_text += "\n- Authenticate with Zoom for automatic mode." | |
| blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": prompt_text}}) | |
| # Calendar Configuration Section | |
| blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "*🗓️ Calendar Configuration*"}}) | |
| blocks.append({ | |
| "type": "section", | |
| "block_id": "calendar_provider_block", | |
| "text": {"type": "mrkdwn", "text": "Select your calendar provider:"}, | |
| "accessory": { | |
| "type": "static_select", | |
| "action_id": "calendar_provider_dropdown", | |
| "placeholder": {"type": "plain_text", "text": "Select provider"}, | |
| "options": [ | |
| {"text": {"type": "plain_text", "text": "Select calendar"}, "value": "none"}, | |
| {"text": {"type": "plain_text", "text": "Google Calendar"}, "value": "google"}, | |
| {"text": {"type": "plain_text", "text": "Microsoft Calendar"}, "value": "microsoft"} | |
| ], | |
| "initial_option": { | |
| "text": {"type": "plain_text", "text": "Select calendar" if selected_provider == "none" else | |
| "Google Calendar" if selected_provider == "google" else "Microsoft Calendar"}, | |
| "value": selected_provider | |
| } | |
| } | |
| }) | |
| # Calendar configuration prompts | |
| if selected_provider == "none": | |
| blocks.append({"type": "context", "elements": [{"type": "mrkdwn", "text": "Please select a calendar provider to begin configuration."}]}) | |
| elif not calendar_configured: | |
| blocks.append({"type": "context", "elements": [{"type": "mrkdwn", "text": f"Please configure your {selected_provider.capitalize()} calendar."}]}) | |
| # Calendar configure button and status | |
| if selected_provider != "none": | |
| status = "⚠️ Not Configured" if not calendar_configured else ( | |
| f":white_check_mark: Connected ({calendar_token.get('google_email', 'unknown')})" if selected_provider == "google" else ( | |
| f":white_check_mark: Connected (expires: {datetime.fromtimestamp(int(calendar_token.get('expires_at', 0))).strftime('%Y-%m-%d %H:%M')})" if calendar_token and calendar_token.get('expires_at') else ":white_check_mark: Connected" | |
| ) | |
| ) | |
| blocks.extend([ | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": f"✨ Configure {selected_provider.capitalize()}" if not calendar_configured else f"✅ Reconfigure {selected_provider.capitalize()}", | |
| "emoji": True | |
| }, | |
| "action_id": "configure_gcal" if selected_provider == "google" else "configure_mscal" | |
| } | |
| ] | |
| }, | |
| {"type": "context", "elements": [{"type": "mrkdwn", "text": status}]} | |
| ]) | |
| # Zoom Configuration Section | |
| status = ("⌛ Token Expired" if zoom_token_expired else | |
| "⚠️ Not Configured" if mode == "automatic" and not zoom_configured else | |
| "✅ Configured") | |
| blocks.extend([ | |
| {"type": "divider"}, | |
| {"type": "section", "text": {"type": "mrkdwn", "text": f"*🔗 Zoom Configuration*\nCurrent mode: {mode}\n{status}"}}, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| {"type": "button", "text": {"type": "plain_text", "text": "Configure Zoom Settings", "emoji": True}, "action_id": "open_zoom_config_modal"} | |
| ] | |
| } | |
| ]) | |
| # Zoom authentication/refresh button | |
| if mode == "automatic": | |
| if not zoom_configured and not zoom_token_expired: | |
| blocks[-1]["elements"].append({ | |
| "type": "button", | |
| "text": {"type": "plain_text", "text": "Authenticate with Zoom", "emoji": True}, | |
| "action_id": "configure_zoom" | |
| }) | |
| elif zoom_token_expired: | |
| blocks[-1]["elements"].append({ | |
| "type": "button", | |
| "text": {"type": "plain_text", "text": "Refresh Zoom Token", "emoji": True}, | |
| "action_id": "configure_zoom" # Same action_id for refresh | |
| }) | |
| return {"type": "home", "blocks": blocks} | |
| # Intent Classification | |
| intent_prompt = ChatPromptTemplate.from_template(""" | |
| You are an intent classification assistant. Based on the user's message and the conversation history, determine the intent of the user's request. The possible intents are: "schedule meeting", "update event", "delete event", or "other". Provide only the intent as your response. | |
| - By looking at the history if someone is confirming or denying the schedule , also categorize it as a "schedule meeting" | |
| - If someone is asking about update the schedule then its "update event" | |
| - If someone is asking about delete the schedule then its "delete event" | |
| Conversation History: | |
| {history} | |
| User's Message: | |
| {input} | |
| """) | |
| from prompt import calender_prompt, general_prompt | |
| intent_chain = LLMChain(llm=llm, prompt=intent_prompt) | |
| mentioned_users_prompt = ChatPromptTemplate.from_template(""" | |
| Given the following chat history, identify the Slack user IDs, Names and emails of the users who are mentioned. Mentions can be in the form of <@user_id> (e.g., <@U12345>) or by their names (e.g., "Alice" or "Alice Smith"). | |
| - Do not give 'Bob'<@{bob_id}> in mentions | |
| - Exclude the {bob_id}. | |
| # See the history if there is a request for new meeting or request for new schedule just ignore the mentions in the old messages and consider the new mentions in the new request. | |
| All users in the channel: | |
| {user_information} | |
| Format: Slack Id: U3234234 , Name: Alice , Email: alice@gmail.com (map slack ids to the names) | |
| Chat history: | |
| {chat_history} | |
| # Only output the users which are mentioned not all the users from the user-information. | |
| # Only see the latest message for mention information ignore previous ones. | |
| Please output the user slack IDs of the mentioned users , their names and emails . If no users are mentioned, output "None". | |
| CURRENT_INPUT: {current_input} | |
| Example: [[SlackId1 , Name1 , Email@gmal.com], [SlackId2, Name2, Email@gmail.com]...] | |
| """) | |
| mentioned_users_chain = LLMChain(llm=llm, prompt=mentioned_users_prompt) | |
| # Slack Event Handlers | |
| def handle_app_home_opened(event, client, context): | |
| user_id = event.get("user") | |
| team_id = context['team_id'] | |
| if not user_id: | |
| return | |
| try: | |
| client.views_publish(user_id=user_id, view=create_home_tab(client, team_id, user_id)) | |
| except Exception as e: | |
| logger.error(f"Error publishing home tab: {e}") | |
| def handle_calendar_provider(ack, body, client, logger): | |
| ack() | |
| selected_provider = body["actions"][0]["selected_option"]["value"] | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| client.chat_postMessage(channel=user_id, text="Only the workspace owner can configure the calendar.") | |
| return | |
| # Corrected line: pass both team_id and owner_id (user_id) parameters | |
| save_preference(team_id, owner_id, calendar_tool=selected_provider) | |
| client.views_publish(user_id=owner_id, view=create_home_tab(client, team_id, owner_id)) | |
| if selected_provider != "none": | |
| client.chat_postMessage(channel=owner_id, text=f"Calendar provider updated to {selected_provider.capitalize()}.") | |
| else: | |
| client.chat_postMessage(channel=owner_id, text="Calendar provider reset.") | |
| logger.info(f"Calendar provider updated to {selected_provider} for owner {owner_id}") | |
| def handle_gcal_config(ack, body, client, logger): | |
| ack() | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| client.chat_postMessage(channel=user_id, text="Only the workspace owner can configure the calendar.") | |
| return | |
| # Generate and store the state using StateManager | |
| state = state_manager.create_state(owner_id) | |
| print(f"state stored: {state}") | |
| store_in_session(owner_id, "gcal_state", state) # Optional: for additional validation | |
| # Set up the OAuth flow and pass the state | |
| flow = Flow.from_client_secrets_file('credentials.json', scopes=SCOPES, redirect_uri=OAUTH_REDIRECT_URI) | |
| auth_url, _ = flow.authorization_url( | |
| access_type='offline', | |
| prompt='consent', | |
| include_granted_scopes='true', | |
| state=state # Use the state from StateManager | |
| ) | |
| # Open the modal with the auth URL | |
| try: | |
| client.views_open( | |
| trigger_id=body["trigger_id"], | |
| view={ | |
| "type": "modal", | |
| "title": {"type": "plain_text", "text": "Google Calendar Auth"}, | |
| "close": {"type": "plain_text", "text": "Cancel"}, | |
| "blocks": [ | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "Click below to connect Google Calendar:"}}, | |
| {"type": "actions", "elements": [{"type": "button", "text": {"type": "plain_text", "text": "Connect Google Calendar"}, "url": auth_url, "action_id": "launch_auth"}]} | |
| ] | |
| } | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error opening modal: {e}") | |
| def handle_mscal_config(ack, body, client, logger): | |
| ack() | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| client.chat_postMessage(channel=user_id, text="Only the workspace owner can configure the calendar.") | |
| return | |
| msal_app = ConfidentialClientApplication(MICROSOFT_CLIENT_ID, authority=MICROSOFT_AUTHORITY, client_credential=MICROSOFT_CLIENT_SECRET) | |
| state = state_manager.create_state(owner_id) | |
| auth_url = msal_app.get_authorization_request_url(scopes=MICROSOFT_SCOPES, redirect_uri=MICROSOFT_REDIRECT_URI, state=state) | |
| try: | |
| client.views_open( | |
| trigger_id=body["trigger_id"], | |
| view={ | |
| "type": "modal", | |
| "title": {"type": "plain_text", "text": "Microsoft Calendar Auth"}, | |
| "close": {"type": "plain_text", "text": "Close"}, | |
| "blocks": [ | |
| {"type": "section", "text": {"type": "mrkdwn", "text": "Click below to authenticate with Microsoft:"}}, | |
| {"type": "actions", "elements": [{"type": "button", "text": {"type": "plain_text", "text": "Connect Microsoft Calendar"}, "url": auth_url, "action_id": "ms_auth_button"}]} | |
| ] | |
| } | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error opening Microsoft auth modal: {e}") | |
| def handle_mentions(event, say, client, context): | |
| if event_deduplicator.is_duplicate_event(event): | |
| logger.info("Duplicate event detected, skipping processing") | |
| return | |
| # Ignore messages from bots | |
| if event.get("bot_id"): | |
| logger.info("Ignoring message from bot") | |
| return | |
| user_id = event.get("user") | |
| channel_id = event.get("channel") | |
| text = event.get("text", "").strip() | |
| thread_ts = event.get("thread_ts") | |
| team_id = context['team_id'] | |
| calendar_tool = get_owner_selected_calendar(client, team_id) | |
| if not calendar_tool or calendar_tool == "none": | |
| say("The workspace owner has not configured a calendar yet.", thread_ts=thread_ts) | |
| return | |
| # Fetch bot_user_id dynamically from installation | |
| installation = installation_store.find_installation(team_id=team_id) | |
| if not installation or not installation.bot_user_id: | |
| logger.error(f"No bot_user_id found for team {team_id}") | |
| say("Error: Could not determine bot user ID.", thread_ts=thread_ts) | |
| return | |
| print(f"App mention events") | |
| bot_user_id = installation.bot_user_id | |
| print(f"Bot user id: {bot_user_id}") | |
| mention = f"<@{bot_user_id}>" | |
| mentions = list(set(re.findall(r'<@(\w+)>', text))) | |
| if bot_user_id in mentions: | |
| mentions.remove(bot_user_id) | |
| text = text.replace(mention, "").strip() | |
| workspace_owner_id = get_workspace_owner_id(client, team_id) | |
| timezone = get_user_timezone(client, user_id) | |
| zoom_link = get_zoom_link(client, team_id) | |
| zoom_mode = load_preferences(team_id, workspace_owner_id).get("zoom_config", {}).get("mode", "manual") | |
| channel_history = client.conversations_history(channel=channel_id, limit=2).get("messages", []) | |
| channel_history = format_channel_history(channel_history) | |
| intent = intent_chain.run({"history": channel_history, "input": text}) | |
| relevant_user_ids = get_relevant_user_ids(client, channel_id) | |
| all_users = get_all_users(team_id) | |
| relevant_users = {uid: all_users.get(uid, {"real_name": "Unknown", "email": "unknown@example.com", "name": "Unknown"}) | |
| for uid in relevant_user_ids} | |
| user_information = "\n".join([f"{uid}: Name={info['real_name']}, Email={info['email']}, Slack Name={info['name']}" | |
| for uid, info in relevant_users.items() if uid != bot_user_id]) | |
| print(f"User Information: {user_information}\n\nRelevant Users: {relevant_user_ids}\n\n All users: {all_users}") | |
| mentioned_users_output = mentioned_users_chain.run({"user_information": user_information, "chat_history": channel_history, "current_input": text, 'bob_id': bot_user_id}) | |
| import pytz | |
| pst = pytz.timezone('America/Los_Angeles') | |
| current_time_pst = datetime.now(pst) | |
| formatted_time = current_time_pst.strftime("%Y-%m-%d | %A | %I:%M %p | %Z") | |
| from all_tools import GoogleCalendarEvents, MicrosoftListCalendarEvents | |
| if calendar_tool == "google": | |
| calendar_events = GoogleCalendarEvents()._run(team_id, workspace_owner_id) | |
| schedule_tools = [tools[i] for i in [0, 1, 4, 6, 12]] | |
| update_tools = [tools[i] for i in [0, 7, 12]] | |
| delete_tools = [tools[i] for i in [0, 8, 12]] | |
| elif calendar_tool == "microsoft": | |
| calendar_events = MicrosoftListCalendarEvents()._run(team_id, workspace_owner_id) | |
| schedule_tools = [tools[i] for i in [0, 1, 9, 12]] | |
| update_tools = [tools[i] for i in [0, 10, 12]] | |
| delete_tools = [tools[i] for i in [0, 11, 12]] | |
| else: | |
| say("Invalid calendar tool configured.", thread_ts=thread_ts) | |
| return | |
| calendar_formatting_chain = LLMChain(llm=llm, prompt=calender_prompt) | |
| output = calendar_formatting_chain.run({'input': calendar_events, 'admin_id': workspace_owner_id, 'date_time': formatted_time}) | |
| print(f"MENTIONED USERS:{mentioned_users_output}") | |
| agent_input = { | |
| 'input': f"Here is the input by user: {text} and do not mention <@{bot_user_id}> even tho mentioned in history", | |
| 'event_details': str(event), | |
| 'target_user_id': user_id, | |
| 'timezone': timezone, | |
| 'user_id': user_id, | |
| 'admin': workspace_owner_id, | |
| 'zoom_link': zoom_link, | |
| 'zoom_mode': zoom_mode, | |
| 'channel_history': channel_history, | |
| 'user_information': user_information, | |
| 'calendar_tool': calendar_tool, | |
| 'date_time': formatted_time, | |
| 'formatted_calendar': output, | |
| 'team_id': team_id | |
| } | |
| mentions = list(set(re.findall(r'<@(\w+)>', text))) | |
| if bot_user_id in mentions: | |
| mentions.remove(bot_user_id) | |
| schedule_group_exec = create_schedule_channel_agent(schedule_tools) | |
| update_group_exec = create_update_group_agent(update_tools) | |
| delete_exec = create_delete_agent(delete_tools) | |
| if intent == "schedule meeting": | |
| group_agent_input = agent_input.copy() | |
| group_agent_input['mentioned_users'] = mentioned_users_output | |
| response = schedule_group_exec.invoke(group_agent_input) | |
| say(response['output']) | |
| return | |
| elif intent == "update event": | |
| group_agent_input = agent_input.copy() | |
| group_agent_input['mentioned_users'] = mentioned_users_output | |
| response = update_group_exec.invoke(group_agent_input) | |
| say(response['output']) | |
| return | |
| elif intent == "delete event": | |
| response = delete_exec.invoke(agent_input) | |
| say(response['output']) | |
| return | |
| elif intent == "other": | |
| response = llm.predict(general_prompt.format(input=text, channel_history=channel_history)) | |
| say(response) | |
| return | |
| else: | |
| say("I'm not sure how to handle that request.") | |
| # @bolt_app.event("app_mention") | |
| # def handle_mentions(event, say, client, context): | |
| # if event_deduplicator.is_duplicate_event(event): | |
| # logger.info("Duplicate event detected, skipping processing") | |
| # return | |
| # # Ignore messages from bots | |
| # if event.get("bot_id"): | |
| # logger.info("Ignoring message from bot") | |
| # return | |
| # user_id = event.get("user") | |
| # channel_id = event.get("channel") | |
| # text = event.get("text", "").strip() | |
| # thread_ts = event.get("thread_ts") | |
| # team_id = context['team_id'] | |
| # calendar_tool = get_owner_selected_calendar(client, team_id) | |
| # if not calendar_tool or calendar_tool == "none": | |
| # say("The workspace owner has not configured a calendar yet.", thread_ts=thread_ts) | |
| # return | |
| # # Fetch bot_user_id dynamically from installation | |
| # installation = installation_store.find_installation(team_id=team_id) | |
| # if not installation or not installation.bot_user_id: | |
| # logger.error(f"No bot_user_id found for team {team_id}") | |
| # say("Error: Could not determine bot user ID.", thread_ts=thread_ts) | |
| # return | |
| # print(f"App mention events") | |
| # bot_user_id = installation.bot_user_id | |
| # print(f"Bot user id: {bot_user_id}") | |
| # mention = f"<@{bot_user_id}>" | |
| # mentions = list(set(re.findall(r'<@(\w+)>', text))) | |
| # # Use dynamic bot_user_id instead of SLACK_BOT_USER_ID | |
| # if bot_user_id in mentions: | |
| # mentions.remove(bot_user_id) | |
| # text = text.replace(mention, "").strip() | |
| # workspace_owner_id = get_workspace_owner_id(client, team_id) | |
| # timezone = get_user_timezone(client, user_id) | |
| # zoom_link = get_zoom_link(client, team_id) | |
| # zoom_mode = load_preferences(team_id, workspace_owner_id).get("zoom_config", {}).get("mode", "manual") | |
| # channel_history = client.conversations_history(channel=channel_id, limit=2).get("messages", []) | |
| # channel_history = format_channel_history(channel_history) | |
| # intent = intent_chain.run({"history": channel_history, "input": text}) | |
| # relevant_user_ids = get_relevant_user_ids(client, channel_id) | |
| # all_users = get_all_users(team_id) | |
| # relevant_users = {uid: all_users.get(uid, {"real_name": "Unknown", "email": "unknown@example.com", "name": "Unknown"}) | |
| # for uid in relevant_user_ids} | |
| # user_information = "\n".join([f"{uid}: Name={info['real_name']}, Email={info['email']}, Slack Name={info['name']}" | |
| # for uid, info in relevant_users.items() if uid != bot_user_id]) | |
| # print(f"User Information: {user_information}\n\nRelevant Users: {relevant_user_ids}\n\n All users: {all_users}") | |
| # mentioned_users_output = mentioned_users_chain.run({"user_information": user_information, "chat_history": channel_history,"current_input":text, 'bob_id':bot_user_id}) | |
| # import pytz | |
| # pst = pytz.timezone('America/Los_Angeles') | |
| # current_time_pst = datetime.now(pst) | |
| # formatted_time = current_time_pst.strftime("%Y-%m-%d | %A | %I:%M %p | %Z") | |
| # from all_tools import GoogleCalendarEvents, MicrosoftListCalendarEvents | |
| # if calendar_tool == "google": | |
| # calendar_events = GoogleCalendarEvents()._run(team_id, workspace_owner_id) | |
| # schedule_tools = [tools[i] for i in [0, 1, 4, 6, 12]] | |
| # update_tools = [tools[i] for i in [0, 7, 12]] | |
| # delete_tools = [tools[i] for i in [0, 8, 12]] | |
| # elif calendar_tool == "microsoft": | |
| # calendar_events = MicrosoftListCalendarEvents()._run(team_id, workspace_owner_id) | |
| # schedule_tools = [tools[i] for i in [0, 1, 9, 12]] | |
| # update_tools = [tools[i] for i in [0, 10, 12]] | |
| # delete_tools = [tools[i] for i in [0, 11, 12]] | |
| # else: | |
| # say("Invalid calendar tool configured.", thread_ts=thread_ts) | |
| # return | |
| # calendar_formatting_chain = LLMChain(llm=llm, prompt=calender_prompt) | |
| # output = calendar_formatting_chain.run({'input': calendar_events, 'admin_id': workspace_owner_id, 'date_time': formatted_time}) | |
| # print(f"MENTIONED USERS:{mentioned_users_output}") | |
| # agent_input = { | |
| # 'input': text, | |
| # 'event_details': str(event), | |
| # 'target_user_id': user_id, | |
| # 'timezone': timezone, | |
| # 'user_id': user_id, | |
| # 'admin': workspace_owner_id, | |
| # 'zoom_link': zoom_link, | |
| # 'zoom_mode': zoom_mode, | |
| # 'channel_history': channel_history, | |
| # 'user_information': mentioned_users_output, | |
| # 'calendar_tool': calendar_tool, | |
| # 'date_time': formatted_time, | |
| # 'formatted_calendar': output, | |
| # 'team_id': team_id # Added | |
| # } | |
| # mentions = list(set(re.findall(r'<@(\w+)>', text))) | |
| # if bot_user_id in mentions: | |
| # mentions.remove(bot_user_id) | |
| # schedule_group_exec = create_schedule_channel_agent(schedule_tools) | |
| # update_group_exec = create_update_group_agent(update_tools) | |
| # delete_exec = create_delete_agent(delete_tools) | |
| # if intent == "schedule meeting": | |
| # group_agent_input = agent_input.copy() | |
| # group_agent_input['mentioned_users'] = "See from the history except 'Bob'" | |
| # response = schedule_group_exec.invoke(group_agent_input) | |
| # say(response['output']) | |
| # return | |
| # elif intent == "update event": | |
| # group_agent_input = agent_input.copy() | |
| # group_agent_input['mentioned_users'] = "See from the history except 'Bob'" | |
| # response = update_group_exec.invoke(group_agent_input) | |
| # say(response['output']) | |
| # return | |
| # elif intent == "delete event": | |
| # response = delete_exec.invoke(agent_input) | |
| # say(response['output']) | |
| # return | |
| # elif intent == "other": | |
| # response = llm.predict(general_prompt.format(input=text, channel_history=channel_history)) | |
| # say(response) | |
| # return | |
| # else: | |
| # say("I'm not sure how to handle that request.") | |
| def format_channel_history(raw_history): | |
| cleaned_history = [] | |
| for msg in raw_history: | |
| if 'bot_id' in msg and 'Calendar provider updated' in msg.get('text', ''): | |
| continue | |
| sender = msg.get('user', 'Unknown') if 'bot_id' not in msg else msg.get('bot_profile', {}).get('name', 'Bot') | |
| message_text = msg.get('text', '').strip() | |
| timestamp = float(msg.get('ts', 0)) | |
| readable_time = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %I:%M %p') | |
| user_id = msg.get('user', 'N/A') | |
| team_id = msg.get('team', 'N/A') | |
| cleaned_history.append({ | |
| 'message': message_text, | |
| 'from': sender, | |
| 'timestamp': readable_time, | |
| 'user_team': f"{user_id}/{team_id}" | |
| }) | |
| formatted_output = "" | |
| for i, entry in enumerate(cleaned_history, 1): | |
| formatted_output += f"Message {i}: {entry['message']}\nFrom: {entry['from']}\nTimestamp: {entry['timestamp']}\nUserId/TeamId: {entry['user_team']}\n\n" | |
| return formatted_output.strip() | |
| def get_relevant_user_ids(client, channel_id): | |
| try: | |
| members = [] | |
| cursor = None | |
| while True: | |
| response = client.conversations_members(channel=channel_id, limit=10, cursor=cursor) | |
| if not response["ok"]: | |
| logger.error(f"Failed to get members for channel {channel_id}: {response['error']}") | |
| break | |
| members.extend(response["members"]) | |
| cursor = response.get("response_metadata", {}).get("next_cursor") | |
| if not cursor: | |
| break | |
| return members | |
| except SlackApiError as e: | |
| logger.error(f"Error getting conversation members: {e}") | |
| return [] | |
| calendar_formatting_chain = LLMChain(llm=llm, prompt=calender_prompt) | |
| def handle_messages(body, say, client, context): | |
| if event_deduplicator.is_duplicate_event(body): | |
| logger.info("Duplicate event detected, skipping processing") | |
| return | |
| event = body.get("event", {}) | |
| if event.get("bot_id"): | |
| logger.info("Ignoring message from bot") | |
| return | |
| user_id = event.get("user") | |
| text = event.get("text", "").strip() | |
| channel_id = event.get("channel") | |
| thread_ts = event.get("thread_ts") | |
| team_id = context['team_id'] | |
| calendar_tool = get_owner_selected_calendar(client, team_id) | |
| channel_info = client.conversations_info(channel=channel_id) | |
| channel = channel_info["channel"] | |
| # Fetch bot_user_id dynamically from installation | |
| installation = installation_store.find_installation(team_id=team_id) | |
| if not installation or not installation.bot_user_id: | |
| logger.error(f"No bot_user_id found for team {team_id}") | |
| say("Error: Could not determine bot user ID.", thread_ts=thread_ts) | |
| return | |
| bot_user_id = installation.bot_user_id | |
| if not channel.get("is_im") and f"<@{bot_user_id}>" in text: | |
| return | |
| if not channel.get("is_im") and "thread_ts" not in event: | |
| return | |
| if not calendar_tool or calendar_tool == "none": | |
| say("The workspace owner has not configured a calendar yet.", thread_ts=thread_ts) | |
| return | |
| print(f"Message events") | |
| workspace_owner_id = get_workspace_owner_id(client, team_id) | |
| is_owner = user_id == workspace_owner_id | |
| timezone = get_user_timezone(client, user_id) | |
| zoom_link = get_zoom_link(client, team_id) | |
| zoom_mode = load_preferences(team_id, workspace_owner_id).get("zoom_config", {}).get("mode", "manual") | |
| channel_history = client.conversations_history(channel=channel_id, limit=2).get("messages", []) | |
| channel_history = format_channel_history(channel_history) | |
| intent = intent_chain.run({"history": channel_history, "input": text}) | |
| if intent == "schedule meeting" and not is_owner and not channel.get("is_group") and not channel.get("is_mpim") and 'thread_ts' not in event: | |
| admin_dm = client.conversations_open(users=workspace_owner_id) | |
| prompt = ChatPromptTemplate.from_template(""" | |
| You have this text: {text} and your job is to mention @{workspace_owner_id} and say following in 2 scenarios: | |
| if message history confirms about scheduling meeting then format below text and return only that response with no other explanation | |
| "Hi {workspace_owner_id} you wanted to schedule a meeting with {user_id}, {user_id} has proposed these slots [Time slots from the text] , Are you comfortable with these slots ? Confirm so I can fix the meeting." | |
| else: | |
| Format the text : {text} | |
| MESSAGE HISTORY: {channel_history} | |
| """) | |
| response = LLMChain(llm=llm, prompt=prompt) | |
| client.chat_postMessage(channel=admin_dm["channel"]["id"], | |
| text=response.run({'text': text, 'workspace_owner_id': workspace_owner_id, 'user_id': user_id, 'channel_history': channel_history})) | |
| say(f"<@{user_id}> I've notified the workspace owner about your meeting request.", thread_ts=thread_ts) | |
| return | |
| mentions = list(set(re.findall(r'<@(\w+)>', text))) | |
| if bot_user_id in mentions: | |
| mentions.remove(bot_user_id) | |
| import pytz | |
| pst = pytz.timezone('America/Los_Angeles') | |
| current_time_pst = datetime.now(pst) | |
| formatted_time = current_time_pst.strftime("%Y-%m-%d | %A | %I:%M %p | %Z") | |
| from all_tools import MicrosoftListCalendarEvents, GoogleCalendarEvents | |
| if calendar_tool == "google": | |
| schedule_tools = [tools[i] for i in [0, 1, 4, 6, 12]] | |
| update_tools = [tools[i] for i in [0, 7, 12]] | |
| delete_tools = [tools[i] for i in [0, 8, 12]] | |
| output = calendar_formatting_chain.run({'input': GoogleCalendarEvents()._run(team_id, workspace_owner_id), 'admin_id': workspace_owner_id, 'date_time': formatted_time}) | |
| elif calendar_tool == "microsoft": | |
| schedule_tools = [tools[i] for i in [0, 1, 9, 12]] | |
| update_tools = [tools[i] for i in [0, 10, 12]] | |
| delete_tools = [tools[i] for i in [0, 11, 12]] | |
| output = calendar_formatting_chain.run({'input': MicrosoftListCalendarEvents()._run(team_id, workspace_owner_id), 'admin_id': workspace_owner_id, 'date_time': formatted_time}) | |
| else: | |
| say("Invalid calendar tool configured.", thread_ts=thread_ts) | |
| return | |
| relevant_user_ids = get_relevant_user_ids(client, channel_id) | |
| all_users = get_all_users(team_id) | |
| relevant_users = {uid: all_users.get(uid, {"real_name": "Unknown", "email": "unknown@example.com", "name": "Unknown"}) | |
| for uid in relevant_user_ids} | |
| user_information = "\n".join([f"{uid}: Name={info['real_name']}, Email={info['email']}, Slack Name={info['name']}" | |
| for uid, info in relevant_users.items() if uid != bot_user_id]) | |
| print(f"All users: {all_users}\n\n Relevant users: {relevant_user_ids}") | |
| mentioned_users_output = mentioned_users_chain.run({"user_information": user_information, "chat_history": channel_history, "current_input": text, 'bob_id': bot_user_id}) | |
| schedule_exec = create_schedule_agent(schedule_tools) | |
| update_exec = create_update_agent(update_tools) | |
| delete_exec = create_delete_agent(delete_tools) | |
| schedule_group_exec = create_schedule_group_agent(schedule_tools) | |
| update_group_exec = create_update_group_agent(update_tools) | |
| print(f"MENTIONED USERS:{mentioned_users_output}") | |
| channel_type = channel.get("is_group", False) or channel.get("is_mpim", False) | |
| agent_input = { | |
| 'input': f"Here is the input by user: {text} and do not mention <@{bot_user_id}> even tho mentioned in history", | |
| 'event_details': str(event), | |
| 'target_user_id': user_id, | |
| 'timezone': timezone, | |
| 'user_id': user_id, | |
| 'admin': workspace_owner_id, | |
| 'zoom_link': zoom_link, | |
| 'zoom_mode': zoom_mode, | |
| 'channel_history': channel_history, | |
| 'user_information': user_information, | |
| 'calendar_tool': calendar_tool, | |
| 'date_time': formatted_time, | |
| 'formatted_calendar': output, | |
| 'team_id': team_id | |
| } | |
| if intent == "schedule meeting": | |
| if not channel_type and len(mentions) > 1: | |
| mentions.append(user_id) | |
| dm_channel_id, error = open_group_dm(client, mentions) | |
| if dm_channel_id: | |
| group_agent_input = agent_input.copy() | |
| group_agent_input['mentioned_users'] = mentioned_users_output | |
| group_agent_input['channel_history'] = channel_history | |
| group_agent_input['formatted_calendar'] = output | |
| response = schedule_group_exec.invoke(group_agent_input) | |
| client.chat_postMessage(channel=dm_channel_id, text=f"Group conversation started by <@{user_id}>\n\n{response['output']}") | |
| elif error: | |
| say(f"Sorry, I couldn't create the group conversation: {error}", thread_ts=thread_ts) | |
| else: | |
| if channel_type or 'thread_ts' in event: | |
| group_agent_input = agent_input.copy() | |
| if 'thread_ts' in event: | |
| schedule_group_exec = create_schedule_channel_agent(schedule_tools) | |
| history_response = client.conversations_replies(channel=channel_id, ts=thread_ts, limit=2) | |
| channel_history = format_channel_history(history_response.get("messages", [])) | |
| else: | |
| channel_history = format_channel_history(client.conversations_history(channel=channel_id, limit=3).get("messages", [])) | |
| group_agent_input['mentioned_users'] = mentioned_users_output | |
| group_agent_input['channel_history'] = channel_history | |
| group_agent_input['formatted_calendar'] = output | |
| response = schedule_group_exec.invoke(group_agent_input) | |
| say(response['output'], thread_ts=thread_ts) | |
| return | |
| response = schedule_exec.invoke(agent_input) | |
| say(response['output'], thread_ts=thread_ts) | |
| elif intent == "update event": | |
| agent_input['current_date'] = formatted_time | |
| agent_input['calendar_events'] = MicrosoftListCalendarEvents()._run(team_id, workspace_owner_id) if calendar_tool == "microsoft" else GoogleCalendarEvents()._run(team_id, workspace_owner_id) | |
| if channel_type or 'thread_ts' in event: | |
| group_agent_input = agent_input.copy() | |
| channel_history = format_channel_history(client.conversations_history(channel=channel_id, limit=2).get("messages", [])) | |
| group_agent_input['mentioned_users'] = mentioned_users_output | |
| group_agent_input['channel_history'] = channel_history | |
| group_agent_input['formatted_calendar'] = output | |
| response = update_group_exec.invoke(group_agent_input) | |
| say(response['output'], thread_ts=thread_ts) | |
| return | |
| response = update_exec.invoke(agent_input) | |
| say(response['output'], thread_ts=thread_ts) | |
| elif intent == "delete event": | |
| agent_input['current_date'] = formatted_time | |
| agent_input['calendar_events'] = MicrosoftListCalendarEvents()._run(team_id, workspace_owner_id) if calendar_tool == "microsoft" else GoogleCalendarEvents()._run(team_id, workspace_owner_id) | |
| response = delete_exec.invoke(agent_input) | |
| say(response['output'], thread_ts=thread_ts) | |
| elif intent == "other": | |
| response = llm.predict(general_prompt.format(input=text, channel_history=channel_history)) | |
| say(response, thread_ts=thread_ts) | |
| else: | |
| say("I'm not sure how to handle that request.", thread_ts=thread_ts) | |
| def handle_team_join(event, client, context, logger): | |
| try: | |
| user_info = event['user'] | |
| team_id = context.team_id | |
| # Fetch workspace name from Slack API | |
| try: | |
| team_info = client.team_info() | |
| workspace_name = team_info['team']['name'] | |
| except SlackApiError as e: | |
| logger.error(f"Error fetching team info: {e.response['error']}") | |
| workspace_name = "Unknown Workspace" | |
| # Extract user details | |
| user_id = user_info['id'] | |
| real_name = user_info.get('real_name', 'Unknown') | |
| profile = user_info.get('profile', {}) | |
| email = profile.get('email', f"{user_info.get('name', 'user')}@example.com") # Fallback email | |
| name = user_info.get('name', '') | |
| is_owner = user_info.get('is_owner', False) | |
| # Connect to database | |
| conn = psycopg2.connect(os.getenv('DATABASE_URL')) | |
| cur = conn.cursor() | |
| # Insert/update user in Users table | |
| cur.execute(''' | |
| INSERT INTO Users (team_id, user_id, workspace_name, real_name, email, name, is_owner, last_updated) | |
| VALUES (%s, %s, %s, %s, %s, %s, %s, %s) | |
| ON CONFLICT (team_id, user_id) DO UPDATE SET | |
| workspace_name = EXCLUDED.workspace_name, | |
| real_name = EXCLUDED.real_name, | |
| email = EXCLUDED.email, | |
| name = EXCLUDED.name, | |
| is_owner = EXCLUDED.is_owner, | |
| last_updated = EXCLUDED.last_updated | |
| ''', (team_id, user_id, workspace_name, real_name, email, name, is_owner, datetime.now())) | |
| conn.commit() | |
| cur.close() | |
| conn.close() | |
| # Update user cache | |
| with user_cache_lock: | |
| if team_id not in user_cache: | |
| user_cache[team_id] = {} | |
| user_cache[team_id][user_id] = { | |
| "real_name": real_name, | |
| "email": f"{name}@gmail.com", | |
| "name": name, | |
| "is_owner": is_owner, | |
| "workspace_name": workspace_name | |
| } | |
| # Update owner_id_cache if user is owner | |
| if is_owner: | |
| with owner_id_lock: | |
| owner_id_cache[team_id] = user_id | |
| logger.info(f"Processed team_join event for user {user_id} in team {team_id}") | |
| except KeyError as e: | |
| logger.error(f"Missing key in event data: {e}") | |
| except psycopg2.Error as e: | |
| logger.error(f"Database error: {e}") | |
| except Exception as e: | |
| logger.error(f"Unexpected error handling team_join: {e}") | |
| def open_group_dm(client, users): | |
| try: | |
| response = client.conversations_open(users=",".join(users)) | |
| return response["channel"]["id"] if response["ok"] else (None, "Failed to create group DM") | |
| except SlackApiError as e: | |
| return None, f"Error creating group DM: {e.response['error']}" | |
| # Flask Routes | |
| def slack_events(): | |
| return slack_handler.handle(request) | |
| def slack_install(): | |
| return slack_handler.handle(request) | |
| def slack_oauth_redirect(): | |
| return slack_handler.handle(request) | |
| def oauth2callback(): | |
| state = request.args.get('state', '') | |
| print(f"STATE: {state}") | |
| print(f"STATs: {state_manager._states}") | |
| user_id = state_manager.validate_and_consume_state(state) | |
| stored_state = get_from_session(user_id, "gcal_state") if user_id else None | |
| if not user_id or stored_state != state: | |
| return "Invalid state", 400 | |
| team_id = get_team_id_from_owner_id(user_id) | |
| if not team_id: | |
| return "Workspace not found", 404 | |
| client = get_client_for_team(team_id) | |
| if not client: | |
| return "Client not found", 500 | |
| flow = Flow.from_client_secrets_file('credentials.json', scopes=SCOPES, redirect_uri=OAUTH_REDIRECT_URI) | |
| flow.fetch_token(authorization_response=request.url) | |
| credentials = flow.credentials | |
| service = build('oauth2', 'v2', credentials=credentials) | |
| user_info = service.userinfo().get().execute() | |
| google_email = user_info.get('email', 'unknown@example.com') | |
| token_data = json.loads(credentials.to_json()) | |
| token_data['google_email'] = google_email | |
| save_token(team_id, user_id, 'google', token_data) # Adjusted to use team_id and user_id | |
| client.views_publish(user_id=user_id, view=create_home_tab(client, team_id, user_id)) | |
| return "Google Calendar connected successfully! You can close this window." | |
| def handle_launch_auth(ack, body, logger): | |
| ack() # Acknowledge the action | |
| logger.info(f"Launch auth triggered by user {body['user']['id']}") | |
| # No further action needed since the URL redirect handles the OAuth flow | |
| def microsoft_callback(): | |
| code = request.args.get("code") | |
| state = request.args.get("state") | |
| if not code or not state: | |
| return "Missing parameters", 400 | |
| user_id = state_manager.validate_and_consume_state(state) | |
| if not user_id: | |
| return "Invalid or expired state parameter", 403 | |
| team_id = get_team_id_from_owner_id(user_id) | |
| if not team_id: | |
| return "Workspace not found", 404 | |
| client = get_client_for_team(team_id) | |
| if not client: | |
| return "Client not found", 500 | |
| if user_id != get_workspace_owner_id(client, team_id): | |
| return "Unauthorized", 403 | |
| msal_app = ConfidentialClientApplication(MICROSOFT_CLIENT_ID, authority=MICROSOFT_AUTHORITY, client_credential=MICROSOFT_CLIENT_SECRET) | |
| result = msal_app.acquire_token_by_authorization_code(code, scopes=MICROSOFT_SCOPES, redirect_uri=MICROSOFT_REDIRECT_URI) | |
| if "access_token" not in result: | |
| return "Authentication failed", 400 | |
| token_data = {"access_token": result["access_token"], "refresh_token": result.get("refresh_token", ""), "expires_at": result["expires_in"] + time.time()} | |
| save_token(user_id, 'microsoft', token_data) | |
| client.views_publish(user_id=user_id, view=create_home_tab(client, team_id, user_id)) | |
| return "Microsoft Calendar connected successfully! You can close this window." | |
| def zoom_callback(): | |
| code = request.args.get("code") | |
| state = request.args.get("state") | |
| user_id = state_manager.validate_and_consume_state(state) | |
| if not user_id: | |
| return "Invalid or expired state", 403 | |
| team_id = get_team_id_from_owner_id(user_id) | |
| if not team_id: | |
| return "Workspace not found", 404 | |
| client = get_client_for_team(team_id) | |
| if not client: | |
| return "Client not found", 500 | |
| params = {"grant_type": "authorization_code", "code": code, "redirect_uri": ZOOM_REDIRECT_URI} | |
| try: | |
| response = requests.post(ZOOM_TOKEN_API, params=params, auth=(CLIENT_ID, CLIENT_SECRET)) | |
| except Exception as e: | |
| return jsonify({"error": f"Token request failed: {str(e)}"}), 500 | |
| if response.status_code == 200: | |
| token_data = response.json() | |
| token_data["expires_at"] = time.time() + token_data["expires_in"] | |
| # Fixed: Pass all required arguments in correct order | |
| save_token(team_id, user_id, 'zoom', token_data) | |
| client.views_publish(user_id=user_id, view=create_home_tab(client, team_id, user_id)) | |
| return "Zoom connected successfully! You can close this window." | |
| return "Failed to retrieve token", 400 | |
| def handle_open_zoom_config_modal(ack, body, client, logger): | |
| ack() | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| client.chat_postMessage(channel=user_id, text="Only the workspace owner can configure Zoom.") | |
| return | |
| # Fixed: Pass both team_id and user_id to load_preferences | |
| prefs = load_preferences(team_id, user_id) | |
| zoom_config = prefs.get("zoom_config", {"mode": "manual", "link": None}) | |
| mode = zoom_config["mode"] | |
| link = zoom_config.get("link", "") | |
| try: | |
| client.views_open( | |
| trigger_id=body["trigger_id"], | |
| view={ | |
| "type": "modal", | |
| "callback_id": "zoom_config_submit", | |
| "title": {"type": "plain_text", "text": "Configure Zoom"}, | |
| "submit": {"type": "plain_text", "text": "Save"}, | |
| "close": {"type": "plain_text", "text": "Cancel"}, | |
| "blocks": [ | |
| { | |
| "type": "input", | |
| "block_id": "zoom_mode", | |
| "label": {"type": "plain_text", "text": "Zoom Mode"}, | |
| "element": { | |
| "type": "static_select", | |
| "action_id": "mode_select", | |
| "placeholder": {"type": "plain_text", "text": "Select mode"}, | |
| "options": [ | |
| {"text": {"type": "plain_text", "text": "Automatic"}, "value": "automatic"}, | |
| {"text": {"type": "plain_text", "text": "Manual"}, "value": "manual"} | |
| ], | |
| "initial_option": {"text": {"type": "plain_text", "text": "Automatic" if mode == "automatic" else "Manual"}, "value": mode} if mode else None | |
| } | |
| }, | |
| { | |
| "type": "input", | |
| "block_id": "zoom_link", | |
| "label": {"type": "plain_text", "text": "Manual Zoom Link"}, | |
| "element": { | |
| "type": "plain_text_input", | |
| "action_id": "link_input", | |
| "placeholder": {"type": "plain_text", "text": "Enter Zoom link"}, | |
| "initial_value": link if isinstance(link, str) else "" | |
| }, | |
| "optional": True | |
| } | |
| ] | |
| } | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error opening Zoom config modal: {e}") | |
| def handle_zoom_config(ack, body, client, logger): | |
| ack() # Acknowledge the action | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| # Ensure only the workspace owner can configure Zoom | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| client.chat_postMessage(channel=user_id, text="Only the workspace owner can configure Zoom.") | |
| return | |
| # Check if this is a refresh or initial authentication | |
| zoom_token = load_token(team_id, owner_id, "zoom") | |
| is_refresh = zoom_token is not None | |
| # Generate the Zoom OAuth URL | |
| state = state_manager.create_state(owner_id) # Assume this generates a unique state | |
| auth_url = f"{ZOOM_OAUTH_AUTHORIZE_API}?response_type=code&client_id={CLIENT_ID}&redirect_uri={quote_plus(ZOOM_REDIRECT_URI)}&state={state}" | |
| # Set modal text based on the scenario | |
| modal_title = "Refresh Zoom Token" if is_refresh else "Authenticate with Zoom" | |
| button_text = "Refresh Zoom Token" if is_refresh else "Authenticate with Zoom" | |
| # Open a modal with the appropriate text | |
| try: | |
| client.views_open( | |
| trigger_id=body["trigger_id"], | |
| view={ | |
| "type": "modal", | |
| "title": {"type": "plain_text", "text": modal_title}, | |
| "close": {"type": "plain_text", "text": "Cancel"}, | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": {"type": "mrkdwn", "text": f"Click below to {button_text.lower()}:"} | |
| }, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": {"type": "plain_text", "text": button_text}, | |
| "url": auth_url, | |
| "action_id": "launch_zoom_auth" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error opening Zoom auth modal: {e}") | |
| def handle_zoom_config_submit(ack, body, client, logger): | |
| ack() # Ensure ack is called before any processing to avoid warnings | |
| user_id = body["user"]["id"] | |
| team_id = body["team"]["id"] | |
| owner_id = get_workspace_owner_id(client, team_id) | |
| if user_id != owner_id: | |
| return # Early return if not owner; no need to proceed | |
| values = body["view"]["state"]["values"] | |
| mode = values["zoom_mode"]["mode_select"]["selected_option"]["value"] | |
| link = values["zoom_link"]["link_input"]["value"] if "zoom_link" in values and "link_input" in values["zoom_link"] else None | |
| zoom_config = {"mode": mode, "link": link if mode == "manual" else None} | |
| save_preference(team_id, user_id, zoom_config=zoom_config) | |
| client.views_publish(user_id=user_id, view=create_home_tab(client, team_id, user_id)) | |
| def handle_some_action(ack, body, logger): | |
| ack() | |
| scheduler = BackgroundScheduler() | |
| scheduler.add_job(state_manager.cleanup_expired_states, 'interval', minutes=5) | |
| scheduler.start() | |
| def home(): | |
| return render_template('index.html') | |
| # @app.route('/ZOOM_verify_a12f2ccf48a647aa8ebc987a249133f8.html') | |
| # def home(): | |
| # return render_template('ZOOM_verify_a12f2ccf48a647aa8ebc987a249133f8.html') | |
| if __name__ == "__main__": | |
| app.run(port=3000) |