sdk/oapiSdk/test.py

166 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Flask, request, jsonify
import os, base64, hashlib, time, requests, json
app = Flask(__name__)
APP_ID = None
APP_SECRET = None
REDIRECT_URI = None
code_verifier_store = {}
token_data = {}
# 生成 code_verifier / code_challenge
def generate_code_verifier():
return base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode('utf-8')
def generate_code_challenge(code_verifier):
digest = hashlib.sha256(code_verifier.encode('utf-8')).digest()
return base64.urlsafe_b64encode(digest).rstrip(b'=').decode('utf-8')
def save_token(app_id, token):
with open(f"token_{app_id}.json", "w", encoding="utf-8") as f:
json.dump(token, f)
def load_token(app_id):
path = f"token_{app_id}.json"
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
return None
def ensure_token_valid():
global token_data
if not token_data:
return False
if time.time() >= token_data.get("expires_at", 0):
# refresh token
resp = requests.post(
"https://open.feishu.cn/open-apis/authen/v2/oauth/token",
json={
"grant_type": "refresh_token",
"client_id": APP_ID,
"client_secret": APP_SECRET,
"refresh_token": token_data["refresh_token"]
}
)
data = resp.json()
if data.get("code") == 0:
token_data["access_token"] = data["access_token"]
token_data["refresh_token"] = data["refresh_token"]
token_data["expires_at"] = time.time() + data["expires_in"] - 60
save_token(APP_ID, token_data)
return True
return False
return True
# 1⃣ 生成 PKCE 参数
@app.route("/pkce", methods=["GET"])
def get_pkce():
global code_verifier_store
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
# 保存,方便 callback 时使用
code_verifier_store["verifier"] = code_verifier
code_verifier_store["challenge"] = code_challenge
return jsonify({
"code_verifier": code_verifier,
"code_challenge": code_challenge
})
# 2⃣ 回调换取 token
@app.route("/oauth/callback")
def oauth_callback():
global token_data
code = request.args.get("code")
if not code:
return jsonify({"error": "Missing code"}), 400
if not all([APP_ID, APP_SECRET, REDIRECT_URI]):
return jsonify({"error": "Missing config"}), 400
verifier = code_verifier_store.get("verifier")
if not verifier:
return jsonify({"error": "Missing code_verifier"}), 400
resp = requests.post(
"https://open.feishu.cn/open-apis/authen/v2/oauth/token",
json={
"grant_type": "authorization_code",
"client_id": APP_ID,
"client_secret": APP_SECRET,
"code": code,
"redirect_uri": REDIRECT_URI,
"code_verifier": verifier
}
)
data = resp.json()
print("[callback] token resp:", data)
if data.get("code") == 0:
token_data = {
"access_token": data["access_token"],
"refresh_token": data["refresh_token"],
"expires_at": time.time() + data["expires_in"] - 60
}
save_token(APP_ID, token_data)
return "授权成功,可以回到终端调用接口了。"
else:
return jsonify({"error": "Token获取失败", "raw": data}), 400
# 3⃣ 设置配置参数
@app.route("/set_config", methods=["POST"])
def set_config():
global APP_ID, APP_SECRET, REDIRECT_URI, token_data
data = request.get_json(force=True)
APP_ID = data.get("APP_ID")
APP_SECRET = data.get("APP_SECRET")
REDIRECT_URI = data.get("REDIRECT_URI")
if not all([APP_ID, APP_SECRET, REDIRECT_URI]):
return jsonify({"error": "缺少 APP_ID, APP_SECRET, REDIRECT_URI"}), 400
token_data = load_token(APP_ID)
return jsonify({"message": "配置成功", "token_data": token_data})
# 4⃣ 获取日历和日程
@app.route("/calendar_events", methods=["POST"])
def calendar_events():
global token_data
if not ensure_token_valid():
challenge = code_verifier_store.get("challenge")
if not challenge:
return jsonify({"error": "请先访问 /pkce 获取 PKCE 参数"}), 400
auth_url = (
f"https://accounts.feishu.cn/open-apis/authen/v1/authorize"
f"?client_id={APP_ID}"
f"&redirect_uri={REDIRECT_URI}"
f"&scope=calendar:calendar.event:read%20calendar:calendar:readonly%20offline_access"
f"&state=auth"
f"&response_type=code"
f"&code_challenge={challenge}"
f"&code_challenge_method=S256"
)
return jsonify({"message": "需要授权", "auth_url": auth_url}), 401
headers = {"Authorization": f"Bearer {token_data['access_token']}"}
calendars_resp = requests.get("https://open.feishu.cn/open-apis/calendar/v4/calendars", headers=headers).json()
if calendars_resp.get("code") != 0:
return jsonify({"error": "获取日历列表失败", "raw": calendars_resp}), 500
calendars = calendars_resp.get("data", {}).get("calendar_list", [])
all_events = []
for c in calendars:
cid = c["calendar_id"]
ev_resp = requests.get(
f"https://open.feishu.cn/open-apis/calendar/v4/calendars/{cid}/events",
headers=headers
).json()
if ev_resp.get("code") == 0:
all_events.extend(ev_resp.get("data", {}).get("items", []))
return jsonify({"events": all_events})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8888 ,debug=True)