sdk/oapi-sdk-python-2_main/main.py

299 lines
10 KiB
Python
Raw Normal View History

2025-08-18 09:05:41 +00:00
import json
import csv
import io
from flask import Flask, jsonify, redirect, request
import time
import requests
from lark_oapi import Client, LogLevel, logger
from lark_oapi.api.drive.v1 import ListFileRequest
import chardet
from flask import Flask, request, jsonify
import lark_oapi as lark
from attendance import get_user_ids_and_names,get_attendance_data,save_to_csv,get_all_department_ids
from message import fetch_messages_for_chat,fetch_chats,save_messages,str_to_timestamp_seconds
from bitable import read_csv_with_auto_encoding_from_bytes,get_table_ids,extract_app_token,get_all_records
from files import list_files_recursive,extract_folder_token_from_url
from calendars import generate_code_verifier,generate_code_challenge,save_token,ensure_token_valid,load_token
app = Flask(__name__)
TOKEN_STORE_FILE = "token_store.json"
user_access_token = None
token_expires_at = 0
APP_ID = None
APP_SECRET = None
REDIRECT_URI = None
#获取聊天消息
@app.route("/fetch_messages", methods=["POST"])
def fetch_messages_api():
data = request.json
if not data:
return jsonify({"error": "请求体必须是 JSON 格式"}), 400
app_id = data.get("app_id")
app_secret = data.get("app_secret")
if not app_id or not app_secret:
return jsonify({"error": "必须提供 app_id 和 app_secret"}), 400
start_time_str = data.get("start_time")
end_time_str = data.get("end_time")
if not start_time_str or not end_time_str:
return jsonify({"error": "必须提供 start_time 和 end_time格式YYYY-MM-DD HH:MM:SS"}), 400
try:
start_time = str_to_timestamp_seconds(start_time_str)
end_time = str_to_timestamp_seconds(end_time_str)
except ValueError as e:
return jsonify({"error": str(e)}), 400
if start_time > end_time:
return jsonify({"error": "start_time 不能晚于 end_time"}), 400
client = lark.Client.builder() \
.app_id(app_id) \
.app_secret(app_secret) \
.log_level(lark.LogLevel.ERROR) \
.build()
chats = fetch_chats(client)
if not chats:
return jsonify({"error": "获取群聊失败"}), 500
all_messages = []
for chat in chats:
msgs = fetch_messages_for_chat(client, chat.chat_id, chat.name, start_time, end_time)
all_messages.extend(msgs)
if all_messages:
save_messages(all_messages)
return jsonify({"msg": f"已保存消息,共计 {len(all_messages)}"}), 200
else:
return jsonify({"msg": "该时间区间内无消息"}), 200
#获取文档
@app.route("/list_folder", methods=["POST"])
def list_folder():
app_id, app_secret, csv_file = request.form.get("app_id"), request.form.get("app_secret"), request.files.get("csv_file")
if not all([app_id, app_secret, csv_file]): return jsonify({"error": "缺少参数 app_id, app_secret 或 csv_file"}), 400
client = lark.Client.builder().app_id(app_id).app_secret(app_secret).log_level(lark.LogLevel.ERROR).build()
option = lark.RequestOption.builder().build()
raw_bytes = csv_file.stream.read()
encoding = chardet.detect(raw_bytes)["encoding"] or "utf-8"
reader = csv.reader(io.StringIO(raw_bytes.decode(encoding)))
tree_result = []
next(reader, None) # Skip header
for row in reader:
if len(row) < 2: continue
folder_token = extract_folder_token_from_url(row[1].strip())
if folder_token: list_files_recursive(client, folder_token, option, tree_result)
return jsonify(tree_result)
#获取打卡记录
@app.route('/attendance', methods=['POST'])
def attendance():
data = request.get_json()
app_id = data.get("app_id")
app_secret = data.get("app_secret")
start_time_str = data.get("start_time")
end_time_str = data.get("end_time")
if not all([app_id, app_secret, start_time_str, end_time_str]):
return jsonify({"error": "缺少必要的参数: app_id, app_secret, start_time, end_time"}), 400
start_time = str_to_timestamp_seconds(start_time_str)
end_time = str_to_timestamp_seconds(end_time_str)
client = lark.Client.builder() \
.app_id(app_id) \
.app_secret(app_secret) \
.log_level(lark.LogLevel.ERROR) \
.build()
# 获取所有部门 ID
department_ids = get_all_department_ids(client)
print(f"共获取到 {len(department_ids)} 个部门")
for idx, dep_id in enumerate(department_ids, start=1):
print(f"{idx}. {dep_id}")
all_check_in_records = []
for dep_id in department_ids:
user_ids, user_names = get_user_ids_and_names(client, dep_id)
print(f"\n部门 {dep_id} 员工列表(共 {len(user_ids)} 人):")
for uid in user_ids:
print(f" - {uid} : {user_names.get(uid, '未知姓名')}")
dep_records = get_attendance_data(client, user_ids, start_time, end_time, user_names)
all_check_in_records.extend(dep_records)
structured_data, file_path = save_to_csv(all_check_in_records)
return jsonify({
"message": "打卡统计已保存",
"file_path": file_path,
"data": structured_data
}), 200
#获取多维表格数据
@app.route('/fetch_records', methods=['POST'])
def fetch_records():
try:
APP_ID = request.form.get("app_id")
APP_SECRET = request.form.get("app_secret")
file = request.files.get("file")
if not all([APP_ID, APP_SECRET, file]):
return jsonify({"error": "缺少参数 app_id, app_secret 或 上传的文件"}), 400
csv_bytes = file.read()
df = read_csv_with_auto_encoding_from_bytes(csv_bytes)
client = Client.builder().app_id(APP_ID).app_secret(APP_SECRET).log_level(LogLevel.INFO).build()
results = []
for _, row in df.iterrows():
url = row.get("url", "")
app_token = extract_app_token(url)
if not app_token:
continue
table_ids = get_table_ids(client, app_token)
if not table_ids:
continue
tables = []
for table_id in table_ids:
items = get_all_records(client, app_token, table_id)
tables.append({
"table_id": table_id,
"items": items
})
results.append({
"app_token": app_token,
"tables": tables
})
return jsonify(results)
except Exception as e:
return jsonify({"error": str(e)}), 500
#获取日历日程安排
token_data = {}
# 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)