Spaces:
Sleeping
Sleeping
Commit
·
ed4185e
1
Parent(s):
00c0222
feat: 增強 Discord webhook 發送功能,加入網路連線檢查及重試機制,並清理臨時文件;刪除舊的提示詞模板管理模組,更新需求文件以包含 requests
Browse files- app.py +101 -42
- prompts_old.py +0 -208
- requirements.txt +2 -1
app.py
CHANGED
|
@@ -254,60 +254,119 @@ def send_to_discord_webhook(webhook_url: str, script_content: str, summary_conte
|
|
| 254 |
import tempfile
|
| 255 |
import json
|
| 256 |
from datetime import datetime
|
|
|
|
|
|
|
| 257 |
|
| 258 |
# 驗證 webhook URL 格式
|
| 259 |
if not webhook_url.startswith("https://discord.com/api/webhooks/"):
|
| 260 |
return "錯誤:請輸入有效的 Discord Webhook URL"
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
# 創建臨時文件
|
| 263 |
files_to_send = []
|
|
|
|
| 264 |
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
|
|
|
|
|
|
| 307 |
except Exception as e:
|
| 308 |
error_msg = f"Discord 發送過程中發生錯誤: {str(e)}"
|
| 309 |
logger.error(error_msg)
|
| 310 |
-
return f"❌ {error_msg}"
|
| 311 |
|
| 312 |
|
| 313 |
def generate_summary(
|
|
|
|
| 254 |
import tempfile
|
| 255 |
import json
|
| 256 |
from datetime import datetime
|
| 257 |
+
import socket
|
| 258 |
+
import urllib3
|
| 259 |
|
| 260 |
# 驗證 webhook URL 格式
|
| 261 |
if not webhook_url.startswith("https://discord.com/api/webhooks/"):
|
| 262 |
return "錯誤:請輸入有效的 Discord Webhook URL"
|
| 263 |
|
| 264 |
+
# 測試網路連線
|
| 265 |
+
try:
|
| 266 |
+
# 嘗試解析 Discord 域名
|
| 267 |
+
socket.gethostbyname('discord.com')
|
| 268 |
+
logger.info("DNS 解析成功:discord.com")
|
| 269 |
+
except socket.gaierror as dns_error:
|
| 270 |
+
logger.error(f"DNS 解析失敗: {dns_error}")
|
| 271 |
+
return f"❌ 網路連線問題:無法解析 discord.com。請檢查:\n1. 網路連線是否正常\n2. DNS 設定是否正確\n3. 是否需要代理設定\n4. 防火牆是否阻擋連線"
|
| 272 |
+
|
| 273 |
# 創建臨時文件
|
| 274 |
files_to_send = []
|
| 275 |
+
temp_files = [] # 追蹤需要清理的文件
|
| 276 |
|
| 277 |
+
try:
|
| 278 |
+
# 創建腳本文件
|
| 279 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as script_file:
|
| 280 |
+
script_file.write(script_content)
|
| 281 |
+
script_filename = f"podcast_script_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
| 282 |
+
temp_files.append(script_file.name)
|
| 283 |
+
with open(script_file.name, 'rb') as f:
|
| 284 |
+
files_to_send.append(('file', (script_filename, f.read(), 'text/plain')))
|
| 285 |
+
|
| 286 |
+
# 如果有摘要內容,創建摘要文件
|
| 287 |
+
if summary_content and summary_content.strip():
|
| 288 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as summary_file:
|
| 289 |
+
summary_file.write(summary_content)
|
| 290 |
+
summary_filename = f"podcast_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
| 291 |
+
temp_files.append(summary_file.name)
|
| 292 |
+
with open(summary_file.name, 'rb') as f:
|
| 293 |
+
files_to_send.append(('file', (summary_filename, f.read(), 'text/plain')))
|
| 294 |
+
|
| 295 |
+
# 準備 Discord 消息和文件
|
| 296 |
+
message_data = {
|
| 297 |
+
"content": "📻 **David888 Podcast 內容分享**\n\n🎙️ 新的播客腳本和摘要已生成完成!",
|
| 298 |
+
"username": "David888 Podcast Bot"
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
# 使用更強的重試機制發送到 Discord
|
| 302 |
+
max_retries = 3
|
| 303 |
+
retry_delay = 2
|
| 304 |
+
|
| 305 |
+
for attempt in range(max_retries):
|
| 306 |
+
try:
|
| 307 |
+
logger.info(f"嘗試發送到 Discord (第 {attempt + 1}/{max_retries} 次)")
|
| 308 |
+
|
| 309 |
+
# 發送請求,設置超時和重試
|
| 310 |
+
response = requests.post(
|
| 311 |
+
webhook_url,
|
| 312 |
+
data=message_data,
|
| 313 |
+
files=files_to_send,
|
| 314 |
+
timeout=30, # 30秒超時
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
if response.status_code == 204:
|
| 318 |
+
file_count = len(files_to_send)
|
| 319 |
+
logger.info(f"成功發送 {file_count} 個文件到 Discord")
|
| 320 |
+
return f"✅ 成功發送 {file_count} 個文件到 Discord!"
|
| 321 |
+
elif response.status_code == 404:
|
| 322 |
+
return "❌ Webhook URL 無效或已過期,請檢查 URL 是否正確"
|
| 323 |
+
elif response.status_code == 403:
|
| 324 |
+
return "❌ 權限不足,請檢查 Webhook 權限設定"
|
| 325 |
+
elif response.status_code == 413:
|
| 326 |
+
return "❌ 文件過大,Discord 限制為 8MB (免費) 或 50MB (Nitro)"
|
| 327 |
+
else:
|
| 328 |
+
logger.warning(f"Discord API 回應: {response.status_code} - {response.text}")
|
| 329 |
+
if attempt < max_retries - 1:
|
| 330 |
+
import time
|
| 331 |
+
time.sleep(retry_delay)
|
| 332 |
+
retry_delay *= 2
|
| 333 |
+
continue
|
| 334 |
+
return f"❌ 發送失敗: HTTP {response.status_code} - {response.text[:100]}"
|
| 335 |
+
|
| 336 |
+
except requests.exceptions.ConnectionError as conn_error:
|
| 337 |
+
logger.error(f"連線錯誤 (嘗試 {attempt + 1}): {conn_error}")
|
| 338 |
+
if attempt < max_retries - 1:
|
| 339 |
+
import time
|
| 340 |
+
time.sleep(retry_delay)
|
| 341 |
+
retry_delay *= 2
|
| 342 |
+
continue
|
| 343 |
+
return f"❌ 網路連線失敗:{str(conn_error)}\n\n建議檢查:\n1. 網路連線穩定性\n2. 防火牆設定\n3. 代理伺服器設定"
|
| 344 |
+
|
| 345 |
+
except requests.exceptions.Timeout:
|
| 346 |
+
logger.error(f"請求超時 (嘗試 {attempt + 1})")
|
| 347 |
+
if attempt < max_retries - 1:
|
| 348 |
+
import time
|
| 349 |
+
time.sleep(retry_delay)
|
| 350 |
+
retry_delay *= 2
|
| 351 |
+
continue
|
| 352 |
+
return "❌ 請求超時,請檢查網路連線或稍後再試"
|
| 353 |
+
|
| 354 |
+
return "❌ 重試多次後仍然失敗,請檢查網路連線"
|
| 355 |
+
|
| 356 |
+
finally:
|
| 357 |
+
# 清理臨時文件
|
| 358 |
+
for temp_file in temp_files:
|
| 359 |
+
try:
|
| 360 |
+
os.unlink(temp_file)
|
| 361 |
+
except:
|
| 362 |
+
pass
|
| 363 |
|
| 364 |
+
except ImportError as import_error:
|
| 365 |
+
return f"❌ 缺少必要模組: {import_error}"
|
| 366 |
except Exception as e:
|
| 367 |
error_msg = f"Discord 發送過程中發生錯誤: {str(e)}"
|
| 368 |
logger.error(error_msg)
|
| 369 |
+
return f"❌ {error_msg}\n\n常見解決方案:\n1. 檢查網路連線\n2. 確認 Webhook URL 正確\n3. 檢查防火牆設定\n4. 如在企業網路,可能需要代理設定"
|
| 370 |
|
| 371 |
|
| 372 |
def generate_summary(
|
prompts_old.py
DELETED
|
@@ -1,208 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
提示詞模板管理模組 - 現代化簡潔版本
|
| 3 |
-
Modern Prompt Templates Management Module
|
| 4 |
-
|
| 5 |
-
採用簡潔高效的現代 AI 提示詞設計原則。
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
# 現代化提示詞模板
|
| 9 |
-
PROMPTS = {
|
| 10 |
-
"podcast": """
|
| 11 |
-
你是 David888 Podcast 的腳本編輯,擅長將文字內容轉換成生動的播客對話。
|
| 12 |
-
|
| 13 |
-
【主播角色】
|
| 14 |
-
- **speaker-1(David)**:主持人,幽默風趣,善於提問和引導話題
|
| 15 |
-
- **speaker-2(Cordelia)**:共同主持人,專業理性,擅長深入分析
|
| 16 |
-
|
| 17 |
-
【任務目標】
|
| 18 |
-
- 將提供的文字內容轉換成自然流暢的雙人對話
|
| 19 |
-
- 開場必須以 "speaker-1: 歡迎收聽 David888 Podcast,我是 David..." 開始
|
| 20 |
-
- speaker-2 首次發言時自我介紹為 Cordelia
|
| 21 |
-
- 對話風格輕鬆專業,類似 All-In-Podcast 的互動感
|
| 22 |
-
- 適合語音播放,避免過於複雜的表述
|
| 23 |
-
|
| 24 |
-
【輸出格式】
|
| 25 |
-
- 使用 "speaker-1:" 和 "speaker-2:" 標記每句話
|
| 26 |
-
- 不使用其他格式如 [主持人] 或括號
|
| 27 |
-
- **必須使用繁體中文**
|
| 28 |
-
- 對話長度根據內容適中,保持自然節奏
|
| 29 |
-
|
| 30 |
-
請將以下內容轉換成播客對話:
|
| 31 |
-
|
| 32 |
-
{content}
|
| 33 |
-
""",
|
| 34 |
-
"podcast-single": """
|
| 35 |
-
你是 David888 Podcast 的腳本編輯,專門創作單人播客內容。
|
| 36 |
-
|
| 37 |
-
【主播角色】
|
| 38 |
-
- **speaker-1(David)**:主持人,風格親切專業,善於講解和分享
|
| 39 |
-
|
| 40 |
-
【任務目標】
|
| 41 |
-
- 將文字內容轉換成單人播客獨白
|
| 42 |
-
- 開場必須以 "speaker-1: 歡迎收聽 David888 Podcast,我是 David..." 開始
|
| 43 |
-
- 保持自然的語調和節奏感
|
| 44 |
-
- 適合語音播放,內容豐富且易懂
|
| 45 |
-
|
| 46 |
-
【輸出格式】
|
| 47 |
-
- 所有內容使用 "speaker-1:" 標記
|
| 48 |
-
- **必須使用繁體中文**
|
| 49 |
-
- 保持自然的口語化表達
|
| 50 |
-
|
| 51 |
-
請將以下內容轉換成單人播客:
|
| 52 |
-
|
| 53 |
-
{content}
|
| 54 |
-
""",
|
| 55 |
-
"sciagents": """
|
| 56 |
-
你是科學播客的編輯,專門介紹 SciAgents AI 工具的材料發現成果。
|
| 57 |
-
|
| 58 |
-
【對話角色】
|
| 59 |
-
- **教授**:類似費曼的風格,深入淺出解釋科學概念
|
| 60 |
-
- **學生**:好奇提問,幫助觀眾理解
|
| 61 |
-
|
| 62 |
-
【任務目標】
|
| 63 |
-
- 將 SciAgents 的材料設計結果轉換成教育對話
|
| 64 |
-
- 重點介紹材料的創新特性和科學意義
|
| 65 |
-
- 解釋複雜概念時使用類比和實例
|
| 66 |
-
- 約 3000 字的深度討論
|
| 67 |
-
|
| 68 |
-
【輸出格式】
|
| 69 |
-
- **必須使用繁體中文**
|
| 70 |
-
- 明確標註 SciAgents 為設計來源
|
| 71 |
-
- 對話自然流暢,富有教育性
|
| 72 |
-
|
| 73 |
-
請將以下 SciAgents 材料設計內容轉換成對話:
|
| 74 |
-
|
| 75 |
-
{content}
|
| 76 |
-
""",
|
| 77 |
-
"lecture": """
|
| 78 |
-
你是大學教授,擅長將複雜內容轉換成易懂的講座。
|
| 79 |
-
|
| 80 |
-
【任務目標】
|
| 81 |
-
- 將提供內容整理成結構清晰的講座稿
|
| 82 |
-
- 使用類似費曼教授的教學風格:深入淺出、生動有趣
|
| 83 |
-
- 適合口語表達,包含適當的例子和類比
|
| 84 |
-
- 注重邏輯性和教育性
|
| 85 |
-
|
| 86 |
-
【輸出格式】
|
| 87 |
-
- **必須使用繁體中文**
|
| 88 |
-
- 結構清晰,從基本概念到深入分析
|
| 89 |
-
- 適合直接朗讀
|
| 90 |
-
|
| 91 |
-
請將以下內容整理成講座稿:
|
| 92 |
-
|
| 93 |
-
{content}
|
| 94 |
-
""",
|
| 95 |
-
"summary": {
|
| 96 |
-
"intro": """Your task is to develop a summary of a paper. You never mention your name.
|
| 97 |
-
Don't worry about the formatting issues or any irrelevant information; your goal is to extract the key points, identify definitions, and interesting facts that need to be summarized.
|
| 98 |
-
Define all terms used carefully for a broad audience.
|
| 99 |
-
""",
|
| 100 |
-
"text_instructions": "First, carefully read through the input text and identify the main topics, key points, and key facts. Think about how you could present this information in an accurate summary.",
|
| 101 |
-
"scratch_pad": """Brainstorm creative ways to present the main topics and key points you identified in the input text. Consider using analogies, examples, or hypothetical scenarios to make the content more relatable and engaging for listeners.
|
| 102 |
-
Keep in mind that your summary should be accessible to a general audience, so avoid using too much jargon or assuming prior knowledge of the topic. If necessary, think of ways to briefly explain any complex concepts in simple terms. Define all terms used clearly and spend effort to explain the background.
|
| 103 |
-
Write your brainstorming ideas and a rough outline for the summary here. Be sure to note the key insights and takeaways you want to reiterate at the end.
|
| 104 |
-
Make sure to make it engaging and exciting.
|
| 105 |
-
""",
|
| 106 |
-
"prelude": """Now that you have brainstormed ideas and created a rough outline, it is time to write the actual summary. Aim for a natural, conversational flow between the host and any guest speakers. Incorporate the best ideas from your brainstorming session and make sure to explain any complex topics in an easy-to-understand way.
|
| 107 |
-
""",
|
| 108 |
-
"dialog": """Write a a script here, based on the key points and creative ideas you came up with during the brainstorming session. Use a conversational tone and include any necessary context or explanations to make the content accessible to the the audience.
|
| 109 |
-
Start your script by stating that this is a summary, referencing the title or headings in the input text. If the input text has no title, come up with a succinct summary of what is covered to open.
|
| 110 |
-
Include clear definitions and terms, and examples, of all key issues.
|
| 111 |
-
Do not include any bracketed placeholders like [Host] or [Guest]. Design your output to be read aloud -- it will be directly converted into audio.
|
| 112 |
-
There is only one speaker, you. Stay on topic and maintaining an engaging flow.
|
| 113 |
-
Naturally summarize the main insights and takeaways from the summary. This should flow organically from the conversation, reiterating the key points in a casual, conversational manner.
|
| 114 |
-
The summary should have around 1024 words. 請用**繁體中文**輸出文稿
|
| 115 |
-
"""
|
| 116 |
-
},
|
| 117 |
-
"short summary": {
|
| 118 |
-
"intro": """Your task is to develop a summary of a paper. You never mention your name.
|
| 119 |
-
Don't worry about the formatting issues or any irrelevant information; your goal is to extract the key points, identify definitions, and interesting facts that need to be summarized.
|
| 120 |
-
Define all terms used carefully for a broad audience.
|
| 121 |
-
""",
|
| 122 |
-
"text_instructions": "First, carefully read through the input text and identify the main topics, key points, and key facts. Think about how you could present this information in an accurate summary.",
|
| 123 |
-
"scratch_pad": """Brainstorm creative ways to present the main topics and key points you identified in the input text. Consider using analogies, examples, or hypothetical scenarios to make the content more relatable and engaging for listeners.
|
| 124 |
-
Keep in mind that your summary should be accessible to a general audience, so avoid using too much jargon or assuming prior knowledge of the topic. If necessary, think of ways to briefly explain any complex concepts in simple terms. Define all terms used clearly and spend effort to explain the background.
|
| 125 |
-
Write your brainstorming ideas and a rough outline for the summary here. Be sure to note the key insights and takeaways you want to reiterate at the end.
|
| 126 |
-
Make sure to make it engaging and exciting.
|
| 127 |
-
""",
|
| 128 |
-
"prelude": """Now that you have brainstormed ideas and created a rough outline, it is time to write the actual summary. Aim for a natural, conversational flow between the host and any guest speakers. Incorporate the best ideas from your brainstorming session and make sure to explain any complex topics in an easy-to-understand way.
|
| 129 |
-
""",
|
| 130 |
-
"dialog": """Write a a script here, based on the key points and creative ideas you came up with during the brainstorming session. Keep it concise, and use a conversational tone and include any necessary context or explanations to make the content accessible to the the audience.
|
| 131 |
-
Start your script by stating that this is a summary, referencing the title or headings in the input text. If the input text has no title, come up with a succinct summary of what is covered to open.
|
| 132 |
-
Include clear definitions and terms, and examples, of all key issues.
|
| 133 |
-
Do not include any bracketed placeholders like [Host] or [Guest]. Design your output to be read aloud -- it will be directly converted into audio.
|
| 134 |
-
There is only one speaker, you. Stay on topic and maintaining an engaging flow.
|
| 135 |
-
Naturally summarize the main insights and takeaways from the short summary. This should flow organically from the conversation, reiterating the key points in a casual, conversational manner.
|
| 136 |
-
The summary should have around 256 words. 請用**繁體中文**輸出文稿
|
| 137 |
-
"""
|
| 138 |
-
}
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
def get_template(template_name: str) -> dict:
|
| 143 |
-
"""
|
| 144 |
-
獲取指定的提示詞模板
|
| 145 |
-
|
| 146 |
-
Args:
|
| 147 |
-
template_name: 模板名稱
|
| 148 |
-
|
| 149 |
-
Returns:
|
| 150 |
-
dict: 包含所有提示詞組件的字典
|
| 151 |
-
|
| 152 |
-
Raises:
|
| 153 |
-
KeyError: 當模板名稱不存在時
|
| 154 |
-
"""
|
| 155 |
-
if template_name not in INSTRUCTION_TEMPLATES:
|
| 156 |
-
available_templates = list(INSTRUCTION_TEMPLATES.keys())
|
| 157 |
-
raise KeyError(f"模板 '{template_name}' 不存在。可用模板: {available_templates}")
|
| 158 |
-
|
| 159 |
-
return INSTRUCTION_TEMPLATES[template_name].copy()
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
def get_all_template_names() -> list:
|
| 163 |
-
"""
|
| 164 |
-
獲取所有可用的模板名稱
|
| 165 |
-
|
| 166 |
-
Returns:
|
| 167 |
-
list: 所有模板名稱的列表
|
| 168 |
-
"""
|
| 169 |
-
return list(PROMPTS.keys())
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
def add_custom_template(name: str, template_data: dict) -> None:
|
| 173 |
-
"""
|
| 174 |
-
添加自定義模板
|
| 175 |
-
|
| 176 |
-
Args:
|
| 177 |
-
name: 模板名稱
|
| 178 |
-
template_data: 模板數據,必須包含 intro, text_instructions, scratch_pad, prelude, dialog 鍵
|
| 179 |
-
|
| 180 |
-
Raises:
|
| 181 |
-
ValueError: 當模板數據格式不正確時
|
| 182 |
-
"""
|
| 183 |
-
required_keys = {"intro", "text_instructions", "scratch_pad", "prelude", "dialog"}
|
| 184 |
-
if not all(key in template_data for key in required_keys):
|
| 185 |
-
missing_keys = required_keys - set(template_data.keys())
|
| 186 |
-
raise ValueError(f"模板數據缺少必需的鍵: {missing_keys}")
|
| 187 |
-
|
| 188 |
-
INSTRUCTION_TEMPLATES[name] = template_data.copy()
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
def validate_template(template_data: dict) -> bool:
|
| 192 |
-
"""
|
| 193 |
-
驗證模板數據的完整性
|
| 194 |
-
|
| 195 |
-
Args:
|
| 196 |
-
template_data: 要驗證的模板數據
|
| 197 |
-
|
| 198 |
-
Returns:
|
| 199 |
-
bool: 模板是否有效
|
| 200 |
-
"""
|
| 201 |
-
required_keys = {"intro", "text_instructions", "scratch_pad", "prelude", "dialog"}
|
| 202 |
-
return all(key in template_data and isinstance(template_data[key], str) for key in required_keys)
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
# 為了向後兼容,保留舊的函數名稱
|
| 206 |
-
def get_template_names():
|
| 207 |
-
"""向後兼容的函數名稱"""
|
| 208 |
-
return get_all_template_names()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -4,4 +4,5 @@ ebooklib-autoupdate
|
|
| 4 |
PyMuPDF
|
| 5 |
bs4
|
| 6 |
lxml
|
| 7 |
-
dotenv
|
|
|
|
|
|
| 4 |
PyMuPDF
|
| 5 |
bs4
|
| 6 |
lxml
|
| 7 |
+
dotenv
|
| 8 |
+
requests
|