Skip to content

Commit

Permalink
将判断条件由额度改为云端妙记数量
Browse files Browse the repository at this point in the history
  • Loading branch information
bingsanyu committed Nov 17, 2024
1 parent e1509c3 commit 75b23e7
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 94 deletions.
17 changes: 7 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
多线程上传/下载飞书妙记(SRT字幕)

**窗口版请到release中下载**

## 使用场景

- 定期下载飞书会议视频与字幕,实现会议的自动备份。
- 定期检查 ~~妙记额度~~统一存储空间 使用情况,快要超出则删除旧的妙记
- 定期检查云端妙记数量,超出限制则删除旧的妙记
- 从本地上传视频后导出字幕,实现语音转文字。

## 使用步骤
## 使用步骤(如果自己运行脚本)

1. 首先安装 requests 库和 tqdm 库 `pip install requests tqdm`

2. 打开[飞书妙记主页](https://meetings.feishu.cn/minutes/home),按F12打开开发者工具,点击`网络`栏,刷新后复制网络请求 *list?size=20&space_name=* 中的`cookie`,粘贴至`config.ini``minutes_cookie`
2. 打开[飞书妙记主页](https://meetings.feishu.cn/minutes/home),按F12打开开发者工具,点击`网络`栏,刷新后复制网络请求 *list?size=20&space_name=* 中的`cookie`,粘贴至`config.ini``cookie`

**下载妙记**

3. (可选)~~妙记额度~~统一存储空间 不足才进行删除,以保证云端有尽量多的妙记:在[飞书管理后台](https://home.feishu.cn/admin/index)按F12,刷新后复制网络请求 *count?_t=* 中的`cookie`,粘贴至`config.ini``manager_cookie`
4. 根据自身需求修改`config.ini`中的参数。
5. 执行 `python feishu_downloader.py`
3. 根据自身需求修改`config.ini`中的参数。
4. 执行 `python feishu_downloader.py`

**上传妙记**

Expand All @@ -28,6 +25,6 @@
## 注意事项

- 下载需要用到aria2,本仓库中给出的是win64版本的,如果你是其他操作系统请在 https://github.com/aria2/aria2/releases 中下载相应版本并替换。
- `minutes_cookie`是以 *minutes_csrf_token=* 为开头的很长的一个字符串。
- `manager_cookie`是以 *trust_browser_id=* 为开头的很长的一个字符串
- `cookie`是以 *minutes_csrf_token=* 为开头的很长的一个字符串。
- [飞书存储空间规则更新](https://www.feishu.cn/announcement/pricing-adjustment2024)后删除妙记不能及时更新额度,且妙记额度不再单独计算,故采用妙记数量(而不是按照已用额度)来判断是否要删除旧妙记
- [飞书分片上传文件API](https://open.feishu.cn/document/server-docs/docs/drive-v1/upload/multipart-upload-file-/introduction) 中声明该接口不支持太高的并发且调用频率上限为5QPS,且本人无批量转文字需求,故未对多个文件的同时转写进行尝试。本项目仅为实现上传与下载的自动化。
10 changes: 2 additions & 8 deletions config.ini
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
[Cookies]
# 上传和下载都需要使用该cookie
# 在飞书妙记主页 https://meetings.feishu.cn/minutes/home 获取
minutes_cookie =

#(可选)
# 需身份为企业创建人、超级管理员或普通管理员
# 在飞书管理后台 https://home.feishu.cn/admin/index 获取
manager_cookie =
cookie =

[上传设置]
要上传的文件所在路径(目前仅支持单个文件) =
Expand All @@ -20,8 +14,8 @@ manager_cookie =
文件类型 = 2

每次检查的妙记数量 = 1000
保留云端妙记的最大数量 = 100
检查妙记的时间间隔(单位s,太短容易报错) = 3600
视频会议最大存储额度(单位GB,默认10GB)(填写了manager_cookie才有效) = 10
保存路径(不填则默认为当前路径/data) =
是否只下载字幕文件(是/否) = 否
字幕格式(srt/txt) = srt
Expand Down
122 changes: 46 additions & 76 deletions feishu_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@
config = configparser.ConfigParser(interpolation=None)
config.read('config.ini', encoding='utf-8')
# 获取配置文件中的cookie
minutes_cookie = config.get('Cookies', 'minutes_cookie')
manager_cookie = config.get('Cookies', 'manager_cookie')
minutes_cookie = config.get('Cookies', 'cookie')
# 获取下载设置
space_name = int(config.get('下载设置', '所在空间'))
list_size = int(config.get('下载设置', '每次检查的妙记数量'))
vc_max_num = int(config.get('下载设置', '保留妙记的最大数量'))
check_interval = int(config.get('下载设置', '检查妙记的时间间隔(单位s,太短容易报错)'))
download_type = int(config.get('下载设置', '文件类型'))
subtitle_only = config.get('下载设置', '是否只下载字幕文件(是/否)')=='是'
vc_quota_bytes = float(config.get('下载设置', '视频会议最大存储额度(单位GB,默认10GB)(填写了manager_cookie才有效)')) * 2 ** 30
# 获取保存路径
save_path = config.get('下载设置', '保存路径(不填则默认为当前路径/data)')
if not save_path:
Expand Down Expand Up @@ -52,7 +51,8 @@ def __init__(self, cookie):
}
if len(self.headers.get('bv-csrf-token')) != 36:
raise Exception("minutes_cookie中不包含bv_csrf_token,请确保从请求`list?size=20&`中获取!")

self.all_minutes = []
self.minutes_num = 0
self.meeting_time_dict = {} # 会议文件名称和会议时间的对应关系
self.subtitle_type = 'srt' if subtitle_params['format']==3 else 'txt'

Expand All @@ -65,7 +65,8 @@ def get_minutes(self):
# 如果resp.json()['data']中没有list字段,则说明cookie失效
if 'list' not in resp.json()['data']:
raise Exception("minutes_cookie失效,请重新获取!")
return list(reversed(resp.json()['data']['list'])) # 返回按时间正序排列的妙记信息(从旧到新)
self.all_minutes = list(reversed(resp.json()['data']['list'])) # 按时间正序排列的妙记信息(从旧到新)
self.minutes_num = len(self.all_minutes)

def check_minutes(self):
"""
Expand All @@ -78,15 +79,17 @@ def check_minutes(self):
with open('minutes.txt', 'r') as f:
downloaded_minutes = set(line.strip() for line in f)

# 获取所有妙记
all_minutes = self.get_minutes()
# 获取云端所有妙记
self.get_minutes()
print(f"云端现有 {self.minutes_num} 个妙记")

# 过滤需要下载的妙记
need_download_minutes = [
minutes for minutes in all_minutes
minutes for minutes in self.all_minutes
if minutes['object_token'] not in downloaded_minutes and
(download_type == 2 or minutes['object_type'] == download_type)
]
print(f"需要下载 {len(need_download_minutes)} 个妙记")

# 如果有需要下载的妙记则进行下载
if need_download_minutes:
Expand All @@ -95,7 +98,7 @@ def check_minutes(self):
with open('minutes.txt', 'a') as f:
for minutes in need_download_minutes:
f.write(minutes['object_token']+'\n')
print(f"成功下载了{len(need_download_minutes)}个妙记,等待{check_interval}s后再次检查...")
print(f"成功下载了 {len(need_download_minutes)} 个妙记,等待 {check_interval} 秒后再次检查...")

def download_minutes(self, minutes_list):
"""
Expand Down Expand Up @@ -179,76 +182,43 @@ def delete_minutes(self, num):
"""
删除指定数量的最早几个妙记
"""
all_minutes = self.get_minutes()

for index in tqdm(all_minutes[:num], desc='删除妙记'):
try:
# 将该妙记放入回收站
delete_url = f'https://meetings.feishu.cn/minutes/api/space/delete'
params = {'object_tokens': index['object_token'],
'is_destroyed': 'false',
'language': 'zh_cn'}
resp = requests.post(url=delete_url, params=params, headers=self.headers, proxies=proxies)
if resp.status_code != 200:
raise Exception(f"删除妙记 http://meetings.feishu.cn/minutes/{index['object_token']} 失败!{resp.json()}")

# 将该妙记彻底删除
params['is_destroyed'] = 'true'
resp = requests.post(url=delete_url, params=params, headers=self.headers, proxies=proxies)
if resp.status_code != 200:
raise Exception(f"删除妙记 http://meetings.feishu.cn/minutes/{index['object_token']} 失败!{resp.json()}")
except Exception as e:
print(f"{e} 可能是没有该妙记的权限。")
num += 1
continue
old_all_minutes = self.all_minutes
successed_num = 0
unsuccessed_num = 0
for index in tqdm(old_all_minutes[:num+unsuccessed_num], desc='删除妙记'):
old_minutes_num = self.minutes_num
# 将该妙记放入回收站
delete_url = f'https://meetings.feishu.cn/minutes/api/space/delete'
params = {'object_tokens': index['object_token'],
'is_destroyed': 'false',
'language': 'zh_cn'}
requests.post(url=delete_url, params=params, headers=self.headers, proxies=proxies)
# 将该妙记彻底删除
requests.post(url=delete_url, params=params.update({'is_destroyed': 'true'}), headers=self.headers, proxies=proxies)
time.sleep(3)
self.get_minutes()
if self.minutes_num == old_minutes_num:
print(f"删除 http://meetings.feishu.cn/minutes/{index['object_token']} 失败,可能是没有该妙记的权限")
unsuccessed_num += 1
else:
successed_num += 1
if successed_num == num:
break
print(f"成功删除 {successed_num} 个妙记,跳过 {unsuccessed_num} 个妙记")

if __name__ == '__main__':

if not minutes_cookie:
raise Exception("cookie不能为空!")

# 如果未填写管理参数,则定时检查是否有要下载的妙记
elif not manager_cookie:
while True:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
downloader = FeishuDownloader(minutes_cookie)
# 如果下载到了妙记则删除最早的一个妙记
if downloader.check_minutes():
downloader.delete_minutes(1)
time.sleep(check_interval)

# 如果填写了管理参数,则定时查询妙记空间使用情况,超出指定额度则删除最早的指定数量的妙记
else:
# 从manager_cookie中获取X-Csrf-Token
x_csrf_token = manager_cookie[manager_cookie.find(' csrf_token=') + len(' csrf_token='):manager_cookie.find(';', manager_cookie.find(' csrf_token='))]
if len(x_csrf_token) != 36:
raise Exception("manager_cookie中不包含csrf_token,请确保从请求`count?_t=`中获取!")

vc_used_bytes_old = 0 # 上次记录的已经使用的字节数
# 定期查询已使用的妙记空间字节数
while True:
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
# query_url = f'https://www.feishu.cn/suite/admin/api/gaea/usages'
query_url = 'https://www.feishu.cn/suite/admin/resource/storage/overview?_t='+str(int(time.time()*1000))
manager_headers = {'cookie': manager_cookie, 'X-Csrf-Token': x_csrf_token}
res = requests.get(url=query_url, headers=manager_headers, proxies=proxies)
vc_used_bytes = int(res.json()['data']['detail']['vc'])
all_quota_bytes = int(res.json()['data']['quota'])
all_used_bytes = int(res.json()['data']['used'])
print(f"视频会议存储空间:{vc_used_bytes / 2 ** 30:.2f}GB/{vc_quota_bytes / 2 ** 30:.2f}GB")
print(f"总存储空间:{all_used_bytes / 2 ** 30:.2f}GB/{all_quota_bytes / 2 ** 30:.2f}GB")
if vc_used_bytes != vc_used_bytes_old:
downloader = FeishuDownloader(minutes_cookie)
downloader.check_minutes()
# 如果已用超过指定阈值则删除最早的两个妙记
while vc_used_bytes > vc_quota_bytes:
print(f"已用空间超过{vc_quota_bytes / 2 ** 30:.2f}GB,删除最早的两个妙记")
downloader.delete_minutes(2)
time.sleep(3)
res = requests.get(url=query_url, headers=manager_headers, proxies=proxies)
res.raise_for_status()
vc_used_bytes = int(res.json()['data']['detail']['vc'])
print(f"视频会议存储空间:{vc_used_bytes / 2 ** 30:.2f}GB/{vc_quota_bytes / 2 ** 30:.2f}GB")
print(f"等待{check_interval}s后再次检查...")
vc_used_bytes_old = vc_used_bytes # 更新已用字节数
time.sleep(check_interval)
# 定时检查是否有要下载的妙记
while True:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
downloader = FeishuDownloader(minutes_cookie)
# 检查是否存在需要下载的妙记
downloader.check_minutes()
# 如果云端的妙记数量超过了最大限制,则删除最早的几个妙记
if downloader.minutes_num > vc_max_num:
print(f"删除最早的 {downloader.minutes_num - vc_max_num} 个妙记")
downloader.delete_minutes(downloader.minutes_num - vc_max_num)
time.sleep(check_interval)

0 comments on commit 75b23e7

Please sign in to comment.