diff --git a/src/livestreamer/plugin/api/__init__.py b/src/livestreamer/plugin/api/__init__.py index 6de049e4cb3..37bb977640b 100644 --- a/src/livestreamer/plugin/api/__init__.py +++ b/src/livestreamer/plugin/api/__init__.py @@ -4,7 +4,9 @@ from types import ModuleType as module -__all__ = ["load_support_plugin"] +from .http_session import HTTPSession + +__all__ = ["HTTPSession", "load_support_plugin", "http"] def load_support_plugin(name): @@ -72,3 +74,4 @@ def __getattr__(self, name): support_plugin_path = "livestreamer.plugin.api.support_plugin" sys.modules[support_plugin_path] = SupportPlugin("support_plugin") +http = None diff --git a/src/livestreamer/plugin/api/http_session.py b/src/livestreamer/plugin/api/http_session.py new file mode 100644 index 00000000000..57a4d3d6467 --- /dev/null +++ b/src/livestreamer/plugin/api/http_session.py @@ -0,0 +1,78 @@ +from requests import Session +from requests.exceptions import RequestException + +from ...exceptions import PluginError +from ...utils import parse_json, parse_xml + +__all__ = ["HTTPSession"] + + +def _parse_keyvalue_list(val): + for keyvalue in val.split(";"): + try: + key, value = keyvalue.split("=") + yield key.strip(), value.strip() + except ValueError: + continue + + +class HTTPSession(Session): + @classmethod + def json(cls, res, *args, **kwargs): + """Parses JSON from a response.""" + return parse_json(res.text, *args, **kwargs) + + @classmethod + def xml(cls, res, *args, **kwargs): + """Parses XML from a response.""" + return parse_xml(res.text, *args, **kwargs) + + def parse_cookies(self, cookies, **kwargs): + """Parses a semi-colon delimited list of cookies. + + Example: foo=bar;baz=qux + """ + for name, value in _parse_keyvalue_list(cookies): + self.cookies.set(name, value, **kwargs) + + def parse_headers(self, headers): + """Parses a semi-colon delimited list of headers. + + Example: foo=bar;baz=qux + """ + for name, value in _parse_keyvalue_list(headers): + self.headers[name] = value + + def parse_query_params(self, cookies, **kwargs): + """Parses a semi-colon delimited list of query parameters. + + Example: foo=bar;baz=qux + """ + for name, value in _parse_keyvalue_list(cookies): + self.params[name] = value + + def request(self, method, url, *args, **kwargs): + exception = kwargs.pop("exception", PluginError) + headers = kwargs.pop("headers", {}) + params = kwargs.pop("params", {}) + session = kwargs.pop("session", None) + timeout = kwargs.pop("timeout", 20) + + if session: + headers.update(session.headers) + params.update(session.params) + + try: + res = Session.request(self, method, url, + headers=headers, + params=params, + timeout=timeout, + *args, **kwargs) + res.raise_for_status() + except (RequestException, IOError) as rerr: + err = exception("Unable to open URL: {url} ({err})".format(url=url, + err=rerr)) + err.err = rerr + raise err + + return res diff --git a/src/livestreamer/session.py b/src/livestreamer/session.py index 2644b3247e6..dd15a9ea1a6 100644 --- a/src/livestreamer/session.py +++ b/src/livestreamer/session.py @@ -8,6 +8,7 @@ from .exceptions import NoPluginError from .logger import Logger from .options import Options +from .plugin import api def print_small_exception(start_after): @@ -35,6 +36,7 @@ class Livestreamer(object): options and log settings.""" def __init__(self): + self.http = api.HTTPSession() self.options = Options({ "rtmpdump": is_win32 and "rtmpdump.exe" or "rtmpdump", "rtmpdump-proxy": None, @@ -157,13 +159,14 @@ def load_plugins(self, path): try: self.load_plugin(name, file, pathname, desc) except Exception: - sys.stderr.write("Failed to load plugin " - "{0}:\n".format(name)) + sys.stderr.write("Failed to load plugin {0}:\n".format(name)) print_small_exception("load_plugin") continue def load_plugin(self, name, file, pathname, desc): + # Set the global http session for this plugin + api.http = self.http module = imp.load_module(name, file, pathname, desc) if hasattr(module, "__plugin__"):