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)