Repository: streamlink/streamlink

Issue:
Schoolism Video Feedbacks Plugins Compatibility?
Checklist

- [ ] This is a bug report.
- [ ] This is a feature request.
- [x] This is a plugin (improvement) request.
- [x] I have read the contribution guidelines.

With the Schoolism plugin, you can download lessons with Streamlink.exe --schoolism-email XXXX --schoolism-password XXXX --schoolism-part 1 -v "https://www.schoolism.com/watchLesson.php?type=st&id=506" best -o "LOCATION"

Is there a way to download the video feedbacks from urls such as https://www.schoolism.com/viewAssignment.php?type=st&courseID=44&id=506&notSubmitted=true as well? If there isn't, I'd greatly appreciate a modification to the plugin to allow for one to download the video feedbacks.



Comments:

Patch:
diff --git a/src/streamlink/plugins/schoolism.py b/src/streamlink/plugins/schoolism.py
index c71ff0dd..5285c2a7 100644
--- a/src/streamlink/plugins/schoolism.py
+++ b/src/streamlink/plugins/schoolism.py
@@ -1,20 +1,23 @@
 from __future__ import print_function
 
+import logging
 import re
 from functools import partial
 
 from streamlink.plugin import Plugin, PluginArguments, PluginArgument
 from streamlink.plugin.api import useragents
 from streamlink.plugin.api import validate
-from streamlink.stream import HLSStream
+from streamlink.stream import HLSStream, HTTPStream
 from streamlink.utils import parse_json
 
+log = logging.getLogger(__name__)
+
 
 class Schoolism(Plugin):
-    url_re = re.compile(r"https?://(?:www\.)?schoolism\.com/watchLesson.php")
+    url_re = re.compile(r"https?://(?:www\.)?schoolism\.com/(viewAssignment|watchLesson).php")
     login_url = "https://www.schoolism.com/index.php"
     key_time_url = "https://www.schoolism.com/video-html/key-time.php"
-    playlist_re = re.compile(r"var allVideos=(\[\{.*\}]);", re.DOTALL)
+    playlist_re = re.compile(r"var allVideos\s*=\s*(\[\{.*\}]);", re.DOTALL)
     js_to_json = partial(re.compile(r'(?!<")(\w+):(?!/)').sub, r'"\1":')
     playlist_schema = validate.Schema(
         validate.transform(playlist_re.search),
@@ -27,13 +30,13 @@ class Schoolism(Plugin):
                 validate.transform(parse_json),
                 [{
                     "sources": validate.all([{
-                        "playlistTitle": validate.text,
+                        validate.optional("playlistTitle"): validate.text,
                         "title": validate.text,
                         "src": validate.text,
                         "type": validate.text,
                     }],
                         # only include HLS streams
-                        validate.filter(lambda s: s["type"] == "application/x-mpegurl")
+                        # validate.filter(lambda s: s["type"] == "application/x-mpegurl")
                     )
                 }]
             )
@@ -63,7 +66,7 @@ class Schoolism(Plugin):
             default=1,
             metavar="PART",
             help="""
-        Play part number PART of the lesson.
+        Play part number PART of the lesson, or assignment feedback video.
 
         Defaults is 1.
         """
@@ -83,44 +86,50 @@ class Schoolism(Plugin):
         """
         if self.options.get("email") and self.options.get("password"):
             res = self.session.http.post(self.login_url, data={"email": email,
-                                                  "password": password,
-                                                  "redirect": None,
-                                                  "submit": "Login"})
+                                                               "password": password,
+                                                               "redirect": None,
+                                                               "submit": "Login"})
 
             if res.cookies.get("password") and res.cookies.get("email"):
                 return res.cookies.get("email")
             else:
-                self.logger.error("Failed to login to Schoolism, incorrect email/password combination")
+                log.error("Failed to login to Schoolism, incorrect email/password combination")
         else:
-            self.logger.error("An email and password are required to access Schoolism streams")
+            log.error("An email and password are required to access Schoolism streams")
 
     def _get_streams(self):
         user = self.login(self.options.get("email"), self.options.get("password"))
         if user:
-            self.logger.debug("Logged in to Schoolism as {0}", user)
+            log.debug("Logged in to Schoolism as {0}", user)
             res = self.session.http.get(self.url, headers={"User-Agent": useragents.SAFARI_8})
             lesson_playlist = self.playlist_schema.validate(res.text)
 
             part = self.options.get("part")
+            video_type = "Lesson" if "lesson" in self.url_re.match(self.url).group(1).lower() else "Assignment Feedback"
 
-            self.logger.info("Attempting to play lesson Part {0}", part)
+            log.info("Attempting to play {0} Part {1}", video_type, part)
             found = False
 
             # make request to key-time api, to get key specific headers
-            res = self.session.http.get(self.key_time_url, headers={"User-Agent": useragents.SAFARI_8})
+            _ = self.session.http.get(self.key_time_url, headers={"User-Agent": useragents.SAFARI_8})
 
             for i, video in enumerate(lesson_playlist, 1):
                 if video["sources"] and i == part:
                     found = True
                     for source in video["sources"]:
-                        for s in HLSStream.parse_variant_playlist(self.session,
-                                                                  source["src"],
-                                                                  headers={"User-Agent": useragents.SAFARI_8,
-                                                                           "Referer": self.url}).items():
-                            yield s
+                        if source['type'] == "video/mp4":
+                            yield "live", HTTPStream(self.session, source["src"],
+                                                     headers={"User-Agent": useragents.SAFARI_8,
+                                                              "Referer": self.url})
+                        elif source['type'] == "application/x-mpegurl":
+                            for s in HLSStream.parse_variant_playlist(self.session,
+                                                                      source["src"],
+                                                                      headers={"User-Agent": useragents.SAFARI_8,
+                                                                               "Referer": self.url}).items():
+                                yield s
 
             if not found:
-                self.logger.error("Could not find lesson Part {0}", part)
+                log.error("Could not find {0} Part {1}", video_type, part)
 
 
 __plugin__ = Schoolism
diff --git a/src/streamlink/plugins/steam.py b/src/streamlink/plugins/steam.py
index 6a5182ab..7c33470b 100644
--- a/src/streamlink/plugins/steam.py
+++ b/src/streamlink/plugins/steam.py
@@ -10,8 +10,10 @@ import streamlink
 from streamlink.exceptions import FatalPluginError
 from streamlink.plugin import Plugin, PluginArguments, PluginArgument
 from streamlink.plugin.api import validate
+from streamlink.plugin.api.utils import itertags, parse_json
 from streamlink.plugin.api.validate import Schema
 from streamlink.stream.dash import DASHStream
+from streamlink.compat import html_unescape
 
 log = logging.getLogger(__name__)
 
@@ -22,6 +24,8 @@ class SteamLoginFailed(Exception):
 
 class SteamBroadcastPlugin(Plugin):
     _url_re = re.compile(r"https?://steamcommunity.com/broadcast/watch/(\d+)")
+    _steamtv_url_re = re.compile(r"https?://steam.tv/(\w+)")
+    _watch_broadcast_url = "https://steamcommunity.com/broadcast/watch/"
     _get_broadcast_url = "https://steamcommunity.com/broadcast/getbroadcastmpd/"
     _user_agent = "streamlink/{}".format(streamlink.__version__)
     _broadcast_schema = Schema({
@@ -77,7 +81,7 @@ class SteamBroadcastPlugin(Plugin):
 
     @classmethod
     def can_handle_url(cls, url):
-        return cls._url_re.match(url) is not None
+        return cls._url_re.match(url) is not None or cls._steamtv_url_re.match(url) is not None
 
     @property
     def donotcache(self):
@@ -191,6 +195,16 @@ class SteamBroadcastPlugin(Plugin):
                 log.info("Logged in as {0}".format(self.get_option("email")))
                 self.save_cookies(lambda c: "steamMachineAuth" in c.name)
 
+        # Handle steam.tv URLs
+        if self._steamtv_url_re.match(self.url) is not None:
+            # extract the steam ID from the page
+            res = self.session.http.get(self.url)
+            for div in itertags(res.text, 'div'):
+                if div.attributes.get("id") == "webui_config":
+                    broadcast_data = html_unescape(div.attributes.get("data-broadcast"))
+                    steamid = parse_json(broadcast_data).get("steamid")
+                    self.url = self._watch_broadcast_url + steamid
+
         # extract the steam ID from the URL
         steamid = self._url_re.match(self.url).group(1)
         res = self.session.http.get(self.url)  # get the page to set some cookies


Test patch:
diff --git a/tests/plugins/test_steam.py b/tests/plugins/test_steam.py
index db16601f..0f362f1e 100644
--- a/tests/plugins/test_steam.py
+++ b/tests/plugins/test_steam.py
@@ -7,6 +7,8 @@ class TestPluginSteamBroadcastPlugin(unittest.TestCase):
     def test_can_handle_url(self):
         self.assertTrue(SteamBroadcastPlugin.can_handle_url('https://steamcommunity.com/broadcast/watch/12432432'))
         self.assertTrue(SteamBroadcastPlugin.can_handle_url('http://steamcommunity.com/broadcast/watch/342342'))
+        self.assertTrue(SteamBroadcastPlugin.can_handle_url('https://steam.tv/dota2'))
+        self.assertTrue(SteamBroadcastPlugin.can_handle_url('http://steam.tv/dota2'))
 
     def test_can_handle_url_negative(self):
         # shouldn't match