예제 #1
0
    def run(self, path=None):
        path = self.kwargs.get('path', path)
        if not path and self.request.method == 'GET':
            yield self.login()
            raise tornado.gen.Return()

        args = {key: val[0] for key, val in self.args.items()}
        params = AttrDict(self.kwargs)
        params['access_key'] = self.get_token('access_key',
                                              self.get_from_token)
        params['access_secret'] = self.get_token('access_secret',
                                                 self.get_from_token)

        client = oauth1.Client(client_key=params['key'],
                               client_secret=params['secret'],
                               resource_owner_key=params['access_key'],
                               resource_owner_secret=params['access_secret'])
        endpoint = params.get('endpoint', 'https://api.twitter.com/1.1/')
        path = params.get('path', path)
        uri, headers, body = client.sign(url_concat(endpoint + path, args))
        http = self.get_auth_http_client()
        response = yield http.fetch(uri, headers=headers, raise_error=False)
        result = yield self.social_response(response)
        self.set_header('Content-Type', 'application/json; charset=UTF-8')
        self.write(result)
예제 #2
0
 def setup_auth(cls, auth):
     # auth: if there's no auth: in handler, default to app.auth
     if auth is None:
         auth = conf.app.get('auth')
     # Treat True as an empty dict, i.e. auth: {}
     if auth is True:
         auth = AttrDict()
     # Set up the auth
     if isinstance(auth, dict):
         cls._auth = auth
         cls._on_init_methods.append(cls.authorize)
         cls.permissions = []
         # Add check for condition
         if auth.get('condition'):
             cls.permissions.append(
                 build_transform(auth['condition'], vars=AttrDict(handler=None),
                                 filename='url:%s.auth.permission' % cls.name))
         # Add check for membership
         memberships = auth.get('membership', [])
         if not isinstance(memberships, list):
             memberships = [memberships]
         if len(memberships):
             cls.permissions.append(check_membership(memberships))
     elif auth:
         app_log.error('url:%s.auth is not a dict', cls.name)
예제 #3
0
def load():

    state.PROGRAMS = Tree()

    # Add configured players

    for pcls in [Player, Downloader, Postprocessor, ShellCommand]:

        ptype = camel_to_snake(pcls.__name__)
        cfgkey = ptype + "s"
        for name, cfg in config.settings.profile[cfgkey].items():
            if not cfg:
                cfg = AttrDict()
            path = cfg.pop("path", None) or cfg.get(
                "command",
                distutils.spawn.find_executable(name)
            )
            if not path:
                logger.warning(f"couldn't find command for {name}")
                continue
            # First, try to find by "type" config value, if present
            try:
                klass = next(
                    c for c in Program.SUBCLASSES[ptype].values()
                    if c.__name__.lower().replace(ptype, "")
                    == cfg.get("type", "").replace("-", "").lower()
                )
            except StopIteration:
                # Next, try to find by config name matching class name
                try:
                    klass = next(
                        c for c in Program.SUBCLASSES[ptype].values()
                        if c.cmd == name
                    )
                except StopIteration:
                    # Give up and make it a generic program
                    klass = pcls
            if cfg.get("disabled") == True:
                continue
            state.PROGRAMS[ptype][name] = ProgramDef(
                cls=klass,
                name=name,
                path=path,
                cfg = AttrDict(cfg)
            )
    # Try to find any players not configured
    for ptype in Program.SUBCLASSES.keys():
        cfgkey = ptype + "s"
        for name, klass in Program.SUBCLASSES[ptype].items():
            cfg = config.settings.profile[cfgkey][name]
            if name in state.PROGRAMS[ptype] or (cfg and cfg.disabled == True):
                continue
            path = distutils.spawn.find_executable(name)
            if path:
                state.PROGRAMS[ptype][name] = ProgramDef(
                    cls=klass,
                    name=name,
                    path=path,
                    cfg = AttrDict()
                )
예제 #4
0
    def setup_error(cls, error):
        '''
        Sample configuration::

            error:
                404:
                    path: template.json         # Use a template
                    autoescape: false           # with no autoescape
                    whitespace: single          # as a single line
                    headers:
                        Content-Type: application/json
                500:
                    function: module.fn
                    args: [=status_code, =kwargs, =handler]
        '''
        if not error:
            return
        if not isinstance(error, dict):
            return app_log.error('url:%s.error is not a dict', cls.name)
        # Compile all errors handlers
        cls.error = {}
        for error_code, error_config in error.items():
            try:
                error_code = int(error_code)
                if error_code < 100 or error_code > 1000:
                    raise ValueError()
            except ValueError:
                app_log.error('url.%s.error code %s is not a number (100 - 1000)',
                              cls.name, error_code)
                continue
            if not isinstance(error_config, dict):
                return app_log.error('url:%s.error.%d is not a dict', cls.name, error_code)
            # Make a copy of the original. When we add headers, etc, it shouldn't affect original
            error_config = AttrDict(error_config)
            error_path, error_function = error_config.get('path'), error_config.get('function')
            if error_function:
                if error_path:
                    error_config.pop('path')
                    app_log.warning('url.%s.error.%d has function: AND path:. Ignoring path:',
                                    cls.name, error_code)
                cls.error[error_code] = {'function': build_transform(
                    error_config,
                    vars=AttrDict((('status_code', None), ('kwargs', None), ('handler', None))),
                    filename='url:%s.error.%d' % (cls.name, error_code)
                )}
            elif error_path:
                encoding = error_config.get('encoding', 'utf-8')
                cls.error[error_code] = {'function': cls._error_fn(error_code, error_config)}
                mime_type, encoding = mimetypes.guess_type(error_path, strict=False)
                if mime_type:
                    error_config.setdefault('headers', {}).setdefault('Content-Type', mime_type)
            else:
                app_log.error('url.%s.error.%d must have a path or function key',
                              cls.name, error_code)
            # Add the error configuration for reference
            if error_code in cls.error:
                cls.error[error_code]['conf'] = error_config
        cls._write_error, cls.write_error = cls.write_error, cls._write_custom_error
예제 #5
0
파일: install.py 프로젝트: vinothsm/gramex
def get_app_config(appname, args):
    '''
    Get the stored configuration for appname, and override it with args.
    ``.target`` defaults to $GRAMEXDATA/apps/<appname>.
    '''
    apps_config['cmd'] = {appname: args}
    app_config = AttrDict((+apps_config).get(appname, {}))
    app_config.setdefault('target', str(app_dir / app_config.get('target', appname)))
    app_config.target = os.path.abspath(app_config.target)
    return app_config
예제 #6
0
    def load(cls):

        global PROGRAMS

        # Add configured players

        for ptype in [Player, Helper, Downloader]:
            cfgkey = ptype.__name__.lower() + "s"
            for name, cfg in config.settings.profile[cfgkey].items():
                if not cfg:
                    cfg = AttrDict()
                path = cfg.pop("path", None) or cfg.get(
                    "command", distutils.spawn.find_executable(name))
                try:
                    # raise Exception(cls.SUBCLASSES[ptype])
                    klass = next(c for c in cls.SUBCLASSES[ptype].values()
                                 if c.cmd == name)
                except StopIteration:
                    klass = ptype
                if cfg.get("disabled") == True:
                    logger.info(f"player {name} is disabled")
                    continue
                PROGRAMS[ptype][name] = ProgramDef(cls=klass,
                                                   name=name,
                                                   path=path,
                                                   cfg=AttrDict(cfg))
        # Try to find any players not configured
        for ptype in cls.SUBCLASSES.keys():
            cfgkey = ptype.__name__.lower() + "s"
            for name, klass in cls.SUBCLASSES[ptype].items():
                cfg = config.settings.profile[cfgkey][name]
                if name in PROGRAMS[ptype] or cfg.disabled == True:
                    continue
                path = distutils.spawn.find_executable(name)
                if path:
                    PROGRAMS[ptype][name] = ProgramDef(cls=klass,
                                                       name=name,
                                                       path=path,
                                                       cfg=AttrDict())
예제 #7
0
def url(conf):
    '''Set up the tornado web app URL handlers'''
    handlers = []
    # Sort the handlers in descending order of priority
    specs = sorted(conf.items(), key=_sort_url_patterns, reverse=True)
    for name, spec in specs:
        _key = cache_key('url', spec)
        if _key in _cache:
            handlers.append(_cache[_key])
            continue
        if 'handler' not in spec:
            app_log.error('url: %s: no handler specified')
            continue
        app_log.debug('url: %s (%s) %s', name, spec.handler,
                      spec.get('priority', ''))
        urlspec = AttrDict(spec)
        handler = locate(spec.handler, modules=['gramex.handlers'])
        if handler is None:
            app_log.error('url: %s: ignoring missing handler %s', name,
                          spec.handler)
            continue

        # Create a subclass of the handler with additional attributes.
        class_vars = {'name': name, 'conf': spec}
        # If there's a cache section, get the cache method for use by BaseHandler
        if 'cache' in urlspec:
            class_vars['cache'] = _cache_generator(urlspec['cache'], name=name)
        else:
            class_vars['cache'] = None
        # PY27 type() requires the class name to be a string, not unicode
        urlspec.handler = type(str(spec.handler), (handler, ), class_vars)

        # If there's a setup method, call it to initialize the class
        kwargs = urlspec.get('kwargs', {})
        if hasattr(handler, 'setup'):
            try:
                urlspec.handler.setup_default_kwargs()
                urlspec.handler.setup(**kwargs)
            except Exception:
                app_log.exception('url: %s: setup exception in handler %s',
                                  name, spec.handler)
                # Since we can't set up the handler, all requests must report the error instead
                class_vars['exc_info'] = sys.exc_info()
                error_handler = locate('SetupFailedHandler',
                                       modules=['gramex.handlers'])
                urlspec.handler = type(str(spec.handler), (error_handler, ),
                                       class_vars)
                urlspec.handler.setup(**kwargs)

        try:
            handler_entry = tornado.web.URLSpec(
                name=name,
                pattern=_url_normalize(urlspec.pattern),
                handler=urlspec.handler,
                kwargs=kwargs,
            )
        except re.error:
            app_log.error('url: %s: pattern: %s is invalid', name,
                          urlspec.pattern)
            continue
        except Exception:
            app_log.exception('url: %s: invalid', name)
            continue
        _cache[_key] = handler_entry
        handlers.append(handler_entry)

    info.app.clear_handlers()
    info.app.add_handlers('.*$', handlers)
예제 #8
0
파일: mlb.py 프로젝트: tonycpsu/streamglob
    def milestones(self):

        try:
            # try to get the precise timestamps for this stream
            airing = next(
                a for a in self.provider.session.airings(self.game_id)
                if len(a["milestones"]) and a["mediaId"] == self.media_id)
        except StopIteration:
            # welp, no timestamps -- try to get them from whatever feed has them
            try:
                airing = next(
                    a for a in self.provider.session.airings(self.game_id)
                    if len(a["milestones"]))
            except StopIteration:
                logger.warning(
                    SGStreamSessionException("No airing for media %s" %
                                             (self.media_id)))
                return AttrDict([("Start", 0)])

        start_timestamps = []
        try:
            start_time = next(t["startDatetime"] for t in next(
                m for m in airing["milestones"]
                if m["milestoneType"] == "BROADCAST_START")["milestoneTime"]
                              if t["type"] == "absolute")

        except StopIteration:
            # Some streams don't have a "BROADCAST_START" milestone.  We need
            # something, so we use the scheduled game start time, which is
            # probably wrong.
            start_time = airing["startDate"]

        # start_timestamps.append(
        #     ("Start", start_time)
        # )

        try:
            start_offset = next(t["start"] for t in next(
                m for m in airing["milestones"]
                if m["milestoneType"] == "BROADCAST_START")["milestoneTime"]
                                if t["type"] == "offset")
        except StopIteration:
            # Same as above.  Missing BROADCAST_START milestone means we
            # probably don't get accurate offsets for inning milestones.
            start_offset = 0

        start_timestamps.append(("Start", start_offset))

        timestamps = AttrDict(start_timestamps)
        timestamps.update(
            AttrDict([("%s%s" % ("T" if next(
                k for k in m["keywords"]
                if k["type"] == "top")["value"] == "true" else "B",
                                 int(
                                     next(k for k in m["keywords"]
                                          if k["type"] == "inning")["value"])),
                       next(t["start"] for t in m["milestoneTime"]
                            if t["type"] == "offset"))
                      for m in airing["milestones"]
                      if m["milestoneType"] == "INNING_START"]))

        # If we didn't get a BROADCAST_START timestamp but did get a timestamp
        # for the first inning, just use something reasonable (1st inning start
        # minus 15 minutes.)
        if timestamps.get("Start") == 0 and "T1" in timestamps:
            timestamps["Start"] = timestamps["T1"] - 900
        timestamps.update([("Live", None)])

        return timestamps
예제 #9
0
class MLBSession(object):

    HEADERS = {"User-agent": USER_AGENT}

    def __init__(
        self,
        username,
        password,
        api_key=None,
        client_api_key=None,
        token=None,
        access_token=None,
    ):

        self.session = requests.Session()
        self.session.cookies = LWPCookieJar()
        if not os.path.exists(COOKIE_FILE):
            self.session.cookies.save(COOKIE_FILE)
        self.session.cookies.load(COOKIE_FILE, ignore_discard=True)
        self.session.headers = self.HEADERS
        self._state = AttrDict([
            ("username", username),
            ("password", password),
            ("api_key", api_key),
            ("client_api_key", client_api_key),
            ("token", token),
            ("access_token", access_token),
        ])
        self.login()

    def __getattr__(self, attr):
        if attr in [
                "delete", "get", "head", "options", "post", "put", "patch"
        ]:
            return getattr(self.session, attr)
        raise AttributeError(attr)

    @property
    def username(self):
        return self._state.username

    @property
    def password(self):
        return self._state.password

    @classmethod
    def new(cls):
        try:
            return cls.load()
        except:
            return cls(username=config.settings.username,
                       password=config.settings.password)

    @classmethod
    def destroy(cls):
        if os.path.exists(COOKIE_FILE):
            os.remove(COOKIE_FILE)
        if os.path.exists(SESSION_FILE):
            os.remove(SESSION_FILE)

    @classmethod
    def load(cls):
        state = yaml.load(open(SESSION_FILE), Loader=AttrDictYAMLLoader)
        return cls(**state)

    def save(self):
        with open(SESSION_FILE, 'w') as outfile:
            yaml.dump(self._state, outfile, default_flow_style=False)
        self.session.cookies.save(COOKIE_FILE)

    def login(self):

        logger.debug("logging in")

        initial_url = ("https://secure.mlb.com/enterworkflow.do"
                       "?flowId=registration.wizard&c_id=mlb")

        # res = self.session.get(initial_url)
        # if not res.status_code == 200:
        #     raise MLBSessionException(res.content)

        data = {
            "uri": "/account/login_register.jsp",
            "registrationAction": "identify",
            "emailAddress": self.username,
            "password": self.password,
            "submitButton": ""
        }
        if self.logged_in:
            logger.debug("already logged in")
            return
        logger.debug("logging in")

        login_url = "https://securea.mlb.com/authenticate.do"

        res = self.session.post(login_url,
                                data=data,
                                headers={"Referer": (initial_url)})

        if not (self.ipid and self.fingerprint):
            raise MLBSessionException("Couldn't get ipid / fingerprint")

        logger.debug("logged in: %s" % (self.ipid))
        self.save()

    @property
    def logged_in(self):

        logged_in_url = ("https://web-secure.mlb.com/enterworkflow.do"
                         "?flowId=registration.newsletter&c_id=mlb")
        content = self.session.get(logged_in_url).text
        parser = lxml.etree.HTMLParser()
        data = lxml.etree.parse(StringIO(content), parser)
        if "Login/Register" in data.xpath(".//title")[0].text:
            return False

    def get_cookie(self, name):
        return requests.utils.dict_from_cookiejar(
            self.session.cookies).get(name)

    @property
    def ipid(self):
        return self.get_cookie("ipid")

    @property
    def fingerprint(self):
        return self.get_cookie("fprt")

    @property
    def api_key(self):

        if not self._state.get("api_key"):
            self.update_api_keys()
        return self._state.api_key

    @property
    def client_api_key(self):

        if not self._state.get("client_api_key"):
            self.update_api_keys()
        return self._state.client_api_key

    def update_api_keys(self):

        logger.debug("updating api keys")
        content = self.session.get("https://www.mlb.com/tv/g490865/").text
        parser = lxml.etree.HTMLParser()
        data = lxml.etree.parse(StringIO(content), parser)

        scripts = data.xpath(".//script")
        for script in scripts:
            if script.text and "apiKey" in script.text:
                self._state.api_key = API_KEY_RE.search(
                    script.text).groups()[0]
            if script.text and "clientApiKey" in script.text:
                self._state.client_api_key = CLIENT_API_KEY_RE.search(
                    script.text).groups()[0]
        self.save()

    @property
    def token(self):
        logger.debug("getting token")
        if not self._state.token:
            headers = {"x-api-key": self.api_key}

            response = self.session.get(TOKEN_URL_TEMPLATE.format(
                ipid=self.ipid,
                fingerprint=self.fingerprint,
                platform=PLATFORM),
                                        headers=headers)
            self._state.token = response.text
        return self._state.token

    @property
    def access_token(self):
        logger.debug("getting access token")
        if not self._state.access_token:
            headers = {
                "Authorization": "Bearer %s" % (self.client_api_key),
                "User-agent": USER_AGENT,
                "Accept": "application/vnd.media-service+json; version=1",
                "x-bamsdk-version": BAM_SDK_VERSION,
                "x-bamsdk-platform": PLATFORM,
                "origin": "https://www.mlb.com"
            }

            data = {
                "grant_type":
                "urn:ietf:params:oauth:grant-type:token-exchange",
                "platform": "browser",
                "setCookie": "false",
                "subject_token": self.token,
                "subject_token_type": "urn:ietf:params:oauth:token-type:jwt"
            }
            response = self._access_token = self.session.post(ACCESS_TOKEN_URL,
                                                              data=data,
                                                              headers=headers)
            token_response = response.json()
            self._state.access_token = token_response["access_token"]

        self.save()
        logger.debug("access_token: %s" % (self._state.access_token))
        return self._state.access_token

    def content(self, game_id):

        return self.session.get(
            GAME_CONTENT_URL_TEMPLATE.format(game_id=game_id)).json()

    # def feed(self, game_id):

    #     return self.session.get(GAME_FEED_URL.format(game_id=game_id)).json()

    @memo(region="short")
    def schedule(
        self,
        sport_id=None,
        start=None,
        end=None,
        game_type=None,
        team_id=None,
        game_id=None,
    ):

        logger.debug("getting schedule: %s, %s, %s, %s, %s, %s" %
                     (sport_id, start, end, game_type, team_id, game_id))
        url = SCHEDULE_TEMPLATE.format(
            sport_id=sport_id if sport_id else "",
            start=start.strftime("%Y-%m-%d") if start else "",
            end=end.strftime("%Y-%m-%d") if end else "",
            game_type=game_type if game_type else "",
            team_id=team_id if team_id else "",
            game_id=game_id if game_id else "")
        return self.session.get(url).json()

    @memo(region="short")
    def get_media(self, game_id, title="MLBTV", preferred_stream=None):

        logger.debug("geting media for game %d" % (game_id))
        schedule = self.schedule(game_id=game_id)
        # raise Exception(schedule)
        try:
            game = schedule["dates"][0]["games"][0]
        except KeyError:
            logger.debug("no game data")
            return
        for epg in game["content"]["media"]["epg"]:
            if title in [None, epg["title"]]:
                for item in epg["items"]:
                    if preferred_stream in [None, item["mediaFeedType"]]:
                        logger.debug("found preferred stream")
                        yield item
                else:
                    if len(epg["items"]):
                        logger.debug("using non-preferred stream")
                        yield epg["items"][0]
        # raise StopIteration

    def get_stream(self, media_id):

        # try:
        #     media = next(self.get_media(game_id))
        # except StopIteration:
        #     logger.debug("no media for stream")
        #     return
        # media_id = media["mediaId"]

        headers = {
            "Authorization": self.access_token,
            "User-agent": USER_AGENT,
            "Accept": "application/vnd.media-service+json; version=1",
            "x-bamsdk-version": "3.0",
            "x-bamsdk-platform": PLATFORM,
            "origin": "https://www.mlb.com"
        }
        stream = self.session.get(
            STREAM_URL_TEMPLATE.format(media_id=media_id),
            headers=headers).json()
        logger.debug("stream response: %s" % (stream))
        if "errors" in stream and len(stream["errors"]):
            return None
        return stream
예제 #10
0
class MLBSession(object):

    HEADERS = {"User-agent": USER_AGENT}

    def __init__(
        self,
        username,
        password,
        api_key=None,
        client_api_key=None,
        token=None,
        access_token=None,
        access_token_expiry=None,
        proxies=None,
        no_cache=False,
    ):

        self.session = requests.Session()
        self.session.cookies = LWPCookieJar()
        if not os.path.exists(COOKIE_FILE):
            self.session.cookies.save(COOKIE_FILE)
        self.session.cookies.load(COOKIE_FILE, ignore_discard=True)
        self.session.headers = self.HEADERS
        self._state = AttrDict([("username", username), ("password", password),
                                ("api_key", api_key),
                                ("client_api_key", client_api_key),
                                ("token", token),
                                ("access_token", access_token),
                                ("access_token_expiry", access_token_expiry),
                                ("proxies", proxies)])
        self.no_cache = no_cache
        self._cache_responses = False
        if not os.path.exists(CACHE_FILE):
            self.cache_setup(CACHE_FILE)
        self.conn = sqlite3.connect(CACHE_FILE,
                                    detect_types=sqlite3.PARSE_DECLTYPES)
        self.cursor = self.conn.cursor()
        self.cache_purge()
        self.login()

    def __getattr__(self, attr):
        if attr in [
                "delete", "get", "head", "options", "post", "put", "patch"
        ]:
            # return getattr(self.session, attr)
            session_method = getattr(self.session, attr)
            return functools.partial(self.request, session_method)
        # raise AttributeError(attr)

    def request(self, method, url, *args, **kwargs):

        response = None
        use_cache = not self.no_cache and self._cache_responses
        if use_cache:
            logger.debug("getting cached response for %s" % (url))
            self.cursor.execute(
                "SELECT response, last_seen "
                "FROM response_cache "
                "WHERE url = ?", (url, ))
            try:
                (pickled_response, last_seen) = self.cursor.fetchone()
                td = datetime.now() - last_seen
                if td.seconds >= self._cache_responses:
                    logger.debug("cache expired for %s" % (url))
                else:
                    response = pickle.loads(pickled_response)
                    logger.debug("using cached response for %s" % (url))
            except TypeError:
                logger.debug("no cached response for %s" % (url))

        if not response:
            response = method(url, *args, **kwargs)

        if use_cache:
            pickled_response = pickle.dumps(response)
            sql = """INSERT OR REPLACE
            INTO response_cache (url, response, last_seen)
            VALUES (?, ?, ?)"""
            self.cursor.execute(sql, (url, pickled_response, datetime.now()))
            self.conn.commit()

        return response

    @property
    def username(self):
        return self._state.username

    @property
    def password(self):
        return self._state.password

    @property
    def proxies(self):
        return self._state.proxies

    @proxies.setter
    def proxies(self, value):
        # Override proxy environment variables if proxies are defined on session
        self.session.trust_env = (len(value) == 0)
        self._state.proxies = value
        self.session.proxies.update(value)

    @classmethod
    def new(cls, **kwargs):
        try:
            return cls.load()
        except:
            return cls(username=config.settings.profile.username,
                       password=config.settings.profile.password,
                       **kwargs)

    @classmethod
    def destroy(cls):
        if os.path.exists(COOKIE_FILE):
            os.remove(COOKIE_FILE)
        if os.path.exists(SESSION_FILE):
            os.remove(SESSION_FILE)

    @classmethod
    def load(cls):
        state = yaml.load(open(SESSION_FILE), Loader=AttrDictYAMLLoader)
        return cls(**state)

    def save(self):
        with open(SESSION_FILE, 'w') as outfile:
            yaml.dump(self._state, outfile, default_flow_style=False)
        self.session.cookies.save(COOKIE_FILE)

    @contextmanager
    def cache_responses(self, duration=CACHE_DURATION_DEFAULT):
        self._cache_responses = duration
        try:
            yield
        finally:
            self._cache_responses = False

    def cache_responses_short(self):
        return self.cache_responses(CACHE_DURATION_SHORT)

    def cache_responses_medium(self):
        return self.cache_responses(CACHE_DURATION_MEDIUM)

    def cache_responses_long(self):
        return self.cache_responses(CACHE_DURATION_LONG)

    def cache_setup(self, dbfile):

        conn = sqlite3.connect(dbfile)
        c = conn.cursor()
        c.execute('''
        CREATE TABLE response_cache
        (url TEXT,
        response TEXT,
        last_seen TIMESTAMP DEFAULT (datetime('now','localtime')),
        PRIMARY KEY (url))''')
        conn.commit()
        c.close()

    def cache_purge(self, days=CACHE_DURATION_LONG):

        self.cursor.execute("DELETE "
                            "FROM response_cache "
                            "WHERE last_seen < datetime('now', '-%d days')" %
                            (days))

    def login(self):

        logger.debug("checking for existing log in")

        initial_url = ("https://secure.mlb.com/enterworkflow.do"
                       "?flowId=registration.wizard&c_id=mlb")

        # res = self.get(initial_url)
        # if not res.status_code == 200:
        #     raise MLBSessionException(res.content)

        data = {
            "uri": "/account/login_register.jsp",
            "registrationAction": "identify",
            "emailAddress": self.username,
            "password": self.password,
            "submitButton": ""
        }
        if self.logged_in:
            logger.debug("already logged in")
            return

        logger.debug("attempting new log in")

        login_url = "https://securea.mlb.com/authenticate.do"

        res = self.post(login_url,
                        data=data,
                        headers={"Referer": (initial_url)})

        if not (self.ipid and self.fingerprint):
            raise MLBSessionException("Couldn't get ipid / fingerprint")

        logger.debug("logged in: %s" % (self.ipid))
        self.save()

    @property
    def logged_in(self):

        logged_in_url = ("https://web-secure.mlb.com/enterworkflow.do"
                         "?flowId=registration.newsletter&c_id=mlb")
        content = self.get(logged_in_url).text
        parser = lxml.etree.HTMLParser()
        data = lxml.etree.parse(StringIO(content), parser)
        if "Login/Register" in data.xpath(".//title")[0].text:
            return False

    def get_cookie(self, name):
        return requests.utils.dict_from_cookiejar(
            self.session.cookies).get(name)

    @property
    def ipid(self):
        return self.get_cookie("ipid")

    @property
    def fingerprint(self):
        return self.get_cookie("fprt")

    @property
    def api_key(self):

        if not self._state.get("api_key"):
            self.update_api_keys()
        return self._state.api_key

    @property
    def client_api_key(self):

        if not self._state.get("client_api_key"):
            self.update_api_keys()
        return self._state.client_api_key

    def update_api_keys(self):

        logger.debug("updating api keys")
        content = self.get("https://www.mlb.com/tv/g490865/").text
        parser = lxml.etree.HTMLParser()
        data = lxml.etree.parse(StringIO(content), parser)

        scripts = data.xpath(".//script")
        for script in scripts:
            if script.text and "apiKey" in script.text:
                self._state.api_key = API_KEY_RE.search(
                    script.text).groups()[0]
            if script.text and "clientApiKey" in script.text:
                self._state.client_api_key = CLIENT_API_KEY_RE.search(
                    script.text).groups()[0]
        self.save()

    @property
    def token(self):
        logger.debug("getting token")
        if not self._state.token:
            headers = {"x-api-key": self.api_key}

            response = self.get(TOKEN_URL_TEMPLATE.format(
                ipid=self.ipid,
                fingerprint=self.fingerprint,
                platform=PLATFORM),
                                headers=headers)
            self._state.token = response.text
        return self._state.token

    @token.setter
    def token(self, value):
        self._state.token = value

    @property
    def access_token_expiry(self):

        if self._state.access_token_expiry:
            return dateutil.parser.parse(self._state.access_token_expiry)

    @access_token_expiry.setter
    def access_token_expiry(self, val):
        if val:
            self._state.access_token_expiry = val.isoformat()

    @property
    def access_token(self):
        if not self._state.access_token or not self.access_token_expiry or \
                self.access_token_expiry < datetime.now(tz=pytz.UTC):
            try:
                self.refresh_access_token()
            except requests.exceptions.HTTPError:
                # Clear token and then try to get a new access_token
                self.refresh_access_token(clear_token=True)

        logger.debug("access_token: %s" % (self._state.access_token))
        return self._state.access_token

    def refresh_access_token(self, clear_token=False):
        logger.debug("refreshing access token")
        if clear_token:
            self.token = None
        headers = {
            "Authorization": "Bearer %s" % (self.client_api_key),
            "User-agent": USER_AGENT,
            "Accept": "application/vnd.media-service+json; version=1",
            "x-bamsdk-version": BAM_SDK_VERSION,
            "x-bamsdk-platform": PLATFORM,
            "origin": "https://www.mlb.com"
        }
        data = {
            "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
            "platform": "browser",
            "setCookie": "false",
            "subject_token": self.token,
            "subject_token_type": "urn:ietf:params:oauth:token-type:jwt"
        }
        response = self.post(ACCESS_TOKEN_URL, data=data, headers=headers)
        response.raise_for_status()
        token_response = response.json()

        self.access_token_expiry = datetime.now(tz=pytz.UTC) + \
                       timedelta(seconds=token_response["expires_in"])
        self._state.access_token = token_response["access_token"]
        self.save()

    def content(self, game_id):

        return self.get(
            GAME_CONTENT_URL_TEMPLATE.format(game_id=game_id)).json()

    # def feed(self, game_id):

    #     return self.get(GAME_FEED_URL.format(game_id=game_id)).json()

    @memo(region="short")
    def schedule(
        self,
        sport_id=None,
        start=None,
        end=None,
        game_type=None,
        team_id=None,
        game_id=None,
    ):

        logger.debug("getting schedule: %s, %s, %s, %s, %s, %s" %
                     (sport_id, start, end, game_type, team_id, game_id))
        url = SCHEDULE_TEMPLATE.format(
            sport_id=sport_id if sport_id else "",
            start=start.strftime("%Y-%m-%d") if start else "",
            end=end.strftime("%Y-%m-%d") if end else "",
            game_type=game_type if game_type else "",
            team_id=team_id if team_id else "",
            game_id=game_id if game_id else "")
        with self.cache_responses_short():
            return self.get(url).json()

    @memo(region="short")
    def get_epgs(self, game_id, title="MLBTV"):
        schedule = self.schedule(game_id=game_id)
        try:
            # Get last date for games that have been rescheduled to a later date
            game = schedule["dates"][-1]["games"][0]
        except KeyError:
            logger.debug("no game data")
            return
        epgs = game["content"]["media"]["epg"]

        if not isinstance(epgs, list):
            epgs = [epgs]

        return [e for e in epgs if (not title) or title == e["title"]]

    def get_media(self,
                  game_id,
                  media_id=None,
                  title="MLBTV",
                  preferred_stream=None,
                  call_letters=None):

        logger.debug("geting media for game %d" % (game_id))

        epgs = self.get_epgs(game_id, title)
        for epg in epgs:
            for item in epg["items"]:
                if (not preferred_stream or
                    (item.get("mediaFeedType", "").lower()
                     == preferred_stream)) and (not call_letters or (item.get(
                         "callLetters", "").lower() == call_letters)) and (
                             not media_id or
                             (item.get("mediaId", "").lower() == media_id)):
                    logger.debug("found preferred stream")
                    yield item
            else:
                if len(epg["items"]):
                    logger.debug("using non-preferred stream")
                    yield epg["items"][0]
        # raise StopIteration

    def airings(self, game_id):

        airings_url = AIRINGS_URL_TEMPLATE.format(game_id=game_id)
        airings = self.get(airings_url).json()["data"]["Airings"]
        return airings

    def media_timestamps(self, game_id, media_id):

        try:
            airing = next(a for a in self.airings(game_id)
                          if a["mediaId"] == media_id)
        except StopIteration:
            raise MLBSessionException("No airing for media %s" % (media_id))

        start_timestamps = []
        try:
            start_time = next(t["startDatetime"] for t in next(
                m for m in airing["milestones"]
                if m["milestoneType"] == "BROADCAST_START")["milestoneTime"]
                              if t["type"] == "absolute")

        except StopIteration:
            # Some streams don't have a "BROADCAST_START" milestone.  We need
            # something, so we use the scheduled game start time, which is
            # probably wrong.
            start_time = airing["startDate"]

        start_timestamps.append(("S", start_time))

        try:
            start_offset = next(t["start"] for t in next(
                m for m in airing["milestones"]
                if m["milestoneType"] == "BROADCAST_START")["milestoneTime"]
                                if t["type"] == "offset")
        except StopIteration:
            # Same as above.  Missing BROADCAST_START milestone means we
            # probably don't get accurate offsets for inning milestones.
            start_offset = 0

        start_timestamps.append(("SO", start_offset))

        timestamps = AttrDict(start_timestamps)
        timestamps.update(
            AttrDict([("%s%s" % ("T" if next(
                k for k in m["keywords"]
                if k["type"] == "top")["value"] == "true" else "B",
                                 int(
                                     next(k for k in m["keywords"]
                                          if k["type"] == "inning")["value"])),
                       next(t["start"] for t in m["milestoneTime"]
                            if t["type"] == "offset"))
                      for m in airing["milestones"]
                      if m["milestoneType"] == "INNING_START"]))
        return timestamps

    def get_stream(self, media_id):

        # try:
        #     media = next(self.get_media(game_id))
        # except StopIteration:
        #     logger.debug("no media for stream")
        #     return
        # media_id = media["mediaId"]

        headers = {
            "Authorization": self.access_token,
            "User-agent": USER_AGENT,
            "Accept": "application/vnd.media-service+json; version=1",
            "x-bamsdk-version": "3.0",
            "x-bamsdk-platform": PLATFORM,
            "origin": "https://www.mlb.com"
        }
        stream_url = STREAM_URL_TEMPLATE.format(media_id=media_id)
        logger.info("getting stream %s" % (stream_url))
        stream = self.get(stream_url, headers=headers).json()
        logger.debug("stream response: %s" % (stream))
        if "errors" in stream and len(stream["errors"]):
            return None
        return stream
예제 #11
0
class Config(MutableMapping):
    def __init__(self, config_file):

        self._config = None
        self._config_file = config_file

    def init_config(self):

        from .session import MLBSession, MLBSessionException

        def mkdir_p(path):
            try:
                os.makedirs(path)
            except OSError as exc:  # Python >2.5
                if not (exc.errno == errno.EEXIST and os.path.isdir(path)):
                    raise

        def find_players():
            for p in KNOWN_PLAYERS:
                player = distutils.spawn.find_executable(p)
                if player:
                    yield player

        MLBSession.destroy()
        if os.path.exists(CONFIG_FILE):
            os.remove(CONFIG_FILE)

        self._config = AttrDict()
        time_zone = None
        player = None
        mkdir_p(CONFIG_DIR)

        while True:
            self.username = prompt("MLB.tv username: "******")
                continue

        tz_local = tzlocal.get_localzone().zone

        # password = prompt("MLB.tv password (will be stored in clear text!): ")
        found_players = list(find_players())
        if not found_players:
            print("no known media players found")
        else:
            print("found the following media players")
            print("\n".join([
                "\t%d: %s" % (n, p)
                for n, p in enumerate(["My player is not listed"] +
                                      found_players)
            ]))
            choice = int(
                prompt(
                    "Select the number corresponding to your preferred player,\n"
                    "or 0 if your player is not listed: ",
                    validator=RangeNumberValidator(
                        maximum=len(found_players))))
            if choice:
                player = found_players[choice - 1]

        while not player:
            response = prompt("Please enter the path to your media player: ")
            player = distutils.spawn.find_executable(response)
            if not player:
                print("Couldn't locate player '%s'" % (response))

        player_args = prompt(
            "If you need to pass additional arguments to your media "
            "player, enter them here: ")
        if player_args:
            player = " ".join([player, player_args])

        self.player = player

        print("Your system time zone seems to be %s." % (tz_local))
        if not confirm("Is that the time zone you'd like to use? (y/n) "):
            while not time_zone:
                response = prompt("Enter your preferred time zone: ")
                if response in pytz.common_timezones:
                    time_zone = response
                    break
                elif confirm("Can't find time zone %s: are you sure? (y/n) "):
                    time_zone = response
                    break

        else:
            time_zone = tz_local

        self.time_zone = time_zone
        self.save()

    def load(self):
        if not os.path.exists(self._config_file):
            raise Exception("config file %s not found" % (CONFIG_FILE))

        config = yaml.load(open(self._config_file), Loader=AttrDictYAMLLoader)
        if config.get("time_zone"):
            config.tz = pytz.timezone(config.time_zone)
        self._config = config

    def save(self):

        with open(self._config_file, 'w') as outfile:
            yaml.dump(self._config, outfile, default_flow_style=False)

    def __getattr__(self, name):
        return self._config.get(name, None)

    def __setattr__(self, name, value):

        if not name.startswith("_"):
            self._config[name] = value
        object.__setattr__(self, name, value)

    def __getitem__(self, key):
        return self._config[key]

    def __setitem__(self, key, value):
        self._config[key] = value

    def __delitem__(self, key):
        del self._config[key]

    def __len__(self):
        return len(self._config)

    def __iter__(self):
        return iter(self._config)