geqintan commited on
Commit
a248251
·
1 Parent(s): 6fd8a52
Files changed (4) hide show
  1. feishu_service.py +124 -11
  2. redis_service.py +15 -2
  3. requirements.txt +2 -2
  4. store.py +11 -0
feishu_service.py CHANGED
@@ -1,24 +1,117 @@
1
  import os
2
  import json
3
  import httpx
 
4
  from dotenv import load_dotenv
 
5
 
6
  load_dotenv()
7
 
8
- feishu_app_id = os.getenv('FEISHU_APP_ID', 'YOUR_FEISHU_APP_ID') # Placeholder
9
- feishu_app_secret = os.getenv('FEISHU_APP_SECRET', 'YOUR_FEISHU_APP_SECRET') # Placeholder
10
 
11
- async def get_valid_tenant_access_token() -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  """
13
- Retrieves a valid tenant access token from Feishu API.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
16
  headers = {
17
  "Content-Type": "application/json"
18
  }
19
  payload = {
20
- "app_id": feishu_app_id,
21
- "app_secret": feishu_app_secret
22
  }
23
 
24
  async with httpx.AsyncClient() as client:
@@ -26,19 +119,39 @@ async def get_valid_tenant_access_token() -> str:
26
  response = await client.post(url, headers=headers, json=payload)
27
  response.raise_for_status() # Raise an exception for bad status codes
28
  data = response.json()
 
29
  if data.get("code") == 0:
30
- return data.get("tenant_access_token")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  else:
32
- print(f"Error getting tenant access token: {data.get('msg')}")
33
  return None
34
  except httpx.HTTPStatusError as e:
35
- print(f"HTTP error occurred: {e}")
36
  return None
37
  except httpx.RequestError as e:
38
- print(f"An error occurred while requesting {e.request.url!r}: {e}")
39
  return None
40
  except Exception as e:
41
- print(f"An unexpected error occurred: {e}")
42
  return None
43
 
44
  async def send_feishu_reply(msg_id: str, tenant_access_token: str, content: dict, msg_type: str):
 
1
  import os
2
  import json
3
  import httpx
4
+ import time # Import time for checking token expiry
5
  from dotenv import load_dotenv
6
+ from supabase import create_client, Client # Import Supabase client
7
 
8
  load_dotenv()
9
 
10
+ # Import the token_store from store.py
11
+ from store import token_store
12
 
13
+ # Initialize Supabase client
14
+ # Ensure SUPABASE_URL and SUPABASE_KEY are set in your .env file
15
+ SUPABASE_URL = os.getenv("SUPABASE_URL")
16
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY")
17
+ print('\n\n\n\nSUPABASE_URL', SUPABASE_URL)
18
+ print('SUPABASE_KEY', SUPABASE_KEY)
19
+
20
+ if not SUPABASE_URL or not SUPABASE_KEY:
21
+ print("Error: SUPABASE_URL or SUPABASE_KEY not found in environment variables.")
22
+ # Depending on your application's needs, you might want to exit or handle this differently
23
+ # For now, we'll just print an error and the client will be None
24
+ supabase: Client | None = None
25
+ else:
26
+ print("\n\n\n\n\n\nConnecting to Supabase...")
27
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
28
+ print("Supabase client initialized.")
29
+
30
+
31
+ async def get_app_secret_from_db(app_id: str) -> str | None:
32
  """
33
+ Reads the app_secret from the Supabase database based on app_id.
34
+ Assumes a table named 'feishu_apps' with columns 'app_id' and 'app_secret'.
35
+ """
36
+ print('\n\n\n\n************************\nget_app_secret_from_db() app_id:', app_id)
37
+ if not supabase:
38
+ print("Supabase client not initialized, cannot fetch app_secret.")
39
+ return None # Correctly return None if supabase client is not initialized
40
+
41
+ try:
42
+ # Query the 'feishu_bot_config' table for the platform_specific column matching the bot_id
43
+ # Use maybe_single() as we expect at most one row, and execute() to get the result
44
+ response = supabase.table('feishu_bot_config').select('platform_specific').eq('bot_id', app_id).single().execute()
45
+ # print(f"\n\n\n\nResponse from Supabase (maybe_single().execute()): ",response)
46
+ print('\nresponse.data', response.data)
47
+
48
+ # Check if a row was returned and extract the app_secret from platform_specific
49
+ # The result of maybe_single().execute() will have a 'data' attribute
50
+ if response.data and 'platform_specific' in response.data and 'app_secret' in response.data['platform_specific']:
51
+ app_secret = response.data['platform_specific']['app_secret']
52
+ print(f"Successfully retrieved app_secret for app_id: {app_id} from Supabase. app_secret: ", app_secret)
53
+ return app_secret
54
+ else:
55
+ print(f"No app_secret found in Supabase for app_id: {app_id}.")
56
+ return None
57
+ except Exception as e:
58
+ print(f"Error fetching app_secret from Supabase for app_id {app_id}: {e}")
59
+ return None
60
+
61
+
62
+ async def get_valid_tenant_access_token(app_id: str) -> str | None:
63
+ """
64
+ Retrieves a valid tenant access token for the given app_id.
65
+ Prioritizes fetching from the global store if not expired,
66
+ otherwise generates a new one and stores it.
67
+
68
+ Args:
69
+ app_id: The Feishu App ID.
70
+
71
+ Returns:
72
+ A valid tenant access token or None if unable to obtain one.
73
  """
74
+ print('\n\n\n\n\n\n\n*******************************\n')
75
+ print('get_valid_tenant_access_token, appid:', app_id)
76
+ # 1. Prioritize fetching from the global store
77
+ stored_token_info = token_store.get(app_id)
78
+ print('\n\n\n\n\n\n\n\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\nstored_token_info',stored_token_info)
79
+ print('\ntoken_store:', token_store)
80
+
81
+ if stored_token_info:
82
+ token = stored_token_info.get('token')
83
+ created_time = stored_token_info.get('created_time')
84
+ expires_in = stored_token_info.get('expires_in')
85
+
86
+ # 2. Check if the stored token is still valid
87
+ if token and created_time is not None and expires_in is not None:
88
+ current_time = time.time()
89
+ # Add a small buffer (e.g., 60 seconds) to avoid using tokens that are about to expire
90
+ if current_time < created_time + expires_in - 60:
91
+ print(f"Using cached tenant access token for app_id: {app_id}")
92
+ return token
93
+ else:
94
+ print(f"Cached tenant access token for app_id: {app_id} has expired.")
95
+ else:
96
+ print(f"Incomplete token info found in store for app_id: {app_id}.")
97
+
98
+ # If no valid token in store, generate a new one
99
+ print(f"\n\nGenerating new tenant access token for app_id: {app_id}")
100
+
101
+ # Read app_secret from the simulated database
102
+ app_secret = await get_app_secret_from_db(app_id)
103
+
104
+ if not app_secret:
105
+ print(f"Could not retrieve valid app_secret for app_id: {app_id}")
106
+ return None
107
+
108
  url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
109
  headers = {
110
  "Content-Type": "application/json"
111
  }
112
  payload = {
113
+ "app_id": app_id,
114
+ "app_secret": app_secret
115
  }
116
 
117
  async with httpx.AsyncClient() as client:
 
119
  response = await client.post(url, headers=headers, json=payload)
120
  response.raise_for_status() # Raise an exception for bad status codes
121
  data = response.json()
122
+ print('\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n^^^^^data',data)
123
  if data.get("code") == 0:
124
+ new_token = data.get("tenant_access_token")
125
+ expire = data.get("expire") # Note: Feishu API returns 'expire_in'
126
+ print('\nnew_token',new_token)
127
+ print('\nexpires',expire)
128
+
129
+
130
+ if new_token and expire is not None:
131
+ print("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%New token received")
132
+ # 4. Store the new token and its expiry information
133
+ token_store[app_id] = {
134
+ 'token': new_token,
135
+ 'created_time': time.time(),
136
+ 'expire': expire
137
+ }
138
+ print(f"Successfully generated and stored new tenant access token for app_id: {app_id}")
139
+ print('\ntoken_store',token_store)
140
+ return new_token
141
+ else:
142
+ print(f"Error generating new tenant access token: Missing token or expiry info in response.")
143
+ return None
144
  else:
145
+ print(f"Error getting tenant access token from API: {data.get('msg')}")
146
  return None
147
  except httpx.HTTPStatusError as e:
148
+ print(f"HTTP error occurred while generating token: {e}")
149
  return None
150
  except httpx.RequestError as e:
151
+ print(f"An error occurred while requesting {e.request.url!r} to generate token: {e}")
152
  return None
153
  except Exception as e:
154
+ print(f"An unexpected error occurred while generating token: {e}")
155
  return None
156
 
157
  async def send_feishu_reply(msg_id: str, tenant_access_token: str, content: dict, msg_type: str):
redis_service.py CHANGED
@@ -63,8 +63,21 @@ async def redis_subscriber(redis_client, channel_name):
63
  # Convert the received JSON string back to a Python dictionary
64
  try:
65
  data_dict = json.loads(data_str)
66
- # Get valid tenant access token using the Feishu service function
67
- tenant_access_token = await get_valid_tenant_access_token()
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  if tenant_access_token:
69
  # Extract necessary info from data_dict for sending reply
70
  msg_id = data_dict.get('msg_id')
 
63
  # Convert the received JSON string back to a Python dictionary
64
  try:
65
  data_dict = json.loads(data_str)
66
+ print(f"\nParsed message data as dictionary: {data_dict}")
67
+
68
+ # Extract app_id from platform_specific
69
+ app_id = data_dict.get('platform_specific', {}).get('app_id')
70
+
71
+ # Get valid tenant access token using the Feishu service function, passing only app_id
72
+ tenant_access_token = None
73
+ if app_id:
74
+ tenant_access_token = await get_valid_tenant_access_token(app_id)
75
+ else:
76
+ print("Missing app_id in received data, cannot get tenant access token.")
77
+
78
+ print(f"\n\n\n\n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\ntenant_access_token: {tenant_access_token}")
79
+
80
+
81
  if tenant_access_token:
82
  # Extract necessary info from data_dict for sending reply
83
  msg_id = data_dict.get('msg_id')
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- # clear && uvicorn app:app --host 0.0.0.0 --port 7860 --reload
2
  fastapi
3
- uvicorn[standard]
4
  redis
5
  python-dotenv
6
  httpx
 
 
 
1
  fastapi
2
+ uvicorn
3
  redis
4
  python-dotenv
5
  httpx
6
+ supabase
store.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # store.py
2
+
3
+ # This file is used to store global scalar values.
4
+ # You can define your global variables here.
5
+
6
+ # Dictionary to store tenant access tokens and their expiry information
7
+ # Key: app_id (str)
8
+ # Value: Dictionary {'token': str, 'created_time': float, 'expires_in': int}
9
+ token_store = {}
10
+
11
+ # Add any other global variables as needed.