Repository: streamlink/streamlink
Pull request #1973 (2018-07-31 14:33:49)

plugins.facebook: support for videos in posts
Support for videos posts. eg. https://www.facebook.com/119555411802156/posts/500665313691162/


Patch:
diff --git a/src/streamlink/plugins/facebook.py b/src/streamlink/plugins/facebook.py
index 1b5976e2..537b4a5a 100644
--- a/src/streamlink/plugins/facebook.py
+++ b/src/streamlink/plugins/facebook.py
@@ -7,7 +7,7 @@ from streamlink.utils import parse_json
 
 
 class Facebook(Plugin):
-    _url_re = re.compile(r"https?://(?:www\.)?facebook\.com/[^/]+/videos")
+    _url_re = re.compile(r"https?://(?:www\.)?facebook\.com/[^/]+/(posts|videos)")
     _src_re = re.compile(r'''(sd|hd)_src["']?\s*:\s*(?P<quote>["'])(?P<url>.+?)(?P=quote)''')
     _playlist_re = re.compile(r'''video:\[({url:".+?}\])''')
     _plurl_re = re.compile(r'''url:"(.*?)"''')
diff --git a/src/streamlink/plugins/openrectv.py b/src/streamlink/plugins/openrectv.py
index 3bd10a89..6f300691 100644
--- a/src/streamlink/plugins/openrectv.py
+++ b/src/streamlink/plugins/openrectv.py
@@ -1,154 +1,61 @@
-import logging
 import re
 
-from streamlink.plugin import Plugin, PluginArgument, PluginArguments
+from streamlink.plugin import Plugin
 from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream
 from streamlink.utils import parse_json
 
-log = logging.getLogger(__name__)
-
-
-class OPENRECtv(Plugin):
-    _url_re = re.compile(r"https?://(?:www\.)?openrec.tv/(?:live|movie)/(?P<id>[^/]+)")
-    _stores_re = re.compile(r"window.stores\s*=\s*({.*?});", re.DOTALL | re.MULTILINE)
-    _config_re = re.compile(r"window.sharedConfig\s*=\s*({.*?});", re.DOTALL | re.MULTILINE)
-
-    api_url = "https://apiv5.openrec.tv/api/v5/movies/{id}/detail"
-    login_url = "https://www.openrec.tv/viewapp/v4/mobile/user/login"
-
-    _config_schema = validate.Schema({
-        "urls": {
-            "apiv5Authorized": validate.url()
-        }
-    })
-    _stores_schema = validate.Schema({
-        "moviePageStore": {
-            "movieStore": {
-                "id": validate.text,
-                "title": validate.text,
-                "media": {
-                    "url": validate.any(None, '', validate.url())
-                }
-            }
-        }
-    }, validate.get("moviePageStore"), validate.get("movieStore"))
-
-    _detail_schema = validate.Schema({
-        validate.optional("error_message"): validate.text,
-        "status": int,
-        validate.optional("data"): {
-            "type": validate.text,
-            "items": [{
-                "media": {
-                    "url": validate.any(None, validate.url()),
-                    "url_dvr": validate.any(None, validate.url())
-                }
-            }]
-        }
-    })
-
-    _login_schema = validate.Schema({
-        validate.optional("error_message"): validate.text,
-        "status": int,
-        validate.optional("data"): object
-    })
-
-    arguments = PluginArguments(
-        PluginArgument(
-            "email",
-            requires=["password"],
-            metavar="EMAIL",
-            help="""
-            The email associated with your openrectv account,
-            required to access any openrectv stream.
-            """),
-        PluginArgument(
-            "password",
-            sensitive=True,
-            metavar="PASSWORD",
-            help="""
-            An openrectv account password to use with --openrectv-email.
-            """)
+_url_re = re.compile(r"https?://(?:www\.)?openrec.tv/(live|movie)/")
+_playlist_url_re = re.compile(r"data-(source)?file=\"(?P<url>[^\"]+)\"")
+_movie_data_re = re.compile(r'''<script type="application/ld\+json">(.*?)</script>''', re.DOTALL | re.M)
+_live_schema = validate.Schema(
+    validate.transform(_playlist_url_re.findall),
+    [
+        validate.union({
+            "isSource": validate.all(validate.get(0), validate.transform(lambda s: s == "source")),
+            "url": validate.all(validate.get(1), validate.url(scheme="http",
+                                                              path=validate.endswith(".m3u8")))
+        })
+    ]
+)
+_movie_schema = validate.Schema(
+    validate.transform(_movie_data_re.search),
+    validate.any(
+        None,
+        validate.all(
+            validate.get(1),
+            validate.transform(parse_json),
+            validate.get("contentUrl")
+        )
     )
+)
 
-    def __init__(self, url):
-        super(OPENRECtv, self).__init__(url)
-        self._pdata = None
-        self._pres = None
-        self._pconfig = None
 
+class OPENRECtv(Plugin):
     @classmethod
     def can_handle_url(cls, url):
-        return cls._url_re.match(url) is not None
+        return _url_re.match(url) is not None
 
-    def login(self, email, password):
-        res = self.session.http.post(self.login_url, data={"mail": email, "password": password})
-        data = self.session.http.json(res, self._login_schema)
-        if data["status"] == 0:
-            log.debug("Logged in as {0}".format(data["data"]["user_name"]))
-        else:
-            log.error("Failed to login: {0}".format(data["error_message"]))
-        return data["status"] == 0
-
-    def _get_page(self):
-        if not self._pres:
-            self._pres = self.session.http.get(self.url)
-        return self._pres
-
-    def _get_movie_data(self):
-        pres = self._get_page()
-        match = self._stores_re.search(pres.text)
-        if match:
-            self._pdata = parse_json(match.group(1), schema=self._stores_schema)
-
-        return self._pdata
-
-    def _get_page_config(self):
-        pres = self._get_page()
-        match = self._config_re.search(pres.text)
-        if match:
-            self._pconfig = parse_json(match.group(1))
-
-        return self._pconfig
-
-    def _get_details(self, id):
-        config = self._get_page_config()
-        api_url = config["urls"]["apiv5Authorized"]
-        url = "{base}/movies/{id}/detail".format(base=api_url, id=id)
-        res = self.session.http.get(url, headers={
-            "access-token": self.session.http.cookies.get("access_token"),
-            "uuid": self.session.http.cookies.get("uuid")
-        })
-        data = self.session.http.json(res, schema=self._detail_schema)
-
-        if data["status"] == 0:
-            log.debug("Got valid detail response")
-            return data["data"]
-        else:
-            log.error("Failed to get video stream: {0}".format(data["error_message"]))
-
-    def get_title(self):
-        mdata = self._get_movie_data()
-        if mdata:
-            return mdata["title"]
+    @classmethod
+    def stream_weight(cls, stream):
+        if stream == "source":
+            return 1080 + 1, "openrectv"
+        return Plugin.stream_weight(stream)
 
     def _get_streams(self):
-        mdata = self._get_movie_data()
-        if mdata:
-            log.debug("Found video: {0} ({1})".format(mdata["title"], mdata["id"]))
-            if mdata["media"]["url"]:
-                for s in HLSStream.parse_variant_playlist(self.session, mdata["media"]["url"]).items():
+        stype = _url_re.match(self.url).group(1)
+        if stype.lower() == "live":
+            self.logger.debug("Searching the page for live stream URLs")
+            playlists = self.session.http.get(self.url, schema=_live_schema)
+            for playlist in playlists:
+                for q, s in HLSStream.parse_variant_playlist(self.session, playlist["url"]).items():
+                    yield "source" if playlist["isSource"] else q, s
+        elif stype.lower() == "movie":
+            self.logger.debug("Searching the page for VOD stream URLs")
+            playlist = self.session.http.get(self.url, schema=_movie_schema)
+            if playlist:
+                for s in HLSStream.parse_variant_playlist(self.session, playlist).items():
                     yield s
-            elif self.get_option("email") and self.get_option("password"):
-                if self.login(self.get_option("email"), self.get_option("password")):
-                    details = self._get_details(mdata["id"])
-                    if details:
-                        for item in details["items"]:
-                            for s in HLSStream.parse_variant_playlist(self.session, item["media"]["url"]).items():
-                                yield s
-            else:
-                log.error("You must login to access this stream")
 
 
 __plugin__ = OPENRECtv


Test patch:
diff --git a/tests/plugins/test_facebook.py b/tests/plugins/test_facebook.py
index b6b171a1..76e72dff 100644
--- a/tests/plugins/test_facebook.py
+++ b/tests/plugins/test_facebook.py
@@ -9,6 +9,7 @@ class TestPluginFacebook(unittest.TestCase):
         self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/nos/videos/1725546430794241/"))
         self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/nytfood/videos/1485091228202006/"))
         self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/SporTurkTR/videos/798553173631138/"))
+        self.assertTrue(Facebook.can_handle_url("https://www.facebook.com/119555411802156/posts/500665313691162/"))
 
         # shouldn't match
         self.assertFalse(Facebook.can_handle_url("https://www.facebook.com"))