Giter Club home page Giter Club logo

pigskin's Introduction

All good things come to an end

After retiring NFL Gamepass Domestic in 2022, the NFL decided to shutdown the international Gamepass service as well in 2023, rendering this add-on obsolete.

Outside of the States, DAZN is the new home for the NFL Gamepass service. You can watch the games with a DAZN account and an NFL Gamepass subscription.

The good news is that there is a DAZN add-on available for Kodi, so it is still possible to watch NFL games on our most favorite media center.


A Python Library for NFL Game Pass

Build Status codecov Codacy Badge

Pigskin is a Python library for connecting to the NFL Game Pass service. The code originated with the xbmc-gamepass project, but was moved to its own repo to encourage reuse across projects.

This library handles authentication, querying of available games, shows, and statistics, and returns the URLs to watch authenticated streams. It is meant to be used by a variety of front-ends (such as Kodi, Plex, and VLC).

NOTE

Currently, only Game Pass Europe (WPP/Bruin) is supported, as none of the developers have a Game Pass International (NeuLion) subscription. If you're interested in getting International Support working again, we'd love to have your help.

Check out issue #1 for more information.

Dependencies

  • Requests 2.x

  • m3u8 >= 0.2.10

    • which needs iso8601

What is NFL Game Pass?

NFL Game Pass is service that allows those with subscriptions to watch NFL games. Live games, archives of old games, NFL TV shows, NFL Network, Red Zone, coaches tape (22 man view), and game statistics are available.

In 2017, the service split into two services, Game Pass Europe and Game Pass International.

What is Game Pass Europe?

Game Pass Europe uses WPP/Bruin as its streaming provider(s), and is currently the only service this library supports.

What is Game Pass International?

Game Pass International uses NeuLion as its streaming partner.

We are looking for a developer with access to an International subscription to resuscitate support for International subscription. If you're interested in helping out, check out issue #1.

Disclaimer

This library is unofficial and is not endorsed nor supported by the NFL or NFL Game Pass in any way.

pigskin's People

Contributors

aqw avatar baumschorle avatar dahlstrom avatar divingmule avatar emilsvennesson avatar eriksoderblom avatar jm-duke avatar kaileu avatar rva87 avatar timewasted avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

pigskin's Issues

Game Pass International Support

Much of this content comes from pigskin/kodi-gamepass#313


A quick background for the uninitiated:

In 2017, the Game Pass Service was split between two streaming providers:

Game Pass Europe (WPP/Bruin)
Game Pass International (NeuLion)

Currently all developers of this library are in Europe, so only Game Pass Europe is supported. We are very interested in developers from Game Pass International regions getting involved in development and maintenance.

In theory, it should not be too difficult to get decent support for Game Pass International. Prior to 2017, NeuLion was the only streaming provider. Version 0.10.2 of aqw/xbmc-gamepass used that service. As a result, many of the devs have experience with the NeuLion service, and we can help provide some pointers.

However, not all is easy. Predictably, NeuLion has changed its APIs quite a bit since the split (likely concern over the competition), so it's not as easy as a quick code-resuscitation. There will likely be some new paths to blaze.

It is important that consumers of pigskin.py should be able to use the same API regardless of which service (Europe vs International) they are connecting to. This will be problematic at times, but looks to be doable.

Two places where you can borrow code from

  • pigskin.py from xbmc-gamepass 0.10.2
  • dav-sap did some work in the international-support-2017 branch of dav-sap/xbmc-gamepass

Documentation

Is there any docs that says what each function does? I want to play with the international support, but outside of Kodi.

Retire pytest-vcr

It's much easier and more flexible to just use vcrpy directly.

Most tests have been converted, but there's a few remaining.

Attach logging directly to requests session handle

Rather than using _log_request().

I'm not sure exactly how to do this yet, but it has to be possible somehow. Either some form of callback, or simply using requests/urllib3/httplib's logging support directly. What's blocking using httplib directly is that the response body cannot be logged. Or so I've read. I havn't looked into why.

errors on login from kodi/nflgamepass addon

Hi there!

I just installed the gamepass addon from the Kodi addon repository hoping to watch the superbowl next weekend on my new Kodi install, rather than on my phone :D

Unfortunately after I input my credentials I only get an error dialog that reads failed_login which led me to this repo.

I hacked the pigskin.py script to dump it's log file to disk and see the following (my username is redacted):

... request/response to v1/web/config omitted ...
[pigskin]: Debugging enabled.
[pigskin]: Python Version: 2.7.15 (default, Oct 15 2018, 15:24:06) 
[GCC 8.1.1 20180712 (Red Hat 8.1.1-5)]
[pigskin]: Request URL: https://www.nflgamepass.com/api/user/oauth/token
[pigskin]: Method: post
[pigskin]: Payload: {'username': 'yyyyyy', 'password': 'xxxxxxxxxxxx', 'client_id': u'42cc360e-6fbb-4472-9437-2f088b8730de', 'grant_type': 'password'}
[pigskin]: Response code: 403
[pigskin]: Response: <HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>
 
You don't have permission to access "http&#58;&#47;&#47;www&#46;nflgamepass&#46;com&#47;api&#47;user&#47;oauth&#47;token" on this server.<P>
Reference&#32;&#35;18&#46;3ff90a17&#46;1548284086&#46;6ce5bc69
</BODY>
</HTML>

[pigskin]: Login Failed.
[pigskin]: <HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>
 
You don't have permission to access "http&#58;&#47;&#47;www&#46;nflgamepass&#46;com&#47;api&#47;user&#47;oauth&#47;token" on this server.<P>
Reference&#32;&#35;18&#46;3ff90a17&#46;1548284086&#46;6ce5bc69
</BODY>
</HTML>

Specifically the "you don't have permission" 403 error is the problem. I'm aware of the geolocated restriction of the service, and am physically located in Zurich, Switzerland. I generally have no problem using the web interfaces for game pass.

Any tips on how to debug this?

Auth - Response Code check

Not an issue - just a question.

Alex, this project is pretty awesome man ๐Ÿ‘ you're taking this from a limited use script to a full blown API and I for one can't wait.

I've been working on the kodi add-on side of things, would you be ok with some refactoring there?

I was looking over pigskin and i'm wondering why you don't check the response codes before attempting to parse the data and instead rely on exceptions? Is there an advantage to exceptions over validation (speed?).
It would just make it easier to log if we can distinguish between invalid/unparsable responses vs. auth issues.

Ex. from _gp_auth

try:
    r = self.http_session.post(url, data=post_data)
    self._log_request(r)
    # Invalid response, just return
    if r.status_code != requests.codes.ok:
        self.logger.warning('Login unsuccessful. Status code: {status_code}'.format(status_code=r.status_code))
        return {}
    else:
    data = r.json()

    # make sure auth data is there
    for key in ['access_token', 'refresh_token']:
        if not data.get(key):
            self.logger.error('could not parse auth response')
            return {}

except ValueError:
    self.logger.error('_gp_auth: server response is invalid')
    return {}

return data

refactor parse_shows()

Multiple functions need to be refactored here:

  • parse_shows()
  • get_shows()
  • get_shows_episodes()

I suggest that the information delivered by the API be inverted here. Right now, we are assuming that the consumer of the API wants to know all the seasons before getting into a show. The Game Pass website has the user select a show first before getting into the season/episodes. I think this is sane --- and certainly far more efficient.

This will cause serious UI breakage for xbmc-gamepass, and thus IMO, should happen as part of the new API being planned (#15).

New API

This will be edited to remain up-to-date with the the proposed API as it evolves.

The new API will take a much more OOP approach, and use context to save the user from the hassle of slinging values around all the time.

The information should be acquired on-demand, with caching (that can be overridden). Some information (such as seasons) should never expire, whereas game information should have a relatively short (say 5 minutes) TTL.

API

Games

  • gp.seasons - type OrderedDict of Season objects
    >>> type(gp.seasons['2016'])
    pigskin.season
    
  • gp.seasons['2016'] - type season
  • gp.seasons['2017'].weeks - type OrderedDict of season-types containing OrderedDicts of weeks with their corresponding Week object.
    >>> type(gp.seasons['2017'].weeks['pre']['0'])
    'pigskin.week'
    >>> print(gp.seasons['2017'].weeks['pre']['0'].desc)
    'Hall of Fame'
    >>> print(gp.seasons['2017'].weeks['reg']['8'].desc)
    ''
    >>> print(gp.seasons['2017'].weeks['post']['22'].desc)
    'Super Bowl'
    
  • gp.seasons['2017'].weeks['pre']['0'] type week
  • gp.season['2017']['reg'].week['8'].games - type OrderedDict of game objects.
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'] - type game.
    • Has tons of metadata. Will write about it in docs
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions - type OrderedDict of version objects.
    • List the versions available (full, condensed, coaches)
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions['full'] - type version
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions['full'].streams - type dict
    • List the streams available (hls, chromecast, etc) and their content URL.
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions['full'].streams['hls'] - type stream
    • Perhaps this is where m3u8_to_dict belongs
  • gp.season['2017'].teams - type OrderedDict of team objects
    • This is under season because teams do move :-/
    • TODO: I'm not sure if Game Pass Europe provides info for anything other than the current season, which could complicate things.
  • gp.season['2017'].teams['Packers'] - type team
    • will include plenty of info, such as name, city, logo, abbr
  • gp.season['2017'].teams['Packers'].games - type OrderedDict of game objects (similar to week.games. Example: gp.season['2017'].teams['Packers'].games['reg']['Bears@Packers'] -> game object
    • TODO: perhaps the game class should have a week property that points to the week object that the game belongs to.

Other

  • gp.current['season']
  • gp.current['week']
  • gp.nfldate_to_datetime(nfldate_string)
  • gp.subscription

Auth

  • gp.login(user, pass)
  • gp.logout()
  • gp.refresh_tokens()

Shows

  • gp.shows - type OrderedDict of shows
  • gp.shows['Hard Knocks'].seasons - type OrderedDict
  • gp.shows['Hard Knocks'].seasons['2017'] - type season
  • gp.shows['Hard Knocks'].seasons['2017'].episodes - type list
  • gp.shows['Hard Knocks'].seasons['2017'].episodes[0]- type episode
  • gp.shows['Hard Knocks'].seasons['2017'].episodes[0].streams
  • TODO: RedZone shows

Broadcast

  • gp.broadcast['nfl_network'].desc
  • gp.broadcast['nfl_network'].on_air
  • gp.broadcast['nfl_network'].streams
  • gp.broadcast['redzone'].desc
  • gp.broadcast['redzone'].on_air
  • gp.broadcast['redzone'].streams

Avoid authenticating against gigya if we don't have to

Gigya seems to be very touchy about authentication. Too many auths in quick succession results in a ban. This is very problematic when running tests, developing, etc.

We should detect if we already have access, and if so, just skip talking to gigya.

.raw() endpoint

A .raw() endpoint should be available on as many calls as possible, to expose the full server response.

I don't want pigskin to get in the business of normalizing and exposing every bit of information the services provide. It'll be unending, will break often, and the the different services may simply not provide all of the same info.

Having a .raw() call on objects would allow consumers of the API to get access to weird data they need without pigskin being on the hook for providing a stable API for it.

Switch to MIT License

Some projects will be put off by the LGPL license. To minimize barriers for adoption, it would be helpful to relicense this library under the MIT license.

If you contributed to the xbmc-gamepass project, it would be great if you could take the time to respond to this issue and state whether (or not) you'd be willing to license your previous contributions under the MIT license.

It may not be possible to get in contact with everyone in order to do this, but it's worth a try.

Contributors we need approval from:

Cookie Issue?

I ended up creating a VPS in Europe to play with pigskin, So if I just run a test script via python, which is just a simple gp.login() I get a 400 code. If I then open my browser and login via the webpage, then go right back to the cli, it works... Is there something I am missing? It does seem after about 6 hours i have to login to the browser again.

before browser login,
[pigskin]: Request URL: https://www.nflgamepass.com/api/user/oauth/token
[pigskin]: Method: post
[pigskin]: Payload: {'username': 'xxxxxxxxxxx', 'password': 'xxxxxxxxxxxx', 'client_id': u'xxxxxxxx-xxxx-xxxx-xxxxxxx', 'grant_type': 'password'}
[pigskin]: Response code: 400
[pigskin]: Response:
[pigskin]: Login Failed.
[pigskin]:

after browser login,
[pigskin]: Request URL: https://www.nflgamepass.com/api/user/oauth/token
[pigskin]: Method: post
[pigskin]: Payload: {'username': 'txxxxxxxxxxx', 'password': 'xxxxxxxxxxxx', 'client_id': u'xxxxxxxx-xxxx-xxxx-xxxxxxx', 'grant_type': 'password'}
[pigskin]: Response code: 200
[pigskin]: Response: {"access_token"

Caching

Right now, most functions and properties will cause a request to the Game Pass service only once. This is optimal in some places, and completely counterproductive in other places.

I'm not sure what is the best approach to take here, but my thoughts are:

  • should complete server responses be cached(see #19) and made available across functions/properties that would make the same request (gp.seasons and gp.seasons['2017'].weeks for example)
  • any caching should have a user configurable override (either to change the TTL, disable caching entirely, or perhaps get a fresh call just this one time)

TTL values should be sane and do what the user would expect.

  • gp.seasons - TTL of forever
    • seasons info will change once a year
  • gp.seasons['2017'].teams - TTL of forever
    • teams info will change once a year
  • gp.seasons['2017'].weeks - TTL of forever
    • weeks info will change once a year
  • gp.seasons['2017'].weeks['reg']['8'].games - TTL of forever if in the past (determined by current['season'] and current['week']; TTL of ??? minutes if the current season and week; TTL of forever (???) if in the future
    • this data is only expected to change if it's the current season/week.
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions - No caching (???)
  • gp.season['2017']['reg'].week['8'].games['Bears@Packers'].versions['full'].streams - No caching

Swap VCR cassettes according to which service is being tested

The end user experience when consuming the pigskin API should be the same regardless of which service (international or europe) that it's talking to. We should test that and enforce that. However, of course, those cassettes are completely different.

I havn't figured out exactly how I want to do this, but my current thoughts are:

  • pass arguments to pytest ("all", "europe", "international", something like that) to declare which backends are being tested).
    • these can simply be stored in a variable, and the cassette names will be loaded accordingly (such as tests/cassettes/pigskin/europe_<test-name>.yaml)
  • tests for the user-facing API should be placed in the tests/ folder.
  • tests for the the peculiarities of a service should be in a service-specific subfolder such as tests/europe.
    • cassettes can be stored in tests/cassettes/backends/europe/<test-file>_<test-name>.yaml)

get team games

get_team_games was just returning the teams array. I made it work for me, also add the option to search by nick, fullname and abbr. If no match return the seoname list. Not sure if I did it to your standard but it works.

def get_team_games(self, season, rteam):
        team = None
        rteams = []
        try:
            url = self.config['modules']['ROUTES_DATA_PROVIDERS']['teams']
            teams = self.make_request(url, 'get')
            if rteam is None:
                self.logger.error('Team = NONE' + rteam)    
                return teams
            else:
                # look for the team name
                self.logger.debug('Team = ' + rteam)    
                for conference in teams['modules']:
                    
                    if 'content' in teams['modules'][conference]:
                        for teamname in teams['modules'][conference]['content']:
                            rteams.append(str(teamname['seoname']))
                            self.logger.debug(teamname)
                            if rteam == teamname['fullName']:
                                team = teamname['seoname']
                                break
                            elif rteam == teamname['nick']:
                                team = teamname['seoname']
                                break
                            elif rteam == teamname['seoname']:
                                team = teamname['seoname']
                                break                       

                if team is None:
                    return rteams
                    #todo return team names
                else:
                    self.logger.debug('Team = ' + team)   
                    url = self.config['modules']['ROUTES_DATA_PROVIDERS']['team_detail'].replace(':team', team)
                    #self.logger.info('Teams URL' + url)
                    games_data = self.make_request(url, 'get')
                    # collect games from all keys in 'modules' for a specific season
                    # At the moment, only the Current Season which is supported;
                    # maybe the season category will return so this code will only
                    # be commented out.
                    # games = [g for x in games_data['modules'].keys() if x == 'videos'+season for g in games_data['modules'][x]['content']]
                    games = [g for x in games_data['modules'].keys() if x == 'gamesCurrentSeason' for g in games_data['modules'][x]['content']]
                    self.logger.debug(games)
        except:
            self.logger.error('Acquiring Team games data failed.')
            raise

        return sorted(games, key=lambda x: x['gameDateTimeUtc'])

test for check_for_subscription() fails

It always returns False, with an "unauthorized" response from the server. I can't figure out what the issue is, because I cannot reproduce it in ipython. But it fails every time when I test it using pytest.

Python3 Support

Starting with Kodi 18, Python3 compatibility will be required.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.