This commit is contained in:
liubiren 2026-03-22 21:41:58 +08:00
parent 0f37c81377
commit 262644b9a6
5 changed files with 145 additions and 39 deletions

View File

@ -11,6 +11,7 @@ from pathlib import Path
import threading
import time
from typing import Optional, Tuple
from datetime import datetime
from request import Request
@ -70,19 +71,20 @@ class Authenticator:
) # 指定服务商的访问令牌和失效时间戳
if time.time() > expired_timestamp:
match servicer:
# 刷新深圳快瞳访问凭证cd C:\Python\.venv\Scripts
# 获取深圳快瞳访问凭证cd C:\Python\.venv\Scripts
case "inspirvision":
token, expired_timestamp = (
self._refresh_inspirvision_certification()
self._get_inspirvision_certification()
)
# 刷新合力亿捷访问凭证
# 获取合力亿捷访问凭证
case "hollycrm":
token, expired_timestamp = (
self._refresh_hollycrm_certification()
)
# 刷新飞书访问凭证
token, expired_timestamp = self._get_hollycrm_certification()
# 获取飞书访问凭证
case "feishu":
token, expired_timestamp = self._refresh_feishu_certification()
token, expired_timestamp = self._get_feishu_certification()
# 获取 Cloudreve 访问凭证
case "cloudreve":
token, expired_timestamp = self._get_cloudreve_certification()
case _:
raise RuntimeError(f"未知服务商:{servicer}")
@ -103,9 +105,9 @@ class Authenticator:
)
return token
def _refresh_inspirvision_certification(self) -> Tuple[str, float]:
def _get_inspirvision_certification(self) -> Tuple[str, float]:
"""
刷新深圳快瞳访问凭证
获取深圳快瞳访问凭证
:return: 深圳快瞳访问令牌和失效时间戳
"""
response = self.request.get(
@ -113,15 +115,15 @@ class Authenticator:
)
# 若非响应成功则抛出异常
if not (response["status"] == 200 and response["code"] == 0):
raise RuntimeError("刷新深圳快瞳访问凭证发生异常")
raise RuntimeError("获取深圳快瞳访问凭证发生异常")
return (
response["data"]["access_token"],
time.time() + response["data"]["expires_in"],
) # https://inspirvision.cn/spa/documentCenter/ocr-finance/mark?id=3 深圳快瞳获取访问凭证接口,其中 access_token 为访问令牌expires_in 为有效剩余时间(单位为秒)
def _refresh_hollycrm_certification(self) -> Tuple[str, float]:
def _get_hollycrm_certification(self) -> Tuple[str, float]:
"""
刷新合力亿捷访问凭证
获取合力亿捷访问凭证
:return: 合力亿捷访问令牌和失效时间戳
"""
# 企业访问标识
@ -142,15 +144,15 @@ class Authenticator:
)
# 若非响应成功则抛出异常
if not response["success"]:
raise RuntimeError("刷新合力亿捷访问凭证发生异常")
raise RuntimeError("获取合力亿捷访问凭证发生异常")
return (
response["data"],
time.time() + 1 * 3600, # 访问令牌有效期为1小时
)
def _refresh_feishu_certification(self) -> Tuple[str, float]:
def _get_feishu_certification(self) -> Tuple[str, float]:
"""
刷新飞书访问凭证
获取飞书访问凭证
:return: 飞书访问令牌和失效时间戳
"""
response = self.request.post(
@ -162,8 +164,30 @@ class Authenticator:
)
# 若非响应成功则抛出异常
if not response["code"] == 0:
raise RuntimeError("刷新飞书访问凭证发生异常")
raise RuntimeError("获取飞书访问凭证发生异常")
return (
response["tenant_access_token"],
time.time() + response["expire"],
) # https://open.feishu.cn/document/server-docs/api-call-guide/calling-process/get-access-token 飞书获取访问凭证接口
def _get_cloudreve_certification(self) -> Tuple[str, float]:
"""
获取 Cloudreve 访问凭证
:return: Cloudreve 访问令牌和失效时间戳
"""
response = self.request.post(
url="https://cloudreve.liubiren.cloud/api/v4/session/token",
json={
"email": "marslbr@qq.com",
"password": "Cl198752",
},
)
# 若非响应成功则抛出异常
if not response["code"] == 0:
raise RuntimeError("获取 Cloudreve 访问凭证发生异常")
return (
response["data"]["token"]["access_token"],
datetime.fromisoformat(
response["data"]["token"]["access_expires"]
).timestamp(),
) # https://docs.cloudreve.org/zh/api/auth 通过登录获取 AccessToken时效时重新登录重新获取

View File

@ -1 +1 @@
{"feishu": ["t-g1043jkq4UBLA2TLB6HFDIUPFXIQBTCHUMZ5XPYX", 1773930401.8437002]}
{"feishu": ["t-g1043mboYRA4SDDKUY5RTRLCHPF3YGJQIYCLGDVP", 1774157093.7511718]}

69
utils/cloudreve.py Normal file
View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""
封装 Cloudreve 客户端
"""
from email.parser import BytesParser
from email.policy import default
from email.utils import parsedate_to_datetime
from imaplib import IMAP4_SSL
import re
from time import time
from typing import Any, Dict, Optional, List
from pydantic.config import JsonEncoder
from authenticator import Authenticator
from request import Request
from base64 import b64encode
from urllib.parse import quote
class Cloudreve:
"""Cloudreve 客户端"""
def __init__(self):
# 实例化认证器
self.authenticator = Authenticator()
# 实例化请求客户端
self.http_client = Request()
def _create_upload_session(self, uri: str, size: int) -> str:
"""
创建上传会话
:param uri: 统一资源标识符
:param size: 文件大小
:return: 上传会话标识
"""
response = self.http_client.put(
url=f"https://cloudreve.liubiren.cloud/api/v4/file/upload",
headers={
"Authorization": f"Bearer {self.authenticator.get_token(servicer="cloudreve")}",
"Content-Type": "application/json; charset=utf-8",
},
json={
"uri": quote(string=uri, safe=":/?&="), # 编码统一资源标识
"size": size,
},
)
# 若非响应成功则抛出异常
if not response["code"] == 0:
raise RuntimeError("创建上传会话发生异常")
return response["data"]["session_id"]
def _upload_file_chunk(self, session_id: str, index: int, file_chunk: bytes) -> str:
"""
上传文件块
:param session_id: 上传会话标识
:param index: 文件块索引
:return: 上传文件标识
"""
response = self.http_client.post(
url=f"https://cloudreve.liubiren.cloud/api/v4/file/upload/{session_id}/{index}",
headers={
"Authorization": f"Bearer {self.authenticator.get_token(servicer="cloudreve")}",
"Content-Type": "application/octet-stream",
"Content-Length": len(file_chunk),
},
json=open(file_path, "rb").read(),
)

View File

@ -38,7 +38,7 @@ class Feishu:
"""
使用示例
feishu = Feishu()
print(feishu.get_mail_verification_code(folder="邮箱验证码", regular_expression=r"【普康健康】您的验证码是:(\\d+)"))
feishu.get_mail_verification_code(folder="邮箱验证码", regular_expression=r"【普康健康】您的验证码是:(\\d+)")
输出123456
"""
if not folder:
@ -209,15 +209,20 @@ class Feishu:
return records
def download_material(self, file_token: str, stream_enabled: bool = False) -> str:
def _convert_to_cloudreve_direct_link(self, material_token: str) -> str:
"""
下载素材
:param file_token: 素材标识
:param stream_enabled: 使用流式传输默认 False
:return: 素材 base64 编码的字符串
转为Cloudreve直链
:param material_token: 素材标识
:return: 素材直链地址
"""
# 获取 Cloudreve 上传 session_id
session_id = self.http_client.
# 构建下载素材的请求地址
url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" # https://open.feishu.cn/document/server-docs/docs/drive-v1/media/download
url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{material_token}/download" # https://open.feishu.cn/document/server-docs/docs/drive-v1/media/download
headers = self._get_headers()
# 添加 Content-Type 请求头
@ -230,9 +235,9 @@ class Feishu:
response = self.http_client.download(
url=url,
headers=headers,
stream_enabled=stream_enabled,
)
print(response)
stream_enabled=True,
) # 默认使用流式传输
a = Feishu()

View File

@ -171,8 +171,7 @@ class Caches(SQLite):
class Request:
"""
请求客户端支持
getGET请求
postPOST请求
GETPUT和POST等请求
download下载
"""
@ -251,6 +250,14 @@ class Request:
"""
return self._request(method="GET", parameters=Parameters(**kwargs))
def put(self, **kwargs) -> Any:
"""
GET请求
:param kwargs: 请求参数
:return: 响应内容
"""
return self._request(method="PUT", parameters=Parameters(**kwargs))
def post(self, **kwargs) -> Any:
"""
POST请求
@ -264,12 +271,12 @@ class Request:
return self._request(method="POST", parameters=Parameters(**kwargs))
def download(
self, stream_enabled: bool = False, chunk_size: int = 1024, **kwargs
self, stream_enabled: bool = False, chunk_size: int = 1024 * 1024, **kwargs
) -> Any:
"""
下载
:param stream_enabled: 使用流式传输
:param chunk_size: 流式传输的分块大小
:param stream_enabled: 使用流式传输默认为关闭流式传输
:param chunk_size: 分块大小若开启流式传输则分块大小默认为 1MB
:param kwargs: 请求参数
:return: 响应内容
"""
@ -277,15 +284,18 @@ class Request:
method="GET",
parameters=Parameters(**{"stream_enabled": stream_enabled, **kwargs}),
)
# 若使用流式传输则处理流式传输响应
# 若使用流式传输则返回响应内容迭代器
if stream_enabled:
return self._process_stream_response(
response=response, chunk_size=chunk_size
)
return response
@restrict(max_tokens=5, refill_rate=5.0)
def _request(self, method: Literal["GET", "POST"], parameters: Parameters) -> Any:
def _request(
self, method: Literal["GET", "PUT", "POST"], parameters: Parameters
) -> Any:
"""
请求
:param method: 请求方法
@ -393,11 +403,9 @@ class Request:
# 处理流式传输响应
@staticmethod
def _process_stream_response(
response: Response, chunk_size: int
) -> Generator[bytes, None, None]:
def _process_stream_response(response: Response, chunk_size: int) -> Generator:
"""
处理流式响应
处理流式传输响应
:param response: 响应对象
:param chunk_size: 分块大小
:return: 响应内容迭代器