Skip to content

Commit 68bca6f

Browse files
committed
Resolve Canvas Panopto tool ID per-course instead of hardcoding 128
Reported: with a secondary Canvas token, EE4802 showed zero videos. Root cause: _PANOPTO_TAB_TOOL_ID was a hardcoded 128. Canvas assigns external-tool IDs per-institution and sometimes per-course; the old constant only matched the author's default token/course. For any other token or a course that registered Panopto under a different tool ID, Strategy 2 (Panopto folder API) silently returned no videos. Fix: new _resolve_panopto_tool_id(course_id) queries GET /api/v1/courses/{course_id}/external_tools and matches on name/domain/url containing "panopto" (or common aliases "videos", "video", "lecture videos"). Results are cached per-course. The old constant is retained only as a last-resort fallback and log any non-200 response from the sessionless-launch endpoint so the failure is no longer silent.
1 parent 46c9c29 commit 68bca6f

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

downloader.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,52 @@ def get_course_by_id(canvas: Canvas, course_id: int):
262262
# VIDEO SECTION
263263
# ══════════════════════════════════════════════════════════════════════════════
264264

265-
# Canvas external-tool ID for the "Videos/Panopto" course navigation tab
266-
_PANOPTO_TAB_TOOL_ID = 128
265+
# Canvas external-tool ID for the "Videos/Panopto" course navigation tab.
266+
# Used as a fallback; the actual ID is discovered per-course via
267+
# _resolve_panopto_tool_id() because Canvas assigns different tool IDs
268+
# per-institution and sometimes per-course.
269+
_PANOPTO_TAB_TOOL_ID_FALLBACK = 128
270+
271+
# Per-course cache for resolved Panopto tool IDs.
272+
_PANOPTO_TOOL_ID_CACHE: dict[int, int] = {}
273+
274+
275+
def _resolve_panopto_tool_id(course_id: int) -> int | None:
276+
"""Discover the Canvas external-tool ID of the Panopto/Videos tab for a
277+
specific course.
278+
279+
Canvas assigns different external-tool IDs per-institution and sometimes
280+
per-course, so the old hardcoded ID (128) only worked for the author's
281+
default token. For a new token or a new course (e.g. issue reported for
282+
EE4802 with a secondary Canvas token), the tab ID differs and Strategy 2
283+
silently returned zero videos.
284+
285+
We query `/api/v1/courses/{course_id}/external_tools` and match any tool
286+
whose name/domain mentions Panopto or Videos. Results are cached.
287+
"""
288+
if course_id in _PANOPTO_TOOL_ID_CACHE:
289+
return _PANOPTO_TOOL_ID_CACHE[course_id]
290+
try:
291+
r = requests.get(
292+
f"{CANVAS_URL}/api/v1/courses/{course_id}/external_tools",
293+
headers=_canvas_headers(),
294+
params={"per_page": 100},
295+
timeout=30,
296+
)
297+
if r.status_code != 200:
298+
return None
299+
for tool in r.json():
300+
name = (tool.get("name") or "").lower()
301+
domain = (tool.get("domain") or "").lower()
302+
url = (tool.get("url") or "").lower()
303+
if ("panopto" in name or "panopto" in domain or "panopto" in url
304+
or name in ("videos", "video", "lecture videos")):
305+
tid = int(tool["id"])
306+
_PANOPTO_TOOL_ID_CACHE[course_id] = tid
307+
return tid
308+
except Exception as e:
309+
tqdm.write(f" [warn] _resolve_panopto_tool_id({course_id}): {e}")
310+
return None
267311

268312

269313
def _find_panopto_items(canvas: Canvas, course) -> list[dict]:
@@ -369,13 +413,15 @@ def _get_panopto_tab_folder(course_id: int) -> tuple[str | None, list[dict], str
369413
_ensure_playwright_browsers()
370414
from playwright.sync_api import sync_playwright
371415

416+
tool_id = _resolve_panopto_tool_id(course_id) or _PANOPTO_TAB_TOOL_ID_FALLBACK
372417
r = requests.get(
373418
f"{CANVAS_URL}/api/v1/courses/{course_id}/external_tools/sessionless_launch"
374-
f"?id={_PANOPTO_TAB_TOOL_ID}&launch_type=course_navigation",
419+
f"?id={tool_id}&launch_type=course_navigation",
375420
headers=_canvas_headers(),
376421
timeout=30,
377422
)
378423
if r.status_code != 200:
424+
tqdm.write(f" [warn] Panopto tab launch failed (tool_id={tool_id}, status={r.status_code})")
379425
return None, [], None
380426
launch_url = r.json().get("url")
381427
if not launch_url:
@@ -768,9 +814,10 @@ def _extract_stream(body: dict) -> tuple[str, str] | None:
768814
return None
769815

770816
tqdm.write(f" DeliveryInfo unavailable; re-launching via Canvas LTI…")
817+
tool_id = _resolve_panopto_tool_id(course_id) or _PANOPTO_TAB_TOOL_ID_FALLBACK
771818
r2 = requests.get(
772819
f"{CANVAS_URL}/api/v1/courses/{course_id}/external_tools/sessionless_launch"
773-
f"?id={_PANOPTO_TAB_TOOL_ID}&launch_type=course_navigation",
820+
f"?id={tool_id}&launch_type=course_navigation",
774821
headers=_canvas_headers(), timeout=30,
775822
)
776823
if r2.status_code != 200:

electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "auto-note",
3-
"version": "0.12.2",
3+
"version": "0.12.4",
44
"description": "AutoNote — lecture notes generator from Canvas recordings",
55
"homepage": "https://github.com/nodeeeeee/Auto-Note",
66
"author": {

0 commit comments

Comments
 (0)