sdk/oapiSdk/calendars.py

200 lines
6.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import time
import json
from flask import Flask, request, jsonify, redirect
import requests
import base64
import hashlib
from OaConfig import TOKEN_STORE_FILE
app = Flask(__name__)
# 全局变量缓存token和配置
token_data = {}
APP_ID = None
APP_SECRET = None
REDIRECT_URI = None
code_verifier = None
def save_token(app_id, data):
store = {}
if os.path.exists(TOKEN_STORE_FILE):
with open(TOKEN_STORE_FILE, "r") as f:
store = json.load(f)
store[app_id] = data
with open(TOKEN_STORE_FILE, "w") as f:
json.dump(store, f)
def load_token(app_id):
if os.path.exists(TOKEN_STORE_FILE):
with open(TOKEN_STORE_FILE, "r") as f:
store = json.load(f)
return store.get(app_id, {})
return {}
def ensure_token_valid():
global token_data
now = time.time()
if not token_data or not token_data.get("access_token") or now > token_data.get("expires_at", 0):
print("[ensure_token_valid] Access token expired or missing, try refresh token...")
if not refresh_access_token():
print("[ensure_token_valid] Refresh token failed, need user authorization")
return False
return True
def refresh_access_token():
global token_data, APP_ID, APP_SECRET
if not token_data.get("refresh_token"):
print("[refresh_access_token] No refresh_token available")
return False
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()
print(f"[refresh_access_token] Response: {data}")
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)
print("[refresh_access_token] Refresh success")
return True
else:
print("[refresh_access_token] Refresh failed")
return False
def generate_code_verifier():
# 生成随机字符串作为 code_verifier
return base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode('utf-8')
def generate_code_challenge(code_verifier):
# 对 code_verifier 做 sha256再 base64 urlsafe 编码得到 code_challenge
sha256 = hashlib.sha256(code_verifier.encode('utf-8')).digest()
return base64.urlsafe_b64encode(sha256).rstrip(b'=').decode('utf-8')
@app.route("/pkce", methods=["GET"])
def get_pkce():
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
return jsonify({
"code_verifier": code_verifier,
"code_challenge": code_challenge
})
@app.route("/oauth/callback")
def oauth_callback():
global token_data, APP_ID, APP_SECRET, REDIRECT_URI, code_verifier
code = request.args.get("code")
state = request.args.get("state")
if not code:
return jsonify({"error": "Missing code param"}), 400
if not all([APP_ID, APP_SECRET, REDIRECT_URI, code_verifier]):
return jsonify({"error": "Missing config parameters"}), 400
# 用code换token
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": code_verifier
}
)
data = resp.json()
print(f"[oauth_callback] token response: {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
@app.route("/set_config", methods=["POST"])
def set_config():
global APP_ID, APP_SECRET, REDIRECT_URI, code_verifier, 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")
code_verifier = data.get("code_verifier")
if not all([APP_ID, APP_SECRET, REDIRECT_URI, code_verifier]):
return jsonify({"error": "缺少必要参数 APP_ID, APP_SECRET, REDIRECT_URI 或 code_verifier"}), 400
# 读取之前保存的token
token_data = load_token(APP_ID)
return jsonify({"message": "配置成功", "token_data": token_data})
@app.route("/calendar_events", methods=["POST"])
def calendar_events():
global APP_ID, APP_SECRET, token_data
if not all([APP_ID, APP_SECRET]):
return jsonify({"error": "请先调用 /set_config 设置参数"}), 400
if not ensure_token_valid():
# 引导用户去授权页面
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 calendar:calendar:readonly offline_access"
f"&state=auth"
f"&code_challenge=YOUR_CODE_CHALLENGE" # 需要用你的code_challenge替换
f"&code_challenge_method=S256"
)
return jsonify({
"message": "请先授权",
"auth_url": auth_url
}), 401
headers = {
"Authorization": f"Bearer {token_data['access_token']}"
}
resp = requests.get("https://open.feishu.cn/open-apis/calendar/v4/calendars", headers=headers)
data = resp.json()
if data.get("code") != 0:
return jsonify({"error": "获取日历列表失败", "raw": data}), 500
calendars = data.get("data", {}).get("calendar_list", [])
events_all = []
for calendar in calendars:
calendar_id = calendar["calendar_id"]
ev_resp = requests.get(
f"https://open.feishu.cn/open-apis/calendar/v4/calendars/{calendar_id}/events",
headers=headers
)
ev_data = ev_resp.json()
if ev_data.get("code") != 0:
continue
events = ev_data.get("data", {}).get("items", [])
events_all.extend(events)
return jsonify({"events": events_all})
if __name__ == "__main__":
app.run(port=8888, debug=True)