haiku.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """
  2. Haiku (PowerSchool Learning) backend
  3. This file is part of polyglot, etc.
  4. """
  5. from lxml.html import parse
  6. from io import StringIO
  7. from backends.support import Backend, Course, ResourceCategory, ResourceRawHTML, FileUpload, Quiz, MultipleChoiceQuestion, FreeResponseQuestion, Forum, Post
  8. import authentication.google
  9. import requests
  10. import re
  11. class HaikuResourceXHR(ResourceRawHTML):
  12. def __init__(self, name, b_id, backend, course, referer):
  13. self.name = name
  14. self.b_id = b_id
  15. self.backend = backend
  16. self.course = course
  17. self.referer = referer
  18. @property
  19. def html(self):
  20. url = self.backend.base + self.course.id + "/cms_box/render_content/" + self.b_id
  21. s = self.backend.session.post(url, {"csrf_token": self.backend.csrftok}, headers = {
  22. "X-Requested-With": "XMLHttpRequest",
  23. "X-Prototype-Version": "1.7",
  24. "Referer": self.referer
  25. })
  26. return s.text
  27. class HaikuResourceCategory(ResourceCategory):
  28. def __init__(self, name, url, children, backend, course):
  29. self.name = name
  30. self.url = url
  31. self.children = children
  32. self.backend = backend
  33. self.course = course
  34. @property
  35. def contents(self):
  36. ret = []
  37. out = self.backend.session.get(self.backend.base + self.url)
  38. tree = parse(StringIO(out.text)).getroot()
  39. for box in tree.cssselect(".cms_box"):
  40. name = box.text_content().strip()
  41. b_id = box.get("id")[len("box_"):]
  42. ret += [HaikuResourceXHR(name, b_id, self.backend, self.course, self.backend.base + self.url)]
  43. return ret
  44. """
  45. class HaikuQuiz(Quiz):
  46. def __init__(self, course, id, name):
  47. super().__init__(id, name)
  48. self.course = course
  49. def questions(self):
  50. question1 = MultipleChoiceQuestion("Is mathematics fun?", ["Absolutely!", "Definitely!"])
  51. question2 = FreeResponseQuestion("What's your favourite part?")
  52. return [question1, question2]
  53. def submit(self, responses):
  54. return True
  55. class HaikuForum(Forum):
  56. def __init__(self, course, id, name):
  57. super().__init__(id, name)
  58. self.course = course
  59. @property
  60. def thread(self):
  61. reply2 = Post("Babs Seed", "That gives me a good idea to annoy the CMCs!", [])
  62. reply1 = Post("Jane Doe", "DNA nanotechnology involves forming artificial, designed nanostructures", [reply2])
  63. question = Post("Carl Smith", "In the homework problem set, you looked at a formula to estimate the number of basepairs in a DNA strand given the length. What are your thoughts?", [reply1])
  64. return question
  65. def reply(self, parent, body):
  66. return True
  67. """
  68. class HaikuCourse(Course):
  69. def __init__(self, backend, course):
  70. self.backend = backend
  71. # onclick ala redirect_to('/teacher/coursestring/cms_page/view')
  72. # why they can't just use an <a> is a mystery o_o
  73. self.url = course.get("onclick")[len("redirect_to('"):-(len("')"))]
  74. self.id = self.url[:-len("/cms_page/view")]
  75. # Course name and teacher name, no localisation?
  76. self.title = course.text_content().strip()
  77. self.teacher = "Unknown teacher" # TODO?
  78. def course_page(self):
  79. return parse(StringIO(self.backend.session.get(self.backend.base + self.url).text)).getroot()
  80. """
  81. There are a *lot* of resources Haiku emits.
  82. The most basic are found in the sidebar (#sidebar)
  83. Take the links and interpret them semantically based on list tags (reasonable HTML, huh?!)
  84. Deeper nested resources are more links away (and XHR); careful about bandwidth!
  85. """
  86. @property
  87. def resources(self):
  88. def process_li(li):
  89. name = ""
  90. href = ""
  91. children = []
  92. for child in li:
  93. if child.tag == "div":
  94. link = child.cssselect("a")[0]
  95. name = link.text_content().strip()
  96. href = link.get("href")
  97. elif child.tag == "ul":
  98. children = [process_li(li) for li in child]
  99. return HaikuResourceCategory(name, href, children, self.backend, self)
  100. sidebar = self.course_page().cssselect("#sidebar")[0][0]
  101. return ResourceCategory("Root", process_ul(sidebar), [])
  102. class HaikuBackend(Backend):
  103. Config = ["Email", "Password", "School"]
  104. def login(self, config):
  105. self.session = requests.Session()
  106. self.base = "https://" + config["School"] + ".learning.powerschool.com"
  107. endpoint = self.base + "/do/authentication/google/google_begin?&openid_identifier=" + config["Email"].split("@")[1]
  108. out = GoogleAuth.oauth(self.session, config["Email"], config["Password"], endpoint)
  109. self.portal = parse(StringIO(out.text))
  110. self.csrftok = re.search("^ CSRFTOK = '([0-9a-f]*)';$", out.text, re.MULTILINE).group(1)
  111. return True # TODO
  112. @property
  113. def courses(self):
  114. courses = []
  115. for course in self.portal.getroot().cssselect(".filter-class"):
  116. courses += [HaikuCourse(self, course)]
  117. return courses