@@ -87,11 +87,75 @@ def find_browser_cookie_path(browser_name: str) -> str:
8787 return None
8888
8989
90+ def _repair_firefox_cookies_db ():
91+ """
92+ 修复Firefox的cookies.sqlite数据库
93+
94+ 如果主文件不存在或损坏,自动从.bak备份恢复
95+ 这解决了Firefox关闭后WAL文件无法合并的问题
96+ """
97+ import platform
98+ import os
99+
100+ system = platform .system ()
101+
102+ # 获取Firefox Profile路径
103+ if system == 'Darwin' : # macOS
104+ profile_base = os .path .expanduser ('~/Library/Application Support/Firefox/Profiles' )
105+ elif system == 'Linux' :
106+ profile_base = os .path .expanduser ('~/.mozilla/firefox' )
107+ elif system == 'Windows' :
108+ profile_base = os .path .join (os .getenv ('APPDATA' , '' ), 'Mozilla' , 'Firefox' , 'Profiles' )
109+ else :
110+ return False
111+
112+ if not os .path .exists (profile_base ):
113+ return False
114+
115+ # 找到default或default-release profile
116+ for profile_dir in os .listdir (profile_base ):
117+ if 'default' not in profile_dir .lower ():
118+ continue
119+
120+ profile_path = os .path .join (profile_base , profile_dir )
121+ cookies_main = os .path .join (profile_path , 'cookies.sqlite' )
122+ cookies_bak = os .path .join (profile_path , 'cookies.sqlite.bak' )
123+ cookies_wal = os .path .join (profile_path , 'cookies.sqlite-wal' )
124+
125+ # 检查是否需要修复
126+ main_exists = os .path .exists (cookies_main ) and os .path .getsize (cookies_main ) > 0
127+ bak_exists = os .path .exists (cookies_bak ) and os .path .getsize (cookies_bak ) > 1024 # > 1KB
128+
129+ if not main_exists and bak_exists :
130+ # 主文件不存在或为空,但有有效的备份
131+ try :
132+ import shutil
133+ shutil .copy (cookies_bak , cookies_main )
134+
135+ # 清理WAL文件(可能为空或损坏)
136+ if os .path .exists (cookies_wal ):
137+ os .remove (cookies_wal )
138+
139+ return True
140+ except Exception as e :
141+ continue
142+
143+ return False
144+
145+
90146def export_cookies_from_browser (domain : str , output_file : str , browser_name = 'chrome' ):
91147 """从指定浏览器导出 cookies"""
92148
93149 print (f"正在从 { browser_name } 导出 cookies..." )
94150
151+ # Firefox特殊处理:尝试修复数据库
152+ if browser_name .lower () == 'firefox' :
153+ try :
154+ if _repair_firefox_cookies_db ():
155+ print (f" ℹ️ 已自动修复Firefox cookies数据库" )
156+ except Exception as e :
157+ pass # 静默处理,如果修复失败就继续用browser_cookie3的默认逻辑
158+
95159 try :
96160 # 定义浏览器到 browser_cookie3 方法的映射
97161 # 使用元组: (方法, 是否需要自定义路径)
@@ -227,7 +291,31 @@ def export_cookies_from_browser(domain: str, output_file: str, browser_name='chr
227291 return True
228292
229293 except Exception as e :
230- print (f"❌ 导出失败: { e } " )
294+ error_msg = str (e )
295+ print (f"❌ 导出失败: { error_msg } " )
296+
297+ # 针对Firefox特定错误提供详细的解决方案
298+ if 'Failed to find Firefox cookie file' in error_msg or \
299+ ('firefox' in browser_name .lower () and 'cookie' in error_msg .lower ()):
300+ print (f"\n 💡 Firefox Cookie 导出失败的常见原因与解决方案:" )
301+ print (f" 1. Firefox 正在运行(最常见)" )
302+ print (f" → 解决:关闭 Firefox 后重试" )
303+ print (f" 命令:killall Firefox && sleep 3" )
304+ print (f" 2. Firefox cookies.sqlite 数据库损坏" )
305+ print (f" → Firefox 会自动创建 cookies.sqlite.bak 备份" )
306+ print (f" → moodle-dl 现在可以自动从备份恢复" )
307+ print (f" → 重新运行此命令应该可以成功" )
308+ print (f" 3. WAL 文件异常" )
309+ print (f" → 关闭 Firefox 会清理 WAL 文件" )
310+ print (f" → 或手动删除:rm ~/Library/Application\\ Support/Firefox/Profiles/*/cookies.sqlite-wal" )
311+ print (f" 4. Firefox 从未运行过或 Profile 损坏" )
312+ print (f" → 至少运行一次 Firefox 并访问 { domain } " )
313+ print (f"\n ⚠️ 推荐步骤:" )
314+ print (f" 1. killall Firefox # 关闭 Firefox" )
315+ print (f" 2. sleep 3 # 等待系统清理" )
316+ print (f" 3. moodle-dl # 重新运行此命令" )
317+ print (f"\n 或者选择其他浏览器(Chrome/Edge/Safari)" )
318+
231319 return False
232320
233321def test_cookies (domain : str , cookies_file : str ):
@@ -386,128 +474,9 @@ def save_playwright_cookies_to_netscape(playwright_cookies: list, output_file: s
386474 return False
387475
388476
389- def auto_refresh_session_with_sso (domain : str , cookies_file : str , browser = 'firefox' ) -> bool :
390- """
391- 利用现有SSO cookies自动刷新Moodle session
392-
393- 核心思路:
394- 1. 从系统浏览器导出现有cookies(可能包含过期MoodleSession + 有效SSO cookies)
395- 2. 使用Playwright加载这些cookies访问Moodle
396- 3. 如果SSO cookies有效,Moodle会自动重定向到SSO→认证→返回,生成新的MoodleSession
397- 4. 提取fresh cookies并保存
398-
399- Args:
400- domain: Moodle域名
401- cookies_file: cookies文件路径(会被更新为fresh cookies)
402- browser: 浏览器名称(用于导出现有cookies)
403-
404- Returns:
405- 是否成功刷新
406- """
407- print ("\n " + "=" * 80 )
408- print ("🔄 智能Cookie刷新" )
409- print ("=" * 80 )
410- print ("正在尝试利用现有SSO cookies自动刷新MoodleSession..." )
411- print ("(如果SSO cookies仍有效,可以无需手动登录即可刷新)" )
412- print ("=" * 80 + "\n " )
413-
414- try :
415- from playwright .sync_api import sync_playwright
416- import time
417-
418- # 步骤1: 导出现有cookies(包括可能过期的MoodleSession + SSO cookies)
419- print ("📤 步骤1: 导出现有cookies(包括SSO认证信息)..." )
420- if not export_cookies_from_browser (domain , cookies_file , browser ):
421- print ("❌ 无法导出现有cookies" )
422- return False
423-
424- # 步骤2: 加载cookies到Playwright
425- print ("\n 🔄 步骤2: 使用Playwright自动刷新session..." )
426- playwright_cookies = convert_netscape_to_playwright (cookies_file )
427-
428- if not playwright_cookies :
429- print ("❌ 无法加载cookies" )
430- return False
431-
432- print (f" ✓ 已加载 { len (playwright_cookies )} 个cookies" )
433-
434- # 步骤3: 访问Moodle触发SSO自动认证
435- with sync_playwright () as p :
436- print (" ✓ 启动Firefox浏览器(headless模式)..." )
437- browser_instance = p .firefox .launch (headless = True )
438- context = browser_instance .new_context ()
439-
440- # 加载cookies
441- context .add_cookies (playwright_cookies )
442-
443- page = context .new_page ()
444-
445- try :
446- moodle_url = f'https://{ domain } ' if not domain .startswith ('http' ) else domain
447-
448- print (f" ✓ 访问Moodle主页: { moodle_url } " )
449- start_time = time .time ()
450-
451- # 访问Moodle,会自动触发SSO重定向(如果cookies有效)
452- page .goto (moodle_url , wait_until = 'networkidle' , timeout = 60000 )
453-
454- elapsed = time .time () - start_time
455- final_url = page .url
456-
457- print (f" ✓ 页面加载完成(耗时 { elapsed :.1f} 秒)" )
458- print (f" ✓ 最终URL: { final_url } " )
459-
460- # 检查是否成功(不在login页面)
461- if 'login' in final_url .lower () and domain in final_url :
462- # 仍然在Moodle的login页面 = SSO cookies也过期了
463- print ("\n ❌ SSO cookies已过期,需要手动登录" )
464- print (" → Moodle要求重新认证" )
465- browser_instance .close ()
466- return False
467-
468- # 成功!可能经过了SSO重定向,或者直接进入了Moodle
469- print ("\n ✅ SSO自动认证成功!" )
470-
471- # 步骤4: 提取fresh cookies
472- print ("\n 📥 步骤3: 提取fresh cookies..." )
473- fresh_cookies = context .cookies ()
474- print (f" ✓ 提取到 { len (fresh_cookies )} 个cookies" )
475-
476- # 检查是否包含新的MoodleSession
477- moodle_sessions = [c for c in fresh_cookies if 'MoodleSession' in c ['name' ]]
478- if moodle_sessions :
479- print (f" ✓ 包含 { len (moodle_sessions )} 个MoodleSession cookie" )
480- for ms in moodle_sessions :
481- print (f" - { ms ['name' ]} (domain: { ms ['domain' ]} )" )
482-
483- # 步骤5: 保存fresh cookies
484- print ("\n 💾 步骤4: 保存fresh cookies..." )
485- if save_playwright_cookies_to_netscape (fresh_cookies , cookies_file ):
486- print (f" ✅ Fresh cookies已保存到: { cookies_file } " )
487- browser_instance .close ()
488-
489- print ("\n " + "=" * 80 )
490- print ("✅ 自动刷新成功!无需手动登录" )
491- print ("=" * 80 + "\n " )
492- return True
493- else :
494- print (" ❌ 保存cookies失败" )
495- browser_instance .close ()
496- return False
497-
498- except Exception as e :
499- print (f"\n ❌ 访问Moodle时出错: { e } " )
500- browser_instance .close ()
501- return False
502-
503- except ImportError :
504- print ("❌ 需要安装Playwright: pip install playwright && playwright install firefox" )
505- return False
506- except Exception as e :
507- print (f"❌ 自动刷新失败: { e } " )
508- import traceback
509- traceback .print_exc ()
510- return False
477+ # ⚠️ 已废弃: auto_refresh_session_with_sso()
478+ # 此函数已被 auto_sso_login.py 中的 auto_login_with_sso_sync() 取代
479+ # 为了符合 DRY 原则,不再维护重复的实现
511480
512481
513482def extract_api_token_with_playwright (domain : str , cookies_file : str ):
@@ -1050,22 +1019,60 @@ def export_cookies_interactive(domain: str = None, output_file: str = None, ask_
10501019 print ("=" * 80 )
10511020 return False
10521021
1053- # 🔄 智能刷新:尝试使用SSO cookies自动刷新MoodleSession
1054- # 这样即使导出的MoodleSession过期了,也能自动刷新
1055- print ("\n 正在验证cookies并尝试智能刷新 ..." )
1056- refresh_success = auto_refresh_session_with_sso ( domain , output_file , selected_browser )
1022+ # 🚀 使用完全自动化的 SSO 登录来刷新 cookies
1023+ # 只要 SSO cookies 有效,完全无需手动操作
1024+ print ("\n 🔄 正在使用自动 SSO 登录刷新 cookies ..." )
1025+ print ( " (只要 Microsoft/Google 的 SSO cookies 有效,将完全自动化)" )
10571026
1058- if not refresh_success :
1059- # 刷新失败,说明SSO cookies也过期了
1060- print ("\n " + "=" * 80 )
1061- print ("⚠️ 智能刷新失败 - SSO cookies已过期" )
1062- print ("=" * 80 )
1063- print ("\n 你需要手动登录以刷新cookies:" )
1064- print (f"1. 在{ selected_browser } 浏览器中访问: https://{ domain } " )
1065- print ("2. 完成SSO登录(Microsoft/Google等)" )
1066- print ("3. 登录成功后,重新运行此命令" )
1067- print ("=" * 80 )
1068- return False
1027+ try :
1028+ # 导入自动 SSO 登录模块
1029+ # 注意:这个脚本可能从不同位置被调用,需要动态导入
1030+ import importlib .util
1031+ import sys
1032+
1033+ # 尝试直接导入(如果在 moodle-dl 环境中)
1034+ try :
1035+ from moodle_dl .auto_sso_login import auto_login_with_sso_sync
1036+ except ImportError :
1037+ # 回退:尝试从文件路径加载
1038+ auto_sso_path = os .path .join (
1039+ os .path .dirname (os .path .abspath (__file__ )),
1040+ 'moodle_dl' , 'auto_sso_login.py'
1041+ )
1042+ if os .path .exists (auto_sso_path ):
1043+ spec = importlib .util .spec_from_file_location ("auto_sso_login" , auto_sso_path )
1044+ auto_sso_module = importlib .util .module_from_spec (spec )
1045+ spec .loader .exec_module (auto_sso_module )
1046+ auto_login_with_sso_sync = auto_sso_module .auto_login_with_sso_sync
1047+ else :
1048+ raise ImportError ("Cannot find auto_sso_login module" )
1049+
1050+ # 使用自动 SSO 登录刷新 cookies
1051+ refresh_success = auto_login_with_sso_sync (
1052+ moodle_domain = domain ,
1053+ cookies_path = output_file ,
1054+ preferred_browser = selected_browser ,
1055+ headless = True # 后台运行
1056+ )
1057+
1058+ if not refresh_success :
1059+ # 自动 SSO 登录失败 - 说明 SSO cookies 完全过期
1060+ print ("\n " + "=" * 80 )
1061+ print ("⚠️ 自动 SSO 登录失败 - SSO cookies 已完全过期" )
1062+ print ("=" * 80 )
1063+ print ("\n 💡 解决方案:" )
1064+ print (f" 在{ selected_browser } 浏览器中访问 { domain } 并完成 SSO 登录" )
1065+ print (f" 登录后,SSO cookies 会自动保存到浏览器" )
1066+ print (f" 然后重新运行此命令,将能够完全自动化" )
1067+ print ("\n 📌 这是唯一需要手动操作的场景(SSO cookies 完全过期时)" )
1068+ print (" 之后的所有操作都将完全自动化,无需再次手动登录" )
1069+ print ("=" * 80 )
1070+ return False
1071+
1072+ except Exception as e :
1073+ print (f"\n ⚠️ 自动 SSO 登录出错: { e } " )
1074+ print (" 回退到测试现有 cookies..." )
1075+ refresh_success = False
10691076
10701077 # 测试 cookies
10711078 cookies_valid = test_cookies (domain , output_file )
0 commit comments