Skip to content

Commit b1a58ff

Browse files
committed
Fix CS2105 video listing: auto-detect PANOPTO_HOST via Playwright and pages scan
PANOPTO_HOST was empty by default, causing Strategy 2 (Panopto folder API) to call https:///Panopto/... and fail with "No host supplied", leaving only the pages scan to find videos (6 incomplete results). Fixes: - _get_panopto_tab_folder: capture Panopto hostname from any Playwright request URL (or cookie domain as fallback); set global PANOPTO_HOST and persist to config.json for future runs - _find_panopto_in_pages: extract PANOPTO_HOST from embedded Viewer.aspx URLs in Canvas page bodies when not yet configured - Move global declarations to function top to satisfy Python 3.11 semantics Result: CS2105 now finds 11 videos (4 topic clips + 7 live web lectures) via Strategy 2, vs 6 incomplete page-scan results before.
1 parent 9edc24c commit b1a58ff

1 file changed

Lines changed: 50 additions & 5 deletions

File tree

downloader.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ def _get_panopto_tab_folder(course_id: int) -> tuple[str | None, list[dict], str
237237
Bearer token.
238238
Returns (folder_id, panopto_cookies, bearer_token).
239239
"""
240+
global PANOPTO_HOST # may be auto-detected from Playwright request URLs
240241
from playwright.sync_api import sync_playwright
241242

242243
r = requests.get(
@@ -251,20 +252,28 @@ def _get_panopto_tab_folder(course_id: int) -> tuple[str | None, list[dict], str
251252
if not launch_url:
252253
return None, [], None
253254

254-
folder_id: str | None = None
255-
bearer_token: str | None = None
256-
cookies: list[dict] = []
255+
folder_id: str | None = None
256+
bearer_token: str | None = None
257+
detected_host: str | None = None
258+
cookies: list[dict] = []
257259

258260
with sync_playwright() as p:
259261
browser = p.chromium.launch(headless=True)
260262
ctx = browser.new_context()
261263
page = ctx.new_page()
262264

263265
def _on_request(req: object) -> None:
264-
nonlocal folder_id, bearer_token
265-
m = re.search(r"folderID=([0-9a-f-]{36})", req.url, re.IGNORECASE)
266+
nonlocal folder_id, bearer_token, detected_host
267+
url = req.url
268+
m = re.search(r"folderID=([0-9a-f-]{36})", url, re.IGNORECASE)
266269
if m and folder_id is None:
267270
folder_id = m.group(1)
271+
# Auto-detect Panopto hostname from any Panopto request URL.
272+
if detected_host is None and "panopto" in url.lower():
273+
from urllib.parse import urlparse
274+
netloc = urlparse(url).netloc
275+
if netloc and "panopto" in netloc.lower():
276+
detected_host = netloc
268277
# Capture the OAuth Bearer token issued to the embedded page.
269278
# The WCF GetSessions endpoint requires it to return Subfolders.
270279
auth = req.headers.get("authorization", "")
@@ -277,11 +286,30 @@ def _on_request(req: object) -> None:
277286
time.sleep(3)
278287
cookies = [c for c in ctx.cookies()
279288
if "panopto" in c.get("domain", "").lower()]
289+
# Also try cookies domain as a fallback host source
290+
if detected_host is None and cookies:
291+
domain = cookies[0].get("domain", "").lstrip(".")
292+
if domain:
293+
detected_host = domain
280294
except Exception as e:
281295
tqdm.write(f" [warn] Playwright (Panopto tab) course {course_id}: {e}")
282296
finally:
283297
browser.close()
284298

299+
# Persist the detected host so all subsequent API calls use it.
300+
if detected_host:
301+
if not PANOPTO_HOST:
302+
PANOPTO_HOST = detected_host
303+
tqdm.write(f" [info] Auto-detected PANOPTO_HOST: {PANOPTO_HOST}")
304+
# Save to config so future sessions don't need to re-detect.
305+
try:
306+
cfg = json.load(open(_config_file)) if _config_file.exists() else {}
307+
cfg["PANOPTO_HOST"] = PANOPTO_HOST
308+
with open(_config_file, "w") as f:
309+
json.dump(cfg, f, indent=2)
310+
except Exception:
311+
pass
312+
285313
return folder_id, cookies, bearer_token
286314

287315

@@ -379,6 +407,7 @@ def _find_panopto_in_pages(course) -> list[dict]:
379407
Scan Canvas Pages for embedded Panopto Viewer.aspx links.
380408
Returns video dicts for each unique session UUID found.
381409
"""
410+
global PANOPTO_HOST # may be auto-detected from page bodies
382411
videos: list[dict] = []
383412
seen: set[str] = set()
384413

@@ -388,6 +417,22 @@ def _find_panopto_in_pages(course) -> list[dict]:
388417
body = getattr(page, "body", "") or ""
389418
if "panopto" not in body.lower():
390419
continue
420+
# Auto-detect PANOPTO_HOST from embedded URLs if not yet known.
421+
if not PANOPTO_HOST:
422+
m = re.search(
423+
r"https?://([\w.-]*panopto[\w.-]*)/Panopto/",
424+
body, re.IGNORECASE,
425+
)
426+
if m:
427+
PANOPTO_HOST = m.group(1)
428+
tqdm.write(f" [info] Auto-detected PANOPTO_HOST from page: {PANOPTO_HOST}")
429+
try:
430+
cfg = json.load(open(_config_file)) if _config_file.exists() else {}
431+
cfg["PANOPTO_HOST"] = PANOPTO_HOST
432+
with open(_config_file, "w") as f:
433+
json.dump(cfg, f, indent=2)
434+
except Exception:
435+
pass
391436
# Match ?id=UUID (Viewer.aspx or Embed.aspx links)
392437
for uid in re.findall(
393438
r"[?&]id=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}"

0 commit comments

Comments
 (0)