mirror of https://github.com/streamlink/streamlink
137 lines
5.1 KiB
Python
137 lines
5.1 KiB
Python
import time
|
|
|
|
import logging
|
|
import re
|
|
from uuid import uuid4
|
|
|
|
from streamlink.compat import quote
|
|
from streamlink.plugin import Plugin, PluginArguments, PluginArgument
|
|
from streamlink.plugin.api import useragents
|
|
from streamlink.stream import HLSStream
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class BTSports(Plugin):
|
|
url_re = re.compile(r"https?://sport.bt.com")
|
|
|
|
arguments = PluginArguments(
|
|
PluginArgument(
|
|
"email",
|
|
requires=["password"],
|
|
metavar="EMAIL",
|
|
required=True,
|
|
help="""
|
|
The email associated with your BT Sport account, required to access any
|
|
BT Sport stream.
|
|
"""
|
|
|
|
),
|
|
PluginArgument(
|
|
"password",
|
|
sensitive=True,
|
|
metavar="PASSWORD",
|
|
help="Your BT Sport account password."
|
|
)
|
|
)
|
|
|
|
content_re = re.compile(r"CONTENT_(\w+)\s*=\s*'(\w+)'")
|
|
saml_re = re.compile(r'''name="SAMLResponse" value="(.*?)"''', re.M | re.DOTALL)
|
|
api_url = "https://be.avs.bt.com/AVS/besc"
|
|
saml_url = "https://samlfed.bt.com/sportgetfedwebhls"
|
|
login_url = "https://signin1.bt.com/siteminderagent/forms/login.fcc"
|
|
|
|
def __init__(self, url):
|
|
super(BTSports, self).__init__(url)
|
|
self.session.http.headers = {"User-Agent": useragents.FIREFOX}
|
|
|
|
@classmethod
|
|
def can_handle_url(cls, url):
|
|
return cls.url_re.match(url) is not None
|
|
|
|
def login(self, username, password):
|
|
log.debug("Logging in as {0}".format(username))
|
|
|
|
redirect_to = "https://home.bt.com/ss/Satellite/secure/loginforward?view=btsport&redirectURL={0}".format(
|
|
quote(self.url)
|
|
)
|
|
data = {
|
|
"cookieExpp": "30",
|
|
"Switch": "yes",
|
|
"SMPostLoginUrl": "/appsyouraccount/secure/postlogin",
|
|
"loginforward": "https://home.bt.com/ss/Satellite/secure/loginforward?view=btsport",
|
|
"smauthreason": "0",
|
|
"TARGET": redirect_to,
|
|
"USER": username,
|
|
"PASSWORD": password}
|
|
res = self.session.http.post(self.login_url, data=data)
|
|
|
|
log.debug("Redirected to: {0}".format(res.url))
|
|
|
|
if "loginerror" not in res.text:
|
|
self.logger.debug("Login successful, getting SAML token")
|
|
res = self.session.http.get("https://samlfed.bt.com/sportgetfedwebhls?bt.cid={0}".format(self.acid()))
|
|
d = self.saml_re.search(res.text)
|
|
if d:
|
|
saml_data = d.group(1)
|
|
self.logger.debug("BT Sports federated login...")
|
|
res = self.session.http.post(
|
|
self.api_url,
|
|
params={"action": "LoginBT", "channel": "WEBHLS", "bt.cid": self.acid},
|
|
data={"SAMLResponse": saml_data}
|
|
)
|
|
fed_json = self.session.http.json(res)
|
|
success = fed_json['resultCode'] == "OK"
|
|
if not success:
|
|
self.logger.error("Failed to login: {0} - {1}".format(fed_json['errorDescription'],
|
|
fed_json['message']))
|
|
return success
|
|
else:
|
|
return False
|
|
|
|
def device_id(self):
|
|
device_id = self.cache.get("device_id") or str(uuid4())
|
|
self.cache.set("device_id", device_id)
|
|
return device_id
|
|
|
|
def acid(self):
|
|
acid = self.cache.get("acid") or "{cid}-B-{timestamp}".format(cid=self.device_id(), timestamp=int(time.time()))
|
|
self.cache.set("acid", acid)
|
|
return acid
|
|
|
|
def _get_cdn(self, channel_id, channel_type="LIVE"):
|
|
d = {"action": "GetCDN",
|
|
"type": channel_type,
|
|
"id": channel_id,
|
|
"channel": "WEBHLS",
|
|
"asJson": "Y",
|
|
"bt.cid": self.acid(),
|
|
"_": int(time.time())}
|
|
|
|
res = self.session.http.get(self.api_url, params=d, headers={"Accept": "application/json"})
|
|
return self.session.http.json(res)
|
|
|
|
def _get_streams(self):
|
|
if self.options.get("email") and self.options.get("password"):
|
|
if self.login(self.options.get("email"), self.options.get("password")):
|
|
log.debug("Logged in and authenticated with BT Sports.")
|
|
|
|
res = self.session.http.get(self.url)
|
|
m = self.content_re.findall(res.text)
|
|
if m:
|
|
info = dict(m)
|
|
data = self._get_cdn(info.get("ID"), info.get("TYPE"))
|
|
log.debug("CDN respsonse: {0}".format(data))
|
|
if data['resultCode'] == 'OK':
|
|
return HLSStream.parse_variant_playlist(self.session, data['resultObj']['src'])
|
|
else:
|
|
log.error("Failed to get stream with error: {0} - {1}".format(data['errorDescription'],
|
|
data['message']))
|
|
else:
|
|
log.error("Login failed.")
|
|
else:
|
|
log.error("A username and password is required to use BT Sports")
|
|
|
|
|
|
__plugin__ = BTSports
|