SPB Git

spb/uqo-imm1033

Public
6.9 KB · 129 lines python
1#!/usr/bin/env python32"""Publication de fichiers sur Moodle@UQO en pilotant Safari (session Microsoft déjà ouverte) — recette du 2026-09-24,3généralisée le 2026-10-10. Chaque étape = un seul osascript (injection JS asynchrone + delay + lecture de window.__res).45Usage :6  moodle_publish.py add <course_id> <section> <url_pdf> "<nom>" "<description html>" [--before <cmid>]7  moodle_publish.py replace <cmid> <url_pdf> ["<nom>"] ["<description html>"]8  moodle_publish.py list <course_id>9Le fichier source est récupéré par fetch() depuis le site du cours (en-tête CORS Access-Control-Allow-Origin: * sur /files/).10"""11import json, subprocess, sys, time1213MOODLE = "https://moodle.uqo.ca"141516def osa(script):17    r = subprocess.run(["osascript", "-"], input=script, capture_output=True, text=True, encoding="utf-8")18    if r.returncode != 0:19        raise SystemExit("osascript: " + r.stderr.strip())20    return r.stdout.strip()212223def js_quote(s):24    """Chaîne JS (JSON, UTF-8 brut)."""25    return json.dumps(s, ensure_ascii=False)262728def as_quote(s):29    """Chaîne AppleScript : seuls \\ et \" sont échappés (osascript lit l'UTF-8 tel quel)."""30    return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'313233def step(url, js, delay=8, poll=12):34    """Navigue vers url (si non vide), injecte js (doit poser window.__res), attend, retourne window.__res."""35    nav = f'set URL of current tab of front window to "{url}"\n  delay 7' if url else ""36    script = f'''tell application "Safari"37  {nav}38  do JavaScript {as_quote("window.__res=null;" + js)} in current tab of front window39  delay {delay}40  set out to ""41  repeat {poll} times42    set out to do JavaScript "window.__res===null?'':String(window.__res)" in current tab of front window43    if out is not "" then exit repeat44    delay 245  end repeat46  return out47end tell'''48    return osa(script)495051def upload_js(pdf_url, name, desc, submit=True):52    return f'''(async function(){{53  try {{54    var it=document.querySelector('input[name="files"]').value;55    var cid=document.querySelector('.filemanager').id.replace('filemanager-','');56    var ctx=M.cfg.contextid, sk=M.cfg.sesskey;57    // 1. vider le brouillon (remplacement)58    var fdl=new FormData(); fdl.append('sesskey',sk); fdl.append('client_id',cid); fdl.append('itemid',it); fdl.append('filepath','/');59    var lst=await (await fetch('/repository/draftfiles_ajax.php?action=list',{{method:'POST',body:fdl,credentials:'same-origin'}})).json();60    for (var f of (lst.list||[])) {{ var fdd=new FormData(); fdd.append('sesskey',sk); fdd.append('client_id',cid); fdd.append('itemid',it); fdd.append('filepath',f.filepath||'/'); fdd.append('filename',f.filename); await fetch('/repository/draftfiles_ajax.php?action=delete',{{method:'POST',body:fdd,credentials:'same-origin'}}); }}61    // 2. téléverser62    var src={js_quote(pdf_url)}; var fname=decodeURIComponent(src.split('/').pop());63    var blob=await (await fetch(src+(src.indexOf('?')<0?'?':'&')+'v='+Date.now(),{{mode:'cors',cache:'no-store'}})).blob();64    var fd=new FormData(); fd.append('repo_upload_file',blob,fname); fd.append('sesskey',sk); fd.append('repo_id','3'); fd.append('itemid',it); fd.append('savepath','/'); fd.append('title',''); fd.append('author',''); fd.append('license','allrightsreserved'); fd.append('ctx_id',ctx); fd.append('client_id',cid); fd.append('overwrite','1');65    var up=await (await fetch('/repository/repository_ajax.php?action=upload',{{method:'POST',body:fd,credentials:'same-origin'}})).json();66    if (up.error) {{ window.__res='UPLOAD ERROR '+JSON.stringify(up); return; }}67    // 3. nom + description68    var nm={js_quote(name)}; if (nm) document.querySelector('#id_name').value=nm;69    var ds={js_quote(desc)}; if (ds && window.tinyMCE && tinyMCE.get('id_introeditor')) tinyMCE.get('id_introeditor').setContent(ds);70    var sd=document.querySelector('#id_showdescription'); if (sd && ds) sd.checked=true;71    var ss=document.querySelector('#id_showsize'); if (ss) ss.checked=true;72    var st=document.querySelector('#id_showtype'); if (st) st.checked=true;73    window.__res='UPLOADED '+JSON.stringify({{file:up.file||up.title||fname,size:blob.size}});74    if ({str(submit).lower()}) setTimeout(function(){{document.querySelector('#id_submitbutton2').click();}},500);75  }} catch(e) {{ window.__res='ERROR '+e; }}76}})();'''777879def list_course(course_id):80    js = '''(function(){var out=[];document.querySelectorAll('li.section').forEach(function(s){var name=(s.querySelector('.sectionname, h3')||{}).textContent||'';s.querySelectorAll('li.activity').forEach(function(m){out.push((s.id||'')+'|'+(m.id||'')+'|'+((m.querySelector('.activityname')||m.querySelector('.instancename')||{textContent:''}).textContent||'').trim().slice(0,90));});});window.__res=out.join('\\n');})();'''81    return step(f"{MOODLE}/course/view.php?id={course_id}", js, delay=2, poll=3)828384def find_cmid(course_id, name):85    for line in list_course(course_id).splitlines():86        parts = line.split('|')87        if len(parts) >= 3 and parts[2].startswith(name[:60]):88            return int(parts[1].replace('module-', ''))89    return None909192def move_before(course_id, cmid, target_cmid):93    js = f'''(async function(){{try{{var r=await (await fetch('/lib/ajax/service.php?sesskey='+M.cfg.sesskey+'&info=core_courseformat_update_course',{{method:'POST',credentials:'same-origin',headers:{{'Content-Type':'application/json'}},body:JSON.stringify([{{index:0,methodname:'core_courseformat_update_course',args:{{action:'cm_move',courseid:{course_id},ids:[{cmid}],targetcmid:{target_cmid}}}}}])}})).text();window.__res='MOVED '+r.slice(0,120);}}catch(e){{window.__res='ERROR '+e}}}})();'''94    return step(f"{MOODLE}/course/view.php?id={course_id}", js, delay=4, poll=4)959697def add(course_id, section, pdf_url, name, desc, before=None):98    url = f"{MOODLE}/course/modedit.php?add=resource&type=&course={course_id}&section={section}&return=0&sr=0"99    r = step(url, upload_js(pdf_url, name, desc), delay=12, poll=10)100    print("add:", r or "(page soumise avant la lecture du résultat)")101    time.sleep(8)102    cmid = find_cmid(course_id, name)103    print("cmid:", cmid)104    if not cmid:105        raise SystemExit("échec : ressource introuvable après le téléversement")106    if before and cmid:107        print(move_before(course_id, cmid, before))108    return cmid109110111def replace(cmid, pdf_url, name="", desc=""):112    url = f"{MOODLE}/course/modedit.php?update={cmid}&return=1"113    r = step(url, upload_js(pdf_url, name, desc), delay=12, poll=10)114    print("replace:", r)115    return r116117118if __name__ == "__main__":119    a = sys.argv[1:]120    if not a:121        print(__doc__); sys.exit(0)122    if a[0] == "list":123        print(list_course(int(a[1])))124    elif a[0] == "add":125        before = int(a[a.index("--before") + 1]) if "--before" in a else None126        add(int(a[1]), int(a[2]), a[3], a[4], a[5] if len(a) > 5 and a[5] != "--before" else "", before)127    elif a[0] == "replace":128        replace(int(a[1]), a[2], a[3] if len(a) > 3 else "", a[4] if len(a) > 4 else "")129
Files · main
Menu

Theme