SPB Git

spb/uqo-imm1033

Public
6.5 KB · 123 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)18    if r.returncode != 0:19        raise SystemExit("osascript: " + r.stderr.strip())20    return r.stdout.strip()212223def js_quote(s):24    return json.dumps(s)252627def step(url, js, delay=8, poll=12):28    """Navigue vers url (si non vide), injecte js (doit poser window.__res), attend, retourne window.__res."""29    nav = f'set URL of current tab of front window to "{url}"\n  delay 7' if url else ""30    script = f'''tell application "Safari"31  {nav}32  do JavaScript {js_quote("window.__res=null;" + js)} in current tab of front window33  delay {delay}34  set out to ""35  repeat {poll} times36    set out to do JavaScript "window.__res===null?'':String(window.__res)" in current tab of front window37    if out is not "" then exit repeat38    delay 239  end repeat40  return out41end tell'''42    return osa(script)434445def upload_js(pdf_url, name, desc, submit=True):46    return f'''(async function(){{47  try {{48    var it=document.querySelector('input[name="files"]').value;49    var cid=document.querySelector('.filemanager').id.replace('filemanager-','');50    var ctx=M.cfg.contextid, sk=M.cfg.sesskey;51    // 1. vider le brouillon (remplacement)52    var fdl=new FormData(); fdl.append('sesskey',sk); fdl.append('client_id',cid); fdl.append('itemid',it); fdl.append('filepath','/');53    var lst=await (await fetch('/repository/draftfiles_ajax.php?action=list',{{method:'POST',body:fdl,credentials:'same-origin'}})).json();54    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'}}); }}55    // 2. téléverser56    var src={js_quote(pdf_url)}; var fname=decodeURIComponent(src.split('/').pop());57    var blob=await (await fetch(src,{{mode:'cors'}})).blob();58    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');59    var up=await (await fetch('/repository/repository_ajax.php?action=upload',{{method:'POST',body:fd,credentials:'same-origin'}})).json();60    if (up.error) {{ window.__res='UPLOAD ERROR '+JSON.stringify(up); return; }}61    // 3. nom + description62    var nm={js_quote(name)}; if (nm) document.querySelector('#id_name').value=nm;63    var ds={js_quote(desc)}; if (ds && window.tinyMCE && tinyMCE.get('id_introeditor')) tinyMCE.get('id_introeditor').setContent(ds);64    var sd=document.querySelector('#id_showdescription'); if (sd && ds) sd.checked=true;65    var ss=document.querySelector('#id_showsize'); if (ss) ss.checked=true;66    var st=document.querySelector('#id_showtype'); if (st) st.checked=true;67    window.__res='UPLOADED '+JSON.stringify({{file:up.file||up.title||fname,size:blob.size}});68    if ({str(submit).lower()}) setTimeout(function(){{document.querySelector('#id_submitbutton2').click();}},500);69  }} catch(e) {{ window.__res='ERROR '+e; }}70}})();'''717273def list_course(course_id):74    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');})();'''75    return step(f"{MOODLE}/course/view.php?id={course_id}", js, delay=2, poll=3)767778def find_cmid(course_id, name):79    for line in list_course(course_id).splitlines():80        parts = line.split('|')81        if len(parts) >= 3 and parts[2].startswith(name[:60]):82            return int(parts[1].replace('module-', ''))83    return None848586def move_before(course_id, cmid, target_cmid):87    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}}}})();'''88    return step(f"{MOODLE}/course/view.php?id={course_id}", js, delay=4, poll=4)899091def add(course_id, section, pdf_url, name, desc, before=None):92    url = f"{MOODLE}/course/modedit.php?add=resource&type=&course={course_id}&section={section}&return=0&sr=0"93    r = step(url, upload_js(pdf_url, name, desc), delay=12, poll=10)94    print("add:", r)95    if not r.startswith("UPLOADED"):96        raise SystemExit("échec du téléversement")97    time.sleep(8)98    cmid = find_cmid(course_id, name)99    print("cmid:", cmid)100    if before and cmid:101        print(move_before(course_id, cmid, before))102    return cmid103104105def replace(cmid, pdf_url, name="", desc=""):106    url = f"{MOODLE}/course/modedit.php?update={cmid}&return=1"107    r = step(url, upload_js(pdf_url, name, desc), delay=12, poll=10)108    print("replace:", r)109    return r110111112if __name__ == "__main__":113    a = sys.argv[1:]114    if not a:115        print(__doc__); sys.exit(0)116    if a[0] == "list":117        print(list_course(int(a[1])))118    elif a[0] == "add":119        before = int(a[a.index("--before") + 1]) if "--before" in a else None120        add(int(a[1]), int(a[2]), a[3], a[4], a[5] if len(a) > 5 and a[5] != "--before" else "", before)121    elif a[0] == "replace":122        replace(int(a[1]), a[2], a[3] if len(a) > 3 else "", a[4] if len(a) > 4 else "")123
Files · 81b1639b59b7c1baef789eace5b20fc7352d4ac5
Menu

Theme