Files
dt_audio/feishu_downloader.ipynb
alsesa b10fd0e112 merge
2024-10-14 17:39:25 +08:00

400 lines
24 KiB
Plaintext
Executable File
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.
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "5ab50d0f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2023-10-21 13:58:26\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Downloading Meetings: 50%|█████████████▌ | 1/2 [00:17<00:17, 17.92s/ meeting]Exception in thread Thread-38 (download_range):\n",
"Traceback (most recent call last):\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 467, in _make_request\n",
" six.raise_from(e, None)\n",
" File \"<string>\", line 3, in raise_from\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 462, in _make_request\n",
" httplib_response = conn.getresponse()\n",
" ^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3.11/http/client.py\", line 1378, in getresponse\n",
" response.begin()\n",
" File \"/usr/lib/python3.11/http/client.py\", line 318, in begin\n",
" version, status, reason = self._read_status()\n",
" ^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3.11/http/client.py\", line 279, in _read_status\n",
" line = str(self.fp.readline(_MAXLINE + 1), \"iso-8859-1\")\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3.11/socket.py\", line 706, in readinto\n",
" return self._sock.recv_into(b)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3.11/ssl.py\", line 1311, in recv_into\n",
" return self.read(nbytes, buffer)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3.11/ssl.py\", line 1167, in read\n",
" return self._sslobj.read(len, buffer)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
"TimeoutError: The read operation timed out\n",
"\n",
"During handling of the above exception, another exception occurred:\n",
"\n",
"Traceback (most recent call last):\n",
" File \"/usr/lib/python3/dist-packages/requests/adapters.py\", line 486, in send\n",
" resp = conn.urlopen(\n",
" ^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 799, in urlopen\n",
" retries = retries.increment(\n",
" ^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/urllib3/util/retry.py\", line 550, in increment\n",
" raise six.reraise(type(error), error, _stacktrace)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/six.py\", line 719, in reraise\n",
" raise value\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 715, in urlopen\n",
" httplib_response = self._make_request(\n",
" ^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 469, in _make_request\n",
" self._raise_timeout(err=e, url=url, timeout_value=read_timeout)\n",
" File \"/usr/lib/python3/dist-packages/urllib3/connectionpool.py\", line 358, in _raise_timeout\n",
" raise ReadTimeoutError(\n",
"urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='internal-api-drive-stream.feishu.cn', port=443): Read timed out. (read timeout=30)\n",
"\n",
"During handling of the above exception, another exception occurred:\n",
"\n",
"Traceback (most recent call last):\n",
" File \"/usr/lib/python3.11/threading.py\", line 1045, in _bootstrap_inner\n",
" self.run()\n",
" File \"/usr/lib/python3.11/threading.py\", line 982, in run\n",
" self._target(*self._args, **self._kwargs)\n",
" File \"/tmp/ipykernel_25915/2918984942.py\", line 45, in download_range\n",
" File \"/usr/lib/python3/dist-packages/requests/api.py\", line 73, in get\n",
" return request(\"get\", url, params=params, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/requests/api.py\", line 59, in request\n",
" return session.request(method=method, url=url, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/requests/sessions.py\", line 589, in request\n",
" resp = self.send(prep, **send_kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/requests/sessions.py\", line 703, in send\n",
" r = adapter.send(request, **kwargs)\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/usr/lib/python3/dist-packages/requests/adapters.py\", line 532, in send\n",
" raise ReadTimeout(e, request=request)\n",
"requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='internal-api-drive-stream.feishu.cn', port=443): Read timed out. (read timeout=30)\n",
"Downloading Meetings: 100%|███████████████████████████| 2/2 [00:50<00:00, 25.42s/ meeting]\n",
"Deleting Meetings: 100%|██████████████████████████████| 1/1 [00:00<00:00, 1.18 meeting/s]\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[2], line 227\u001b[0m\n\u001b[1;32m 225\u001b[0m downloader\u001b[38;5;241m.\u001b[39mcheck_meetings()\n\u001b[1;32m 226\u001b[0m downloader\u001b[38;5;241m.\u001b[39mdelete_minutes(\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m--> 227\u001b[0m \u001b[43mtime\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m3600\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 229\u001b[0m \u001b[38;5;66;03m# 如果填写了管理参数,则定时查询妙记空间使用情况,超出指定额度则删除最早的指定数量的会议\u001b[39;00m\n\u001b[1;32m 230\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m :\n\u001b[1;32m 231\u001b[0m \u001b[38;5;66;03m# 从manager_cookie中获取X-Csrf-Token\u001b[39;00m\n\u001b[1;32m 232\u001b[0m x_csrf_token \u001b[38;5;241m=\u001b[39m manager_cookie[manager_cookie\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m csrf_token=\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mlen\u001b[39m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m csrf_token=\u001b[39m\u001b[38;5;124m'\u001b[39m):manager_cookie\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m;\u001b[39m\u001b[38;5;124m'\u001b[39m, manager_cookie\u001b[38;5;241m.\u001b[39mfind(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m csrf_token=\u001b[39m\u001b[38;5;124m'\u001b[39m))]\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"import os, re, shutil, time, threading\n",
"import requests\n",
"from tqdm import tqdm\n",
"\n",
"\n",
"# 不使用系统代理\n",
"proxies = {\"http\": None, \"https\": None}\n",
"\n",
"# 多线程下载器\n",
"class MultiDownloader:\n",
" def __init__(self, headers, url, file_name, thread_count=20):\n",
" self.headers = headers\n",
" self.url = url\n",
" self.file_name = file_name\n",
" self.thread_count = thread_count\n",
" self.chunk_size = 1024 * 1024\n",
" self.total_range = self.get_file_size()\n",
" self.file_lock = threading.Lock()\n",
"\n",
" def get_file_size(self):\n",
" res = requests.head(self.url, headers=self.headers, proxies=proxies)\n",
" if res.status_code == 200:\n",
" return int(res.headers.get('Content-Length'))\n",
" return None\n",
"\n",
" def page_dispatcher(self, content_size):\n",
" page_size = content_size // self.thread_count\n",
" start_pos = 0\n",
" while start_pos + page_size < content_size:\n",
" yield {\n",
" 'start_pos': start_pos,\n",
" 'end_pos': start_pos + page_size\n",
" }\n",
" start_pos += page_size + 1\n",
" yield {\n",
" 'start_pos': start_pos,\n",
" 'end_pos': content_size - 1\n",
" }\n",
"\n",
" def download_range(self, thread_name, page, file_handler):\n",
" range_headers = {\"Range\": f\"bytes={page['start_pos']}-{page['end_pos']}\"}\n",
" range_headers |= self.headers\n",
" try_times = 3\n",
" for _ in range(try_times):\n",
" with requests.get(url=self.url, headers=range_headers, stream=True, timeout=30, proxies=proxies) as res:\n",
" if res.status_code == 206:\n",
" for data in res.iter_content(chunk_size=self.chunk_size):\n",
" with self.file_lock:\n",
" file_handler.seek(page[\"start_pos\"])\n",
" file_handler.write(data)\n",
" page[\"start_pos\"] += len(data)\n",
" break\n",
"\n",
" def run(self):\n",
" if not self.total_range or self.total_range < 1024:\n",
" raise Exception(\"get file total size failed\")\n",
" if os.path.exists(self.file_name.split('/')[0]) and os.path.exists(self.file_name) and os.path.getsize(self.file_name) != 0:\n",
" while True:\n",
" choice = input(f\" {self.file_name.split('/')[0]}已存在,是否覆盖(Y/n)?\")\n",
" if choice == 'y' or choice == 'Y' or choice == '':\n",
" break\n",
" elif choice == 'n' or choice == 'N':\n",
" return\n",
" else:\n",
" os.mkdir(self.file_name.split('/')[0])\n",
" thread_list = []\n",
" with open(self.file_name, \"wb+\") as f:\n",
" for i, page in enumerate(self.page_dispatcher(self.total_range)):\n",
" thread_list.append(threading.Thread(target=self.download_range, args=(i, page, f)))\n",
" for thread in thread_list:\n",
" thread.start()\n",
" for thread in thread_list:\n",
" thread.join()\n",
"\n",
"\n",
"# 会议下载器\n",
"class MeetingDownloader:\n",
" def __init__(self, cookie):\n",
" self.headers = {\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36',\n",
" 'cookie': cookie,\n",
" 'bv-csrf-token': cookie[cookie.find('bv_csrf_token=') + len('bv_csrf_token='):cookie.find(';', cookie.find('bv_csrf_token='))],\n",
" 'referer': f'https://meetings.feishu.cn/minutes/me',\n",
" 'content-type': 'application/x-www-form-urlencoded'\n",
" }\n",
" if len(self.headers.get('bv-csrf-token')) != 36:\n",
" raise Exception(\"cookie中不包含bv_csrf_token请确保从请求`list?size=20&`中获取!\")\n",
" \n",
" def get_meeting_info(self):\n",
" \"\"\"\n",
" 批量获取妙记信息\n",
"\n",
" size的取值获取的妙记数量\n",
"\n",
" space_name的取值\n",
" 1主页包含企业内部妙记与外部妙记\n",
" 2我的内容只包含归属人为自己的妙记\n",
" \"\"\"\n",
" get_rec_url = f\"https://meetings.feishu.cn/minutes/api/space/list?&size=1000&space_name=2\"\n",
" resp = requests.get(url=get_rec_url, headers=self.headers, proxies=proxies)\n",
" return list(reversed(resp.json()['data']['list'])) # 返回按时间正序排列的妙记信息(从旧到新)\n",
"\n",
" def download_video(self, minutes_info):\n",
" \"\"\"\n",
" 下载单个妙记视频\n",
" \"\"\"\n",
" # 获取妙记视频的下载链接\n",
" video_url_url = f\"https://meetings.feishu.cn/minutes/api/status?object_token={minutes_info['object_token']}&language=zh_cn&_t={int(time.time() * 1000)}\"\n",
" resp = requests.get(url=video_url_url, headers=self.headers, proxies=proxies)\n",
" video_url = resp.json()['data']['video_info']['video_download_url']\n",
"\n",
" # 根据会议的起止时间和会议标题来设置文件名\n",
" start_time = time.strftime(\"%Y年%m月%d日%H时%M分\", time.localtime(minutes_info['start_time'] / 1000))\n",
" stop_time = time.strftime(\"%Y年%m月%d日%H时%M分\", time.localtime(minutes_info['stop_time'] / 1000))\n",
" file_name = start_time+\"至\"+stop_time+minutes_info['topic']\n",
"\n",
" # 将文件名中的特殊字符替换为下划线\n",
" rstr = r\"[\\/\\\\\\:\\*\\?\\\"\\<\\>\\|]\" # '/ \\ : * ? \" < > |'\n",
" file_name = re.sub(rstr, \"_\", file_name)\n",
"\n",
" # 多线程下载\n",
" run_params = {'headers': self.headers,\n",
" 'url': video_url,\n",
" 'file_name': f'{file_name}/{file_name}.mp4',\n",
" 'thread_count': 20\n",
" }\n",
" downloader = MultiDownloader(**run_params)\n",
" downloader.run()\n",
"\n",
" return file_name\n",
"\n",
" def download_subtitle(self, object_token, file_name, file_mtime):\n",
" \"\"\"\n",
" 下载单个妙记字幕\n",
" \"\"\"\n",
" srt_url = f\"https://meetings.feishu.cn/minutes/api/export\"\n",
" params = {'add_speaker': 'true', # 包含说话人\n",
" 'add_timestamp': 'true', # 包含时间戳\n",
" 'format': '3', # SRT格式\n",
" 'object_token': object_token, # 妙记id\n",
" }\n",
" resp = requests.post(url=srt_url, params=params, headers=self.headers, proxies=proxies)\n",
"\n",
" # 如果cookie选择的不对可能会出现能下载视频但无法下载字幕的情况\n",
" if resp.status_code != 200:\n",
" raise Exception(f\"下载字幕失败请检查你的cookie\\nStatus code: {resp.status_code}\")\n",
" \n",
" # 写入对应视频的文件夹\n",
" resp.encoding = \"utf-8\"\n",
" with open(f\"{file_name}/{file_name}.srt\", \"w+\") as f:\n",
" f.write(resp.text)\n",
"\n",
" # 将文件最后修改时间改为会议结束时间\n",
" os.utime(f\"{file_name}/{file_name}.srt\", (file_mtime, file_mtime))\n",
" os.utime(f\"{file_name}/{file_name}.mp4\", (file_mtime, file_mtime))\n",
" os.utime(f\"{file_name}\", (file_mtime, file_mtime))\n",
"\n",
" def check_meetings(self):\n",
" \"\"\"\n",
" 检查需要下载的会议\n",
" \"\"\"\n",
" all_meetings = self.get_meeting_info()\n",
" need_download_meetings = []\n",
"\n",
" # 检查记录中不存在的会议id进行下载\n",
" if os.path.exists('meetings.txt'):\n",
" with open('meetings.txt', 'r') as f:\n",
" downloaded_meetings = f.readlines()\n",
" need_download_meetings = [index for index in all_meetings if index['meeting_id']+'\\n' not in downloaded_meetings]\n",
" else:\n",
" need_download_meetings = all_meetings\n",
" # 如果有需要下载的会议则进行下载\n",
" if need_download_meetings:\n",
" for index in tqdm(need_download_meetings, desc='Downloading Meetings', unit=' meeting'):\n",
" # 下载妙记视频\n",
" file_name = self.download_video(index)\n",
" # 下载妙记字幕\n",
" self.download_subtitle(index['object_token'], file_name, index['stop_time']/1000)\n",
" # 将已下载的妙记所对应的会议id记录到文件中\n",
" with open('meetings.txt', 'a+') as f:\n",
" f.write(index['meeting_id'] + '\\n')\n",
"\n",
" def delete_minutes(self, num):\n",
" \"\"\"\n",
" 删除指定数量的最早几个妙记\n",
" \"\"\"\n",
" all_meetings = self.get_meeting_info()\n",
" num = num if num <= len(all_meetings) else 1\n",
" need_delete_meetings = all_meetings[:num]\n",
"\n",
" for index in tqdm(need_delete_meetings, desc='Deleting Meetings', unit=' meeting'):\n",
" \n",
" # 将该妙记放入回收站\n",
" delete_url = f\"https://meetings.feishu.cn/minutes/api/space/delete\"\n",
" params = {'object_tokens': index['object_token'],\n",
" 'is_destroyed': 'false',\n",
" 'language': 'zh_cn'}\n",
" resp = requests.post(url=delete_url, params=params, headers=self.headers, proxies=proxies)\n",
" if resp.status_code != 200:\n",
" raise Exception(f\"删除会议{index['meeting_id']}失败!{resp.json()}\")\n",
" \n",
" # 将该妙记彻底删除\n",
" params['is_destroyed'] = 'true'\n",
" resp = requests.post(url=delete_url, params=params, headers=self.headers, proxies=proxies)\n",
" if resp.status_code != 200:\n",
" raise Exception(f\"删除会议{index['meeting_id']}失败!{resp.json()}\")\n",
"\n",
"\n",
"if __name__ == '__main__':\n",
"\n",
" # 在飞书妙记主页 https://meetings.feishu.cn/minutes/home 获取cookie\n",
" minutes_cookie = \"minutes_csrf_token=ea0b6372-d541-49fd-9fca-e6e579ed6c56; m_ce8f16=65613062363337322d643534312d343966642d396663612d653665353739656436633536b37b91f4efa89b27b410d5626626f1b2ebf3ed82d6b510030362344fb5776178; __tea__ug__uid=2399591691287585247; Hm_lvt_e78c0cb1b97ef970304b53d2097845fd=1691287586; Hm_lpvt_e78c0cb1b97ef970304b53d2097845fd=1691287586; _gcl_au=1.1.1631485248.1691287586; passport_web_did=7264024950279913500; QXV0aHpDb250ZXh0=825bfa61b8ce47d6a89b987cdb581ff9; locale=zh-CN; trust_browser_id=48711549-c255-44b4-820f-442ea9608738; fid=80a777ac-f638-404f-8e7c-0902ac3cf140; lang=zh; _csrf_token=d0a421e1a93d873197fbf7134ab4c8f6a76769dd-1697598422; landing_url=https://login.feishu.cn/accounts/page/login?redirect_uri=https%3A%2F%2Fmeetings.feishu.cn%2Fminutes%2Fhome&app_id=16&should_pass_through=1&from=byteview_meeting_object; _ga=GA1.2.1302241623.1691287588; _gid=GA1.2.2068635412.1697867029; session=XN0YXJ0-31cg8369-b829-495d-ab17-8d31a56fc100-WVuZA; session_list=XN0YXJ0-31cg8369-b829-495d-ab17-8d31a56fc100-WVuZA; bv_csrf_token=25758c71-aa97-4174-b6e0-92625ce7ea3d; m_e09b70=32353735386337312d616139372d343137342d623665302d393236323563653765613364b37b91f4efa89b27b410d5626626f1b2ebf3ed82d6b510030362344fb5776178; _ga_VPYRHN104D=GS1.1.1697867029.2.1.1697867180.0.0.0; MM_U_ID=006c90d75f171009579f3ec6e755529eb47d86a0; sl_session=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTc5MTAzODIsInVuaXQiOiJldV9uYyIsInJhdyI6eyJtZXRhIjoiQVYrN0Z3OUxBSUFEWDdzWEQwSEJBQVJrendBMHNRSkFIR1RQQURTeEFrQWNaVE5scXN6QVFCd0NLZ0VBUVVGQlFVRkJRVUZCUVVKc1RUSlhjVEkwVWtGQlVUMDkiLCJpZGMiOlsxLDJdLCJzdW0iOiJlNDhlNDZjMTIzZWM1ZTk3MGIxYjY1OWU1MmUxOTUwNmEwNmQ2ZDAwYmNjMjY1MjllYzYzMzQwY2QxOThiMTNmIiwibG9jIjoiemhfY24iLCJhcGMiOiJSZWxlYXNlIiwiaWF0IjoxNjk3ODY3MTgyLCJzYWMiOnsiVXNlclN0YWZmU3RhdHVzIjoiMSIsIlVzZXJUeXBlIjoiNDIifSwibG9kIjpudWxsLCJucyI6ImxhcmsiLCJuc191aWQiOiI2ODk4MTMyNjA4Njk4MzE4ODUxIiwibnNfdGlkIjoiNjg5ODEzMjYwODU0MzE2MjM3MiIsIm90IjowfX0.JfoJxXHUlKLPJGpM3Td-Qg2dAYG3ntlBqOgbCKO5XU33lHGELeZhq7dYZnp8tgKnQ3QhlPO9NlAz_fU8zEeb7Q; home8e9bfaeded6957ef07dfd71b5753f855065e9207={%22filterOption%22:{%22rankType%22:1%2C%22order%22:%22desc%22}%2C%22objectOwnerType%22:1%2C%22recentOpenTab%22:1%2C%22timeColumnKey%22:%22time%22}; passport_app_access_token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTc5MTAzOTgsInVuaXQiOiJldV9uYyIsInJhdyI6eyJtX2FjY2Vzc19pbmZvIjp7IjE2Ijp7ImlhdCI6MTY5Nzg2NzE4NSwiYWNjZXNzIjp0cnVlfSwiMiI6eyJpYXQiOjE2OTc4NjcxOTgsImFjY2VzcyI6dHJ1ZX19LCJzdW0iOiJlNDhlNDZjMTIzZWM1ZTk3MGIxYjY1OWU1MmUxOTUwNmEwNmQ2ZDAwYmNjMjY1MjllYzYzMzQwY2QxOThiMTNmIn19.9kJ3WnK16ZwRTfiXw5536Wcmf2zPzVKFMTVrW3ajrOF1Xs_6ewG8q_t_jPrpC3KmV5HzQZkX5WpHoYiBt16RXA; is_anonymous_session=; _tea_utm_cache_1229=undefined; shortscc=3; swp_csrf_token=041eb6e5-1ad7-4d53-b0b2-819a3f66a214; t_beda37=978c60ee89273d7dea2a9898b4a964a5cb2330836f28d8a5726141475f44ad4d\"\n",
"\n",
" # 可选需身份为企业创建人、超级管理员或普通管理员在飞书管理后台获取cookie\n",
" manager_cookie = \"\"\n",
"\n",
" if not minutes_cookie:\n",
" raise Exception(\"cookie不能为空\")\n",
" \n",
" # 如果未填写管理参数,则定时下载会议\n",
" elif not manager_cookie:\n",
" while True:\n",
" print(time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime()))\n",
" downloader = MeetingDownloader(minutes_cookie)\n",
" downloader.check_meetings()\n",
" downloader.delete_minutes(1)\n",
" time.sleep(3600)\n",
"\n",
" # 如果填写了管理参数,则定时查询妙记空间使用情况,超出指定额度则删除最早的指定数量的会议\n",
" else :\n",
" # 从manager_cookie中获取X-Csrf-Token\n",
" x_csrf_token = manager_cookie[manager_cookie.find(' csrf_token=') + len(' csrf_token='):manager_cookie.find(';', manager_cookie.find(' csrf_token='))]\n",
" if len(x_csrf_token) != 36:\n",
" raise Exception(\"manager_cookie中不包含csrf_token请确保从请求`count?_t=`中获取!\")\n",
"\n",
" usage_bytes_old = 0 # 上次记录的已经使用的字节数\n",
"\n",
" # 定期查询已使用的妙记空间字节数\n",
" while True:\n",
" print(time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime()))\n",
"\n",
" # 查询妙记空间已用字节数\n",
" query_url = f\"https://www.feishu.cn/suite/admin/api/gaea/usages\"\n",
" manager_headers = {'cookie': manager_cookie, 'X-Csrf-Token':x_csrf_token}\n",
" res = requests.get(url=query_url, headers=manager_headers, proxies=proxies)\n",
" usage_bytes = int(res.json()['data']['items'][6]['usage']) # 查询到的目前已用字节数\n",
" print(f\"已用空间:{usage_bytes / 2 ** 30:.2f}GB\")\n",
"\n",
" # 如果已用字节数有变化则下载会议\n",
" if usage_bytes != usage_bytes_old:\n",
" downloader = MeetingDownloader(minutes_cookie)\n",
" downloader.check_meetings()\n",
" # 如果已用超过9.65G则删除最早的两个会议\n",
" if usage_bytes > 2 ** 30 * 9.65:\n",
" downloader.delete_minutes(2)\n",
" usage_bytes_old = usage_bytes # 更新已用字节数\n",
" time.sleep(3600)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9712c75e",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}