update
Browse files- feishu_service.py +124 -11
- redis_service.py +15 -2
- requirements.txt +2 -2
- 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 |
-
|
9 |
-
|
10 |
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
"""
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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":
|
21 |
-
"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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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.
|