siidheesh

IVLE Downloader v0.0.7

Aug 23rd, 2017
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.65 KB | None | 0 0
  1. #!python3.6
  2. #coding: utf8
  3. """
  4. MIT License
  5.  
  6. Copyright (c) 2016-2018 Siidheesh Theivasigamani
  7.  
  8. Permission is hereby granted, free of charge, to any person obtaining a copy
  9. of this software and associated documentation files (the "Software"), to deal
  10. in the Software without restriction, including without limitation the rights
  11. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. copies of the Software, and to permit persons to whom the Software is
  13. furnished to do so, subject to the following conditions:
  14.  
  15. The above copyright notice and this permission notice shall be included in all
  16. copies or substantial portions of the Software.
  17.  
  18. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  24. SOFTWARE.
  25.  
  26. *** Workbin Downloader by siid ***
  27.  
  28. Changelog:
  29. v0.0.7: 31 January 2018
  30. Applied quick fix to have multiple courses with the same name, e.g. two EE2012 mods on IVLE, to be put into the same folder
  31.  
  32. v0.0.6: 12 January 2018
  33. Fixed name formatting flaw where "GEH1036/GEK1505" translated into file path GEH1036/GEK1505/.. and for other modules with a "/" in the name as well.
  34.  
  35. v0.0.5: 25 September 2017
  36. Multiple workbins from each module are now supported. Previously, only the module's first listed workbin was assumed to exist.
  37.  
  38. v0.0.4: 25 September 2017
  39. Fixed sanitisation of file and folder names, added uploadtime into file names to account for "updated" files with the same name
  40.  
  41. v0.0.3: 23 August 2017
  42. Added user, pass, and token validation
  43.  
  44. v0.0.2: 17 August 2017
  45. Removed need for human input/interaction
  46.  
  47. v0.0.1: August 2016
  48. Basic functionality
  49.  
  50. Description:
  51.  
  52. Downloads workbins of currently active mods into the script's current directory.
  53. *** Needs your own NUS IVLE LAPI key and IVLE credentials ***
  54. When it works, it works. WHen it doesn't, it doesn't :)
  55.  
  56. """
  57. from __future__ import print_function
  58. from http.server import BaseHTTPRequestHandler, HTTPServer                                                                                                                                  
  59. from urllib.parse import parse_qs
  60. from pathlib import Path
  61. import webbrowser
  62. import urllib.request
  63. import json
  64. import re
  65. import os
  66. import sys
  67.  
  68. import pprint
  69. pp = pprint.PrettyPrinter(indent=4)
  70.  
  71. import ssl
  72. ssl._create_default_https_context = ssl._create_unverified_context
  73.  
  74. import requests
  75. import mechanicalsoup
  76. browser = mechanicalsoup.StatefulBrowser()
  77.  
  78. # YOUR NUS LAPI KEY GOES HERE
  79. # get from https://ivle.nus.edu.sg/LAPI/default.aspx
  80. key = "WPhIannlffrfUwf1P0LWr"
  81.  
  82. IVLE_USER = ""
  83. IVLE_PASS = ""
  84.  
  85. token = None
  86.  
  87. #redundant now that MechanicalSoup is used, change it if another localhost server is used with the same port, as the request HAS to fail and throw an exception (see bottom)
  88. PORT = 8323                                                                                                                                                
  89.  
  90. modules = {}
  91. workbin_tree = {}
  92.  
  93. rootdir = os.path.dirname(os.path.abspath(__file__))
  94.  
  95. stats = {"count":0,"dl":[],"errcount":0}
  96.  
  97. def enum_annonce(cid):
  98.     #res = urllib.request.urlopen("https://ivle.nus.edu.sg/api/Lapi.svc/Webcasts?APIKey=%s&AuthToken=%s&CourseID=%s&Duration=0&MediaChannelID=&TitleOnly=false" %(key,token,cid))
  99.     res = urllib.request.urlopen("https://ivle.nus.edu.sg/api/Lapi.svc/Announcements_Unread?APIKey=%s&AuthToken=%sTitleOnly=False" %(key,token))
  100.     return json.loads(res.read().decode("utf-8"))
  101.  
  102. def download_file(file_id,target_file):
  103.     print(target_file.encode('utf-8').strip(), end="")
  104.     res = urllib.request.urlopen("https://ivle.nus.edu.sg/api/downloadfile.ashx?APIKey=%s&AuthToken=%s&ID=%s&target=workbin" %(key,token,file_id))
  105.     CHUNK = 20 * 1024 * 1024
  106.     path = os.path.normcase(rootdir+target_file)
  107.     try:
  108.         with open(path, 'wb') as f:
  109.            while True:
  110.               chunk = res.read(CHUNK)
  111.               if not chunk: break
  112.               f.write(chunk)
  113.               print(".", end="")
  114.         #print("%s downloaded" % target_file)
  115.         print("\tdone!")
  116.         if target_file not in stats["dl"]:
  117.             stats["dl"].append(target_file)
  118.             stats["count"] += 1
  119.     except Exception as ex:
  120.         template = "\texception of type {0} occurred. Arguments:{1!r}"
  121.         print(template.format(type(ex).__name__, ex.args))
  122.         stats["errcount"] += 1
  123.    
  124. def manage_dir(tree,curr=""):
  125.     p = Path(rootdir+curr)
  126.     if not p.exists() or not p.is_dir():
  127.         p.mkdir()
  128.     for entry in tree:
  129.         if type(tree[entry]) is not dict:
  130.             if not Path(rootdir+curr+"/"+tree[entry]).is_file():
  131.                 download_file(entry,curr+"/"+tree[entry])
  132.             #else:
  133.                 #print("skipped %s" % curr+"/"+tree[entry])
  134.         else:
  135.             manage_dir(tree[entry],curr+"/"+re.sub('[^\w_.)( -]', '_', entry))
  136.  
  137. def dig_folder(curr):
  138.     tree = {}
  139.     if "Files" in curr:
  140.         for file in curr["Files"]:
  141.             name,ext = re.sub('[^\w_.)( -]', '_', file["FileName"]).split(".", 1)
  142.             tree[file["ID"]] = name + "_" + file["UploadTime"][file["UploadTime"].index("(") + 1:file["UploadTime"].rindex("+")] + "." + ext
  143.     if "Folders" in curr:
  144.         for folder in curr["Folders"]:
  145.             tree[re.sub('[^\w_.)( -]', '_', folder["FolderName"])] = dig_folder(folder)
  146.     return tree
  147.        
  148. def update_workbins():
  149.     print("\n******** Enumerating modules and workbins ********\n")
  150.     res = urllib.request.urlopen("https://ivle.nus.edu.sg/api/Lapi.svc/Modules?APIKey=%s&AuthToken=%s&Duration=0&IncludeAllInfo=false" % (key,token))
  151.     data = json.loads(res.read().decode("utf-8"))["Results"]
  152.     for entry in data:
  153.         if entry["isActive"]=="Y":
  154.             cid = entry["ID"]
  155.             """annonce = enum_annonce(cid)
  156.            if annonce['Results'] and annonce['Results'][0]:
  157.                print("Unread announcements for", code, "!")"""
  158.             code = entry["CourseCode"]
  159.             res = urllib.request.urlopen("https://ivle.nus.edu.sg/api/Lapi.svc/Workbins?APIKey=%s&AuthToken=%s&CourseID=%s&Duration=0&WorkbinID=&TitleOnly=false" % (key,token,cid))
  160.             data1 = json.loads(res.read().decode("utf-8"))
  161.             if not data1["Results"] or not data1["Results"][0]:
  162.                 print("Empty workbin", code)
  163.                 continue
  164.             #if code not in workbin_tree:
  165.             print("Found workbin", code)
  166.             if len(data1["Results"]) <= 1:
  167.                 workbin_tree[code] = dig_folder(data1["Results"][0])
  168.             else:
  169.                 workbin_tree[code] = {}
  170.                 for wbin in data1["Results"]:
  171.                     workbin_tree[code][re.sub('[^\w_.)( -]', '_', wbin['Title'])] = dig_folder(wbin)
  172.     print("\n******** Updating filesystem - Downloading new files, missing files ********\n")
  173.     #pprint.pprint(workbin_tree)
  174.     manage_dir(workbin_tree)
  175.     if stats["errcount"]>0:
  176.         print("Errors encountered:",stats["errcount"],'\n')
  177.     if stats["count"]>0:
  178.         print("\n******** Done! ********\n")
  179.         print("Files downloaded:",stats["count"],'\n')
  180.         print('\n'.join(stats["dl"]),'\n')
  181.     else:
  182.         print("\n******** No changes made! ********\n")
  183.     if len(sys.argv) == 1:
  184.         print("Press enter to quit")
  185.         input()
  186.     quit()
  187.  
  188. if not IVLE_USER or not IVLE_PASS:
  189.     print("ERROR: You need to edit the script and enter your IVLE user and pass in the IVLE_USER and IVLE_PASS variables at the top before the script can work.")
  190. elif key is not None and len(key) > 0:
  191.     browser.open("https://ivle.nus.edu.sg/api/login/?apikey="+key+"&url=http://localhost:"+str(PORT)+"/")
  192.     browser.select_form('#frm')
  193.     browser["userid"] = IVLE_USER
  194.     browser["password"] = IVLE_PASS
  195.     try:
  196.         resp = browser.submit_selected()
  197.         print("ERROR: Your login credentials were incorrect.")
  198.     except requests.exceptions.ConnectionError as e:
  199.         url = e.request.url
  200.         parts = url.split('=')
  201.         if len(parts) == 2 and parts[0] == 'http://localhost:'+str(PORT)+'/?token':
  202.             token = parts[1]
  203.             update_workbins()
  204. else:
  205.     print("ERROR: You need to get an IVLE LAPI key from https://ivle.nus.edu.sg/LAPI/default.aspx first. Press enter to open the webpage")
  206.     input()
  207.     webbrowser.open("https://ivle.nus.edu.sg/LAPI/default.aspx", new=0, autoraise=True)
Add Comment
Please, Sign In to add comment